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() {
 
     // Unlocking a secret
-    $('button.unlock-secret').click(function (event) {
+    $('button.unlock-secret').click(function() {
         var secret_id = $(this).attr('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');
-        }
-
+        unlock_secret(secret_id);
     });
 
     // Locking a secret
-    $('button.lock-secret').click(function (event) {
+    $('button.lock-secret').click(function() {
         var secret_id = $(this).attr('secret-id');
-        var secret_div = $('#secret_' + secret_id);
-
-        // Delete the plaintext from the DOM element.
-        secret_div.html('********');
-        $(this).hide();
-        $(this).siblings('button.unlock-secret').show();
+        lock_secret(secret_id);
     });
 
     // Retrieve a session key
     $('#request_session_key').click(function() {
         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);
     });
 
@@ -43,23 +25,35 @@ $(document).ready(function() {
             type: 'GET',
             dataType: 'json',
             success: function (response, status) {
-                console.log("Secret retrieved successfully");
-                $('#secret_' + secret_id).html(response.plaintext);
-                $('button.unlock-secret[secret-id=' + secret_id + ']').hide();
-                $('button.lock-secret[secret-id=' + secret_id + ']').show();
+                if (response.plaintext) {
+                    console.log("Secret retrieved successfully");
+                    $('#secret_' + secret_id).html(response.plaintext);
+                    $('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) {
                 console.log("Error: " + xhr.responseText);
                 if (xhr.status == 403) {
                     alert("Permission denied");
                 } else {
-                    var json = jQuery.parseJSON(xhr.responseText);
-                    alert("Secret retrieval failed: " + json['error']);
+                    alert(xhr.responseText);
                 }
             }
         });
     }
 
+    // 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
     function get_session_key(private_key) {
         var csrf_token = $('input[name=csrfmiddlewaretoken]').val();
@@ -74,7 +68,7 @@ $(document).ready(function() {
                 xhr.setRequestHeader("X-CSRFToken", csrf_token);
             },
             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.');
             },
             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 extras.api.renderers import FormlessBrowsableAPIRenderer, FreeRADIUSClientsRenderer
+from secrets.exceptions import InvalidSessionKey
 from secrets.filters import SecretFilter
 from secrets.models import Secret, SecretRole, SessionKey, UserKey
 from utilities.api import WritableSerializerMixin
@@ -53,42 +54,50 @@ class SecretViewSet(WritableSerializerMixin, ModelViewSet):
     authentication_classes = [BasicAuthentication, SessionAuthentication]
     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
         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:
-            session_key = base64.b64decode(request.META['HTTP_X_SESSION_KEY'])
-        else:
-            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
+            return base64.b64decode(request.META['HTTP_X_SESSION_KEY'])
+        return None
 
     def retrieve(self, request, *args, **kwargs):
-        master_key = self._get_master_key(request)
+
         secret = self.get_object()
+        session_key = self._read_session_key(request)
 
-        if master_key is not None:
-            secret.decrypt(master_key)
+        # Retrieve session key cipher (if any) for the current user
+        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)
         return Response(serializer.data)
 
     def list(self, request, *args, **kwargs):
-        master_key = self._get_master_key(request)
+
         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
         page = self.paginate_queryset(queryset)
         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 utilities.models import CreatedUpdatedModel
 
+from .exceptions import InvalidSessionKey
 from .hashers import SecretValidationHasher
 
 
@@ -220,7 +221,7 @@ class SessionKey(models.Model):
 
         # Validate the provided session key
         if not check_password(session_key, self.hash):
-            raise Exception("Invalid session key")
+            raise InvalidSessionKey()
 
         # Decrypt master key using provided session key
         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>
                 </div>
                 <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
                     </button>
                 </div>