Browse Source

Secrets UI work

Jeremy Stretch 8 years ago
parent
commit
105d17748e

+ 23 - 29
netbox/project-static/js/secrets.js

@@ -1,38 +1,20 @@
 $(document).ready(function() {
 $(document).ready(function() {
 
 
     // Unlocking a secret
     // Unlocking a secret
-    $('button.unlock-secret').click(function (event) {
+    $('button.unlock-secret').click(function() {
         var secret_id = $(this).attr('secret-id');
         var secret_id = $(this).attr('secret-id');
-
+        unlock_secret(secret_id);
-        // If we have an active cookie containing a session key, send the API request.
-        if (document.cookie.indexOf('session_key') > 0) {
-            console.log("Retrieving secret...");
-            unlock_secret(secret_id);
-        // Otherwise, prompt the user for a private key so we can request a session key.
-        } else {
-            console.log("No session key found. Prompt user for private key.");
-            $('#privkey_modal').modal('show');
-        }
-
     });
     });
 
 
     // Locking a secret
     // Locking a secret
-    $('button.lock-secret').click(function (event) {
+    $('button.lock-secret').click(function() {
         var secret_id = $(this).attr('secret-id');
         var secret_id = $(this).attr('secret-id');
-        var secret_div = $('#secret_' + secret_id);
+        lock_secret(secret_id);
-
-        // Delete the plaintext from the DOM element.
-        secret_div.html('********');
-        $(this).hide();
-        $(this).siblings('button.unlock-secret').show();
     });
     });
 
 
     // Retrieve a session key
     // Retrieve a session key
     $('#request_session_key').click(function() {
     $('#request_session_key').click(function() {
         var private_key = $('#user_privkey').val();
         var private_key = $('#user_privkey').val();
-
-        // POST the user's private key to request a temporary session key.
-        console.log("Requesting a session key...");
         get_session_key(private_key);
         get_session_key(private_key);
     });
     });
 
 
@@ -43,23 +25,35 @@ $(document).ready(function() {
             type: 'GET',
             type: 'GET',
             dataType: 'json',
             dataType: 'json',
             success: function (response, status) {
             success: function (response, status) {
-                console.log("Secret retrieved successfully");
+                if (response.plaintext) {
-                $('#secret_' + secret_id).html(response.plaintext);
+                    console.log("Secret retrieved successfully");
-                $('button.unlock-secret[secret-id=' + secret_id + ']').hide();
+                    $('#secret_' + secret_id).html(response.plaintext);
-                $('button.lock-secret[secret-id=' + secret_id + ']').show();
+                    $('button.unlock-secret[secret-id=' + secret_id + ']').hide();
+                    $('button.lock-secret[secret-id=' + secret_id + ']').show();
+                } else {
+                    console.log("Secret was not decrypted. Prompt user for private key.");
+                    $('#privkey_modal').modal('show');
+                }
             },
             },
             error: function (xhr, ajaxOptions, thrownError) {
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log("Error: " + xhr.responseText);
                 console.log("Error: " + xhr.responseText);
                 if (xhr.status == 403) {
                 if (xhr.status == 403) {
                     alert("Permission denied");
                     alert("Permission denied");
                 } else {
                 } else {
-                    var json = jQuery.parseJSON(xhr.responseText);
+                    alert(xhr.responseText);
-                    alert("Secret retrieval failed: " + json['error']);
                 }
                 }
             }
             }
         });
         });
     }
     }
 
 
