Browse Source

Closes #985: Added preserve_key to get-session-key endpoint

Jeremy Stretch 8 years ago
parent
commit
5d022a575a
3 changed files with 39 additions and 14 deletions
  1. 24 10
      netbox/secrets/api/views.py
  2. 2 2
      netbox/secrets/exceptions.py
  3. 13 2
      netbox/secrets/models.py

+ 24 - 10
netbox/secrets/api/views.py

@@ -1,7 +1,6 @@
 import base64
 import base64
 from Crypto.PublicKey import RSA
 from Crypto.PublicKey import RSA
 
 
-from django.core.urlresolvers import reverse
 from django.http import HttpResponseBadRequest
 from django.http import HttpResponseBadRequest
 
 
 from rest_framework.exceptions import ValidationError
 from rest_framework.exceptions import ValidationError
@@ -9,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet, ViewSet
 from rest_framework.viewsets import ModelViewSet, ViewSet
 
 
-from secrets.exceptions import InvalidSessionKey
+from secrets.exceptions import InvalidKey
 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
@@ -84,7 +83,7 @@ class SecretViewSet(WritableSerializerMixin, ModelViewSet):
                 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, InvalidKey):
                     raise ValidationError("Invalid session key.")
                     raise ValidationError("Invalid session key.")
 
 
     def retrieve(self, request, *args, **kwargs):
     def retrieve(self, request, *args, **kwargs):
@@ -140,6 +139,9 @@ class GetSessionKeyViewSet(ViewSet):
         {
         {
             "session_key": "+8t4SI6XikgVmB5+/urhozx9O5qCQANyOk1MNe6taRf="
             "session_key": "+8t4SI6XikgVmB5+/urhozx9O5qCQANyOk1MNe6taRf="
         }
         }
+
+    This endpoint accepts one optional parameter: `preserve_key`. If True and a session key exists, the existing session
+    key will be returned instead of a new one.
     """
     """
     permission_classes = [IsAuthenticated]
     permission_classes = [IsAuthenticated]
 
 
@@ -163,14 +165,26 @@ class GetSessionKeyViewSet(ViewSet):
         if master_key is None:
         if master_key is None:
             return HttpResponseBadRequest(ERR_PRIVKEY_INVALID)
             return HttpResponseBadRequest(ERR_PRIVKEY_INVALID)
 
 
-        # Delete the existing SessionKey for this user if one exists
-        SessionKey.objects.filter(userkey__user=request.user).delete()
+        try:
+            current_session_key = SessionKey.objects.get(userkey__user_id=request.user.pk)
+        except SessionKey.DoesNotExist:
+            current_session_key = None
+
+        if current_session_key and request.GET.get('preserve_key', False):
+
+            # Retrieve the existing session key
+            key = current_session_key.get_session_key(master_key)
+
+        else:
+
+            # Create a new SessionKey
+            SessionKey.objects.filter(userkey__user=request.user).delete()
+            sk = SessionKey(userkey=user_key)
+            sk.save(master_key=master_key)
+            key = sk.key
 
 
-        # Create a new SessionKey
-        sk = SessionKey(userkey=user_key)
-        sk.save(master_key=master_key)
-        encoded_key = base64.b64encode(sk.key)
-        # b64decode() returns a bytestring under Python 3
+        # Encode the key using base64. (b64decode() returns a bytestring under Python 3.)
+        encoded_key = base64.b64encode(key)
         if not isinstance(encoded_key, str):
         if not isinstance(encoded_key, str):
             encoded_key = encoded_key.decode()
             encoded_key = encoded_key.decode()
 
 

+ 2 - 2
netbox/secrets/exceptions.py

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

+ 13 - 2
netbox/secrets/models.py

@@ -13,7 +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 .exceptions import InvalidKey
 from .hashers import SecretValidationHasher
 from .hashers import SecretValidationHasher
 
 
 
 
@@ -221,13 +221,24 @@ 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 InvalidSessionKey()
+            raise InvalidKey("Invalid session key")
 
 
         # Decrypt master key using provided session key
         # Decrypt master key using provided session key
         master_key = xor_keys(session_key, bytes(self.cipher))
         master_key = xor_keys(session_key, bytes(self.cipher))
 
 
         return master_key
         return master_key
 
 
+    def get_session_key(self, master_key):
+
+        # Recover session key using the master key
+        session_key = xor_keys(master_key, bytes(self.cipher))
+
+        # Validate the recovered session key
+        if not check_password(session_key, self.hash):
+            raise InvalidKey("Invalid master key")
+
+        return session_key
+
 
 
 @python_2_unicode_compatible
 @python_2_unicode_compatible
 class SecretRole(models.Model):
 class SecretRole(models.Model):