Browse Source

Implemented full read/write support for secrets

Jeremy Stretch 8 years ago
parent
commit
e3ae013e42
1 changed files with 26 additions and 8 deletions
  1. 26 8
      netbox/secrets/api/views.py

+ 26 - 8
netbox/secrets/api/views.py

@@ -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):
     """
     """