policies/api: fix PolicyBinding's target being validated against the wrong pks

This commit is contained in:
Jens Langhammer 2020-09-09 17:20:37 +02:00
parent 1776b72356
commit 860ba994a6
3 changed files with 42 additions and 74 deletions

View File

@ -1,36 +0,0 @@
"""passbook API Helpers"""
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.query import QuerySet
from model_utils.managers import InheritanceQuerySet
from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField
class InheritancePrimaryKeyRelatedField(PrimaryKeyRelatedField):
"""rest_framework PrimaryKeyRelatedField which resolves
model_manager's InheritanceQuerySet"""
def get_queryset(self) -> QuerySet:
queryset = super().get_queryset()
if isinstance(queryset, InheritanceQuerySet):
return queryset.select_subclasses()
return queryset
def to_internal_value(self, data):
if self.pk_field is not None:
data = self.pk_field.to_internal_value(data)
try:
queryset = self.get_queryset()
if isinstance(queryset, InheritanceQuerySet):
return queryset.get_subclass(pk=data)
return queryset.get(pk=data)
except ObjectDoesNotExist:
self.fail("does_not_exist", pk_value=data)
except (TypeError, ValueError):
self.fail("incorrect_type", data_type=type(data).__name__)
class InheritanceModelSerializer(ModelSerializer):
"""rest_framework ModelSerializer which automatically uses InheritancePrimaryKeyRelatedField
for every primary key"""
serializer_related_field = InheritancePrimaryKeyRelatedField

View File

@ -1,52 +1,55 @@
"""policy API Views""" """policy API Views"""
from rest_framework.serializers import ModelSerializer, SerializerMethodField from django.core.exceptions import ObjectDoesNotExist
from rest_framework.utils.model_meta import get_field_info from rest_framework.serializers import (
ModelSerializer,
PrimaryKeyRelatedField,
SerializerMethodField,
)
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from passbook.lib.api import InheritancePrimaryKeyRelatedField
from passbook.policies.forms import GENERAL_FIELDS from passbook.policies.forms import GENERAL_FIELDS
from passbook.policies.models import Policy, PolicyBinding, PolicyBindingModel from passbook.policies.models import Policy, PolicyBinding, PolicyBindingModel
class PolicyBindingModelForeignKey(PrimaryKeyRelatedField):
"""rest_framework PrimaryKeyRelatedField which resolves
model_manager's InheritanceQuerySet"""
def use_pk_only_optimization(self):
return False
def to_internal_value(self, data):
if self.pk_field is not None:
data = self.pk_field.to_internal_value(data)
try:
# Due to inheritance, a direct DB lookup for the primary key
# won't return anything. This is because the direct lookup
# checks the PK of PolicyBindingModel (for example),
# but we get given the Primary Key of the inheriting class
for model in self.get_queryset().select_subclasses().all().select_related():
if model.pk == data:
return model
# as a fallback we still try a direct lookup
return self.get_queryset().get_subclass(pk=data)
except ObjectDoesNotExist:
self.fail("does_not_exist", pk_value=data)
except (TypeError, ValueError):
self.fail("incorrect_type", data_type=type(data).__name__)
def to_representation(self, value):
correct_model = PolicyBindingModel.objects.get_subclass(pbm_uuid=value.pbm_uuid)
return correct_model.pk
class PolicyBindingSerializer(ModelSerializer): class PolicyBindingSerializer(ModelSerializer):
"""PolicyBinding Serializer""" """PolicyBinding Serializer"""
# Because we're not interested in the PolicyBindingModel's PK but rather the subclasses PK, # Because we're not interested in the PolicyBindingModel's PK but rather the subclasses PK,
# we have to manually declare this field # we have to manually declare this field
target = InheritancePrimaryKeyRelatedField( target = PolicyBindingModelForeignKey(
queryset=PolicyBindingModel.objects.all().select_subclasses(), queryset=PolicyBindingModel.objects.select_subclasses(), required=True,
source="target.pk",
required=True,
) )
def update(self, instance, validated_data):
info = get_field_info(instance)
# Simply set each attribute on the instance, and then save it.
# Note that unlike `.create()` we don't need to treat many-to-many
# relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with.
m2m_fields = []
for attr, value in validated_data.items():
if attr in info.relations and info.relations[attr].to_many:
m2m_fields.append((attr, value))
else:
if attr == "target":
instance.target_pk = value["pk"].pbm_uuid
else:
setattr(instance, attr, value)
instance.save()
# Note that many-to-many fields are set after updating instance.
# Setting m2m fields triggers signals which could potentially change
# updated instance and we do not want it to collide with .update()
for attr, value in m2m_fields:
field = getattr(instance, attr)
field.set(value)
return instance
class Meta: class Meta:
model = PolicyBinding model = PolicyBinding

View File

@ -1,7 +1,7 @@
"""passbook policy engine""" """passbook policy engine"""
from multiprocessing import Pipe, set_start_method from multiprocessing import Pipe, set_start_method
from multiprocessing.connection import Connection from multiprocessing.connection import Connection
from typing import List, Optional from typing import Iterator, List, Optional
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpRequest from django.http import HttpRequest
@ -40,7 +40,7 @@ class PolicyProcessInfo:
class PolicyEngine: class PolicyEngine:
"""Orchestrate policy checking, launch tasks and return result""" """Orchestrate policy checking, launch tasks and return result"""
use_cache: bool = True use_cache: bool
request: PolicyRequest request: PolicyRequest
__pbm: PolicyBindingModel __pbm: PolicyBindingModel
@ -58,8 +58,9 @@ class PolicyEngine:
self.request.http_request = request self.request.http_request = request
self.__cached_policies = [] self.__cached_policies = []
self.__processes = [] self.__processes = []
self.use_cache = True
def _iter_bindings(self) -> List[PolicyBinding]: def _iter_bindings(self) -> Iterator[PolicyBinding]:
"""Make sure all Policies are their respective classes""" """Make sure all Policies are their respective classes"""
return PolicyBinding.objects.filter(target=self.__pbm, enabled=True).order_by( return PolicyBinding.objects.filter(target=self.__pbm, enabled=True).order_by(
"order" "order"