Implemented full read/write support for secrets

This commit is contained in:
Jeremy Stretch 2017-03-15 14:47:18 -04:00
parent 07a2b136b8
commit e3ae013e42

View File

@ -4,13 +4,11 @@ from Crypto.PublicKey import RSA
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
from rest_framework.authentication import BasicAuthentication, SessionAuthentication from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet from rest_framework.viewsets import ModelViewSet, ViewSet
from extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer
from secrets.exceptions import InvalidSessionKey from secrets.exceptions import InvalidSessionKey
from secrets.filters import SecretFilter from secrets.filters import SecretFilter
from secrets.models import Secret, SecretRole, SessionKey, UserKey from secrets.models import Secret, SecretRole, SessionKey, UserKey
@ -38,7 +36,6 @@ class SecretRoleViewSet(ModelViewSet):
# Secrets # Secrets
# #
# TODO: Need to implement custom create() and update() methods to handle secret encryption.
class SecretViewSet(WritableSerializerMixin, ModelViewSet): class SecretViewSet(WritableSerializerMixin, ModelViewSet):
queryset = Secret.objects.select_related( queryset = Secret.objects.select_related(
'device__primary_ip4', 'device__primary_ip6', 'role', 'device__primary_ip4', 'device__primary_ip6', 'role',
@ -48,11 +45,21 @@ class SecretViewSet(WritableSerializerMixin, ModelViewSet):
serializer_class = serializers.SecretSerializer serializer_class = serializers.SecretSerializer
write_serializer_class = serializers.WritableSecretSerializer write_serializer_class = serializers.WritableSecretSerializer
filter_class = SecretFilter filter_class = SecretFilter
# DRF's BrowsableAPIRenderer can't support passing the secret key as a header, so we disable it.
renderer_classes = [FormlessBrowsableAPIRenderer, JSONRenderer, FreeRADIUSClientsRenderer]
master_key = None master_key = None
def _get_encrypted_fields(self, serializer):
"""
Since we can't call encrypt() on the serializer like we can on the Secret model, we need to calculate the
ciphertext and hash values by encrypting a dummy copy. These can be passed to the serializer's save() method.
"""
s = Secret(plaintext=serializer.validated_data['plaintext'])
s.encrypt(self.master_key)
return ({
'ciphertext': s.ciphertext,
'hash': s.hash,
})
def initial(self, request, *args, **kwargs): def initial(self, request, *args, **kwargs):
super(SecretViewSet, self).initial(request, *args, **kwargs) super(SecretViewSet, self).initial(request, *args, **kwargs)
@ -66,13 +73,18 @@ class SecretViewSet(WritableSerializerMixin, ModelViewSet):
else: else:
session_key = None session_key = None
# We can't encrypt secret plaintext without a session key.
# assert False, self.action
if self.action in ['create', 'update'] and session_key is None:
raise ValidationError("A session key must be provided when creating or updating secrets.")
# Attempt to retrieve the master key for encryption/decryption if a session key has been provided. # Attempt to retrieve the master key for encryption/decryption if a session key has been provided.
if session_key is not None: if session_key is not None:
try: try:
sk = SessionKey.objects.get(userkey__user=request.user) sk = SessionKey.objects.get(userkey__user=request.user)
self.master_key = sk.get_master_key(session_key) self.master_key = sk.get_master_key(session_key)
except (SessionKey.DoesNotExist, InvalidSessionKey): except (SessionKey.DoesNotExist, InvalidSessionKey):
return HttpResponseBadRequest("Invalid session key.") raise ValidationError("Invalid session key.")
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
@ -107,6 +119,12 @@ class SecretViewSet(WritableSerializerMixin, ModelViewSet):
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
def perform_create(self, serializer):
serializer.save(**self._get_encrypted_fields(serializer))
def perform_update(self, serializer):
serializer.save(**self._get_encrypted_fields(serializer))
class GetSessionKeyViewSet(ViewSet): class GetSessionKeyViewSet(ViewSet):
""" """