mirror of
				https://github.com/netbox-community/netbox.git
				synced 2024-05-10 07:54:54 +00:00 
			
		
		
		
	Secrets UI work
This commit is contained in:
		@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 | 
			
		||||
        if master_key is not None:
 | 
			
		||||
            secret.decrypt(master_key)
 | 
			
		||||
        secret = self.get_object()
 | 
			
		||||
        session_key = self._read_session_key(request)
 | 
			
		||||
 | 
			
		||||
        # 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
									
								
								netbox/secrets/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								netbox/secrets/exceptions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
class InvalidSessionKey(Exception):
 | 
			
		||||
    """
 | 
			
		||||
    Raised when the a provided session key is invalid.
 | 
			
		||||
    """
 | 
			
		||||
    pass
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user