Skip to content

Commit c030d2b

Browse files
committed
Add RBAC support
fixes: #399
1 parent 5f58544 commit c030d2b

File tree

13 files changed

+925
-51
lines changed

13 files changed

+925
-51
lines changed

CHANGES/399.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added RBAC support.

docs/tech-preview.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ The following features are currently being released as part of a tech preview
1010
* ``Twine`` upload packages to indexes at endpoints '/simple` or '/legacy'.
1111
* Create pull-through caches of remote sources.
1212
* Pulp Domain support
13+
* RBAC support

pulp_python/app/__init__.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
from django.db.models.signals import post_migrate
12
from pulpcore.plugin import PulpPluginAppConfig
3+
from gettext import gettext as _
24

35

46
class PulpPythonPluginAppConfig(PulpPluginAppConfig):
@@ -11,3 +13,54 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
1113
version = "3.12.0.dev"
1214
python_package_name = "pulp-python"
1315
domain_compatible = True
16+
17+
def ready(self):
18+
"""Register PyPI access policy hook."""
19+
super().ready()
20+
post_migrate.connect(
21+
_populate_pypi_access_policies,
22+
sender=self,
23+
dispatch_uid="populate_pypi_access_policies_identifier",
24+
)
25+
26+
27+
# TODO: Remove this when https://github.com/pulp/pulpcore/issues/5500 is resolved
28+
def _populate_pypi_access_policies(sender, apps, verbosity, **kwargs):
29+
from pulp_python.app.pypi.views import PyPIView, SimpleView, UploadView, MetadataView
30+
31+
try:
32+
AccessPolicy = apps.get_model("core", "AccessPolicy")
33+
except LookupError:
34+
if verbosity >= 1:
35+
print(_("AccessPolicy model does not exist. Skipping initialization."))
36+
return
37+
38+
for viewset in (PyPIView, SimpleView, UploadView, MetadataView):
39+
access_policy = getattr(viewset, "DEFAULT_ACCESS_POLICY", None)
40+
if access_policy is not None:
41+
viewset_name = viewset.urlpattern()
42+
db_access_policy, created = AccessPolicy.objects.get_or_create(
43+
viewset_name=viewset_name, defaults=access_policy
44+
)
45+
if created:
46+
if verbosity >= 1:
47+
print(
48+
"Access policy for {viewset_name} created.".format(
49+
viewset_name=viewset_name
50+
)
51+
)
52+
elif not db_access_policy.customized:
53+
dirty = False
54+
for key in ["statements", "creation_hooks", "queryset_scoping"]:
55+
value = access_policy.get(key)
56+
if getattr(db_access_policy, key, None) != value:
57+
setattr(db_access_policy, key, value)
58+
dirty = True
59+
if dirty:
60+
db_access_policy.save()
61+
if verbosity >= 1:
62+
print(
63+
"Access policy for {viewset_name} updated.".format(
64+
viewset_name=viewset_name
65+
)
66+
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from django.conf import settings
2+
3+
4+
# Access Condition methods that can be used with PyPI access policies
5+
6+
7+
def index_has_perm(request, view, action, perm="python.view_pythondistribution"):
8+
"""Access Policy condition that checks if the user has the perm on the index(distro)."""
9+
if request.user.has_perm(perm):
10+
return True
11+
if settings.DOMAIN_ENABLED:
12+
if request.user.has_perm(perm, obj=request.pulp_domain):
13+
return True
14+
return request.user.has_perm(perm, obj=view.distribution)
15+
16+
17+
def index_has_repo_perm(request, view, action, perm="python.view_pythonrepository"):
18+
"""
19+
Access Policy condition that checks if the user has the perm on the index's repository.
20+
21+
If index doesn't have a repository, then default return True.
22+
"""
23+
if request.user.has_perm(perm):
24+
return True
25+
if settings.DOMAIN_ENABLED:
26+
if request.user.has_perm(perm, obj=request.pulp_domain):
27+
return True
28+
if repo := view.distribution.repository:
29+
return request.user.has_perm(perm, obj=repo.cast())
30+
return True
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Generated by Django 4.2.10 on 2024-06-14 01:25
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('python', '0012_add_domain'),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name='pythondistribution',
15+
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('manage_roles_pythondistribution', 'Can manage roles on python distributions')]},
16+
),
17+
migrations.AlterModelOptions(
18+
name='pythonpublication',
19+
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('manage_roles_pythonpublication', 'Can manage roles on python publications')]},
20+
),
21+
migrations.AlterModelOptions(
22+
name='pythonremote',
23+
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('manage_roles_pythonremote', 'Can manage roles on python remotes')]},
24+
),
25+
migrations.AlterModelOptions(
26+
name='pythonrepository',
27+
options={'default_related_name': '%(app_label)s_%(model_name)s', 'permissions': [('sync_pythonrepository', 'Can start a sync task'), ('modify_pythonrepository', 'Can modify content of the repository'), ('manage_roles_pythonrepository', 'Can manage roles on python repositories'), ('repair_pythonrepository', 'Can repair repository versions')]},
28+
),
29+
]

pulp_python/app/models.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django.db import models
77
from django.conf import settings
88
from pulpcore.plugin.models import (
9+
AutoAddObjPermsMixin,
910
Content,
1011
Publication,
1112
Distribution,
@@ -47,7 +48,7 @@
4748
)
4849

4950

50-
class PythonDistribution(Distribution):
51+
class PythonDistribution(Distribution, AutoAddObjPermsMixin):
5152
"""
5253
Distribution for 'Python' Content.
5354
"""
@@ -119,6 +120,9 @@ def content_handler(self, path):
119120

120121
class Meta:
121122
default_related_name = "%(app_label)s_%(model_name)s"
123+
permissions = [
124+
("manage_roles_pythondistribution", "Can manage roles on python distributions"),
125+
]
122126

123127

124128
class NormalizeName(models.Transform):
@@ -213,7 +217,7 @@ class Meta:
213217
unique_together = ("sha256", "_pulp_domain")
214218

215219

216-
class PythonPublication(Publication):
220+
class PythonPublication(Publication, AutoAddObjPermsMixin):
217221
"""
218222
A Publication for PythonContent.
219223
"""
@@ -222,9 +226,12 @@ class PythonPublication(Publication):
222226

223227
class Meta:
224228
default_related_name = "%(app_label)s_%(model_name)s"
229+
permissions = [
230+
("manage_roles_pythonpublication", "Can manage roles on python publications"),
231+
]
225232

226233

227-
class PythonRemote(Remote):
234+
class PythonRemote(Remote, AutoAddObjPermsMixin):
228235
"""
229236
A Remote for Python Content.
230237
@@ -259,9 +266,12 @@ def get_remote_artifact_content_type(self, relative_path=None):
259266

260267
class Meta:
261268
default_related_name = "%(app_label)s_%(model_name)s"
269+
permissions = [
270+
("manage_roles_pythonremote", "Can manage roles on python remotes"),
271+
]
262272

263273

264-
class PythonRepository(Repository):
274+
class PythonRepository(Repository, AutoAddObjPermsMixin):
265275
"""
266276
Repository for "python" content.
267277
"""
@@ -274,6 +284,12 @@ class PythonRepository(Repository):
274284

275285
class Meta:
276286
default_related_name = "%(app_label)s_%(model_name)s"
287+
permissions = [
288+
("sync_pythonrepository", "Can start a sync task"),
289+
("modify_pythonrepository", "Can modify content of the repository"),
290+
("manage_roles_pythonrepository", "Can manage roles on python repositories"),
291+
("repair_pythonrepository", "Can repair repository versions"),
292+
]
277293

278294
def on_new_version(self, version):
279295
"""

pulp_python/app/pypi/serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,6 @@ class PackageUploadTaskSerializer(serializers.Serializer):
8989
A Serializer for responding to a package upload task.
9090
"""
9191

92-
session = serializers.CharField()
92+
session = serializers.CharField(allow_null=True)
9393
task = serializers.CharField()
94-
task_start_time = serializers.DateTimeField()
94+
task_start_time = serializers.DateTimeField(allow_null=True)

0 commit comments

Comments
 (0)