Skip to content

Commit 3b72a3e

Browse files
committed
Process remote objects in attached methods
1 parent 1a0c7b8 commit 3b72a3e

File tree

5 files changed

+33
-10
lines changed

5 files changed

+33
-10
lines changed

ansible_base/rbac/evaluations.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import inspect
12
from typing import Iterable, Optional, Type
23

34
from django.conf import settings
@@ -120,7 +121,9 @@ def remote_obj_id_qs(actor, remote_cls: Type[RemoteObject], codename: str = 'vie
120121

121122

122123
def bound_has_obj_perm(self, obj, codename) -> bool:
123-
if not permission_registry.is_registered(obj):
124+
if (inspect.isclass(obj) and issubclass(obj, RemoteObject)) or isinstance(obj, RemoteObject):
125+
pass # no need to validate remote content type, assumed we have content type entry already
126+
elif not permission_registry.is_registered(obj):
124127
raise ValidationError(f'Object of {obj._meta.model_name} type is not registered with DAB RBAC')
125128
full_codename = validate_codename_for_model(codename, obj)
126129
if has_super_permission(self, full_codename):

ansible_base/rbac/models/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from django.db import connection
21
import inspect
32

3+
from django.db import connection
4+
45
from ..remote import RemoteObject
56
from .content_type import DABContentType
67
from .permission import DABPermission
@@ -20,8 +21,11 @@
2021

2122

2223
def get_evaluation_model(cls):
23-
if (inspect.isclass(cls) and issubclass(cls, RemoteObject)) or isinstance(cls, RemoteObject):
24+
if isinstance(cls, RemoteObject):
2425
# For remote models, we save the pk type in the database specifically for use here
26+
pk_db_type = cls.content_type.pk_field_type
27+
elif inspect.isclass(cls) and issubclass(cls, RemoteObject):
28+
# Weirdness when passed a remote class but not a remote object, get type first
2529
pk_db_type = cls.get_ct_from_type().pk_field_type
2630
else:
2731
pk_field = cls._meta.pk

ansible_base/rbac/remote.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@
1919
"""
2020

2121

22+
class StandinMeta:
23+
def __init__(self, ct: models.Model, abstract=False):
24+
self.service = ct.service
25+
self.model_name = ct.model
26+
self.app_label = ct.app_label
27+
self.abstract = abstract
28+
29+
2230
class RemoteObject:
2331
"""Placeholder for objects that live in another project."""
2432

2533
def __init__(self, content_type: models.Model, object_id: Union[int, str]):
2634
self.content_type = content_type
2735
self.object_id = object_id
36+
self._meta = StandinMeta(content_type, abstract=True)
2837

2938
def __repr__(self):
3039
return f"<RemoteObject {self.content_type} id={self.object_id}>"
@@ -48,6 +57,10 @@ def access_ids_qs(cls, actor, codename: str = 'view', content_types=None, cast_f
4857

4958
return remote_obj_id_qs(actor, remote_cls=cls, codename=codename, content_types=content_types, cast_field=cast_field)
5059

60+
@property
61+
def pk(self):
62+
return self.object_id
63+
5164

5265
def get_remote_base_class() -> Type[RemoteObject]:
5366
"""Return the class which represents remote objects.
@@ -120,12 +133,6 @@ def get_remote_standin_class(content_type: models.Model) -> Type:
120133
base = get_remote_base_class()
121134
name = f"Remote[{content_type.service}:{content_type.app_label}.{content_type.model}]"
122135

123-
class StandinMeta:
124-
def __init__(self, ct: models.Model):
125-
self.service = ct.service
126-
self.model_name = ct.model
127-
self.app_label = ct.app_label
128-
129136
standin = type(
130137
name,
131138
(base,),

ansible_base/rbac/validators.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ def prnt_codenames(codename_set: set[str]) -> str:
3030

3131
def codenames_for_remote_cls(cls: Union[Type[RemoteObject], RemoteObject]) -> list[str]:
3232
"""For remote objects, we have to use the database to get its known permissions"""
33-
ct = cls.get_ct_from_type()
33+
if inspect.isclass(cls):
34+
ct = cls.get_ct_from_type()
35+
else:
36+
ct = cls.content_type
3437
return [permission.codename for permission in ct.dab_permissions.all()]
3538

3639

test_app/tests/rbac/remote/test_remote_models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@ def test_give_remote_permission(rando):
1818

1919
# We can do evaluation querysets, but these can not return objects, just id values
2020
assert set(foo_type.model_class().access_ids_qs(actor=rando, codename='foo')) == {(int(assignment.object_id),)}
21+
22+
# Test that user-attached methods also work
23+
assert rando.has_obj_perm(a_foo, 'foo')
24+
with pytest.raises(RuntimeError) as exc:
25+
assert not rando.has_obj_perm(a_foo, 'bar') # not a valid permission
26+
assert 'The permission bar_foo is not valid for model foo' in str(exc)

0 commit comments

Comments
 (0)