+    // Remove secret data from the DOM
+    function lock_secret(secret_id) {
+        var secret_div = $('#secret_' + secret_id);
+        secret_div.html('********');
+        $('button.lock-secret[secret-id=' + secret_id + ']').hide();
+        $('button.unlock-secret[secret-id=' + secret_id + ']').show();
+    }
+
     // Request a session key via the API
     // Request a session key via the API
     function get_session_key(private_key) {
     function get_session_key(private_key) {
         var csrf_token = $('input[name=csrfmiddlewaretoken]').val();
         var csrf_token = $('input[name=csrfmiddlewaretoken]').val();
@@ -74,7 +68,7 @@ $(document).ready(function() {
                 xhr.setRequestHeader("X-CSRFToken", csrf_token);
                 xhr.setRequestHeader("X-CSRFToken", csrf_token);
             },
             },
             success: function (response, status) {
             success: function (response, status) {
-                console.log("Received a new session key; valid until " + response.expiration_time);
+                console.log("Received a new session key");
                 alert('Session key received! You may now unlock secrets.');
                 alert('Session key received! You may now unlock secrets.');
             },
             },
             error: function (xhr, ajaxOptions, thrownError) {
             error: function (xhr, ajaxOptions, thrownError) {

+ 30 - 21
netbox/secrets/api/views.py

@@ -11,6 +11,7 @@ from rest_framework.response import Response
 from rest_framework.viewsets import ViewSet, ModelViewSet
 from rest_framework.viewsets import ViewSet, ModelViewSet
 
 
 from extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer
 from extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer
+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
 from utilities.api import WritableSerializerMixin
 from utilities.api import WritableSerializerMixin
@@ -53,42 +54,50 @@ class SecretViewSet(WritableSerializerMixin, ModelViewSet):
     authentication_classes = [BasicAuthentication, SessionAuthentication]
     authentication_classes = [BasicAuthentication, SessionAuthentication]
     permission_classes = [IsAuthenticated]
     permission_classes = [IsAuthenticated]
 
 
-    def _get_master_key(self, request):
+    def _read_session_key(self, request):
 
 
         # Check for a session key provided as a cookie or header
         # Check for a session key provided as a cookie or header
         if 'session_key' in request.COOKIES:
         if 'session_key' in request.COOKIES:
-            session_key = base64.b64decode(request.COOKIES['session_key'])
+            return base64.b64decode(request.COOKIES['session_key'])
         elif 'HTTP_X_SESSION_KEY' in request.META:
         elif 'HTTP_X_SESSION_KEY' in request.META:
-            session_key = base64.b64decode(request.META['HTTP_X_SESSION_KEY'])
+            return base64.b64decode(request.META['HTTP_X_SESSION_KEY'])
-        else:
+        return None
-            return None
-
-        # Retrieve session key cipher (if any) for the current user
-        try:
-            sk = SessionKey.objects.get(user=request.user)
-        except SessionKey.DoesNotExist:
-            return None
-
-        # Recover master key
-        # TODO: Exception handling
-        master_key = sk.get_master_key(session_key)
-
-        return master_key
 
 
     def retrieve(self, request, *args, **kwargs):
     def retrieve(self, request, *args, **kwargs):
-        master_key = self._get_master_key(request)
+
         secret = self.get_object()
         secret = self.get_object()
+        session_key = self._read_session_key(request)
 
 
-        if master_key is not None:
+        # Retrieve session key cipher (if any) for the current user
-            secret.decrypt(master_key)
+        if session_key is not None:
+            try:
+                sk = SessionKey.objects.get(user=request.user)
+                master_key = sk.get_master_key(session_key)
+                secret.decrypt(master_key)
+            except SessionKey.DoesNotExist:
+                return HttpResponseBadRequest("No active session key for current user.")
+            except InvalidSessionKey:
+                return HttpResponseBadRequest("Invalid session key.")
 
 
         serializer = self.get_serializer(secret)
         serializer = self.get_serializer(secret)
         return Response(serializer.data)
         return Response(serializer.data)
 
 
     def list(self, request, *args, **kwargs):
     def list(self, request, *args, **kwargs):
-        master_key = self._get_master_key(request)
+
         queryset = self.filter_queryset(self.get_queryset())
         queryset = self.filter_queryset(self.get_queryset())
 
 
+        # Attempt to retrieve the master key for decryption
+        session_key = self._read_session_key(request)
+        master_key = None
+        if session_key is not None:
+            try:
+                sk = SessionKey.objects.get(user=request.user)
+                master_key = sk.get_master_key(session_key)
+            except SessionKey.DoesNotExist:
+                return HttpResponseBadRequest("No active session key for current user.")
+            except InvalidSessionKey:
+                return HttpResponseBadRequest("Invalid session key.")
+
         # Pagination
         # Pagination
         page = self.paginate_queryset(queryset)
         page = self.paginate_queryset(queryset)
         if page is not None:
         if page is not None:

+ 5 - 0
netbox/secrets/exceptions.py

@@ -0,0 +1,5 @@
+class InvalidSessionKey(Exception):
+    """
+    Raised when the a provided session key is invalid.
+    """
+    pass

+ 2 - 1
netbox/secrets/models.py

@@ -13,6 +13,7 @@ from django.utils.encoding import force_bytes, python_2_unicode_compatible
 from dcim.models import Device
 from dcim.models import Device
 from utilities.models import CreatedUpdatedModel
 from utilities.models import CreatedUpdatedModel
 
 
+from .exceptions import InvalidSessionKey
 from .hashers import SecretValidationHasher
 from .hashers import SecretValidationHasher
 
 
 
 
@@ -220,7 +221,7 @@ class SessionKey(models.Model):
 
 
         # Validate the provided session key
         # Validate the provided session key
         if not check_password(session_key, self.hash):
         if not check_password(session_key, self.hash):
-            raise Exception("Invalid session key")
+            raise InvalidSessionKey()
 
 
         # Decrypt master key using provided session key
         # Decrypt master key using provided session key
         master_key = xor_keys(session_key, self.cipher)
         master_key = xor_keys(session_key, self.cipher)

+ 1 - 1
netbox/templates/secrets/inc/private_key_modal.html

@@ -17,7 +17,7 @@
                     <textarea class="form-control" id="user_privkey" style="height: 300px;"></textarea>
                     <textarea class="form-control" id="user_privkey" style="height: 300px;"></textarea>
                 </div>
                 </div>
                 <div class="form-group text-right">
                 <div class="form-group text-right">
-                    <button id="request_session_key" class="btn btn-primary unlock-secret" data-dismiss="modal">
+                    <button id="request_session_key" class="btn btn-primary" data-dismiss="modal">
                         Request session key
                         Request session key
                     </button>
                     </button>
                 </div>
                 </div>