diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index e0eda087f..213db4180 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404 from rest_framework import generics from rest_framework import status -from rest_framework.permissions import IsAuthenticated, BasePermission +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.views import APIView @@ -20,11 +20,6 @@ ERR_USERKEY_INACTIVE = "UserKey has not been activated for decryption." ERR_PRIVKEY_INVALID = "Invalid private key." -class SecretViewPermission(BasePermission): - def has_permission(self, request, view): - return request.user.has_perm('secrets.view_secret') - - class SecretRoleListView(generics.ListAPIView): """ List all secret roles @@ -47,10 +42,10 @@ class SecretListView(generics.GenericAPIView): e.g. curl -X GET http://netbox/api/secrets/secrets/ --data-binary "@/path/to/private_key" """ - queryset = Secret.objects.select_related('device__primary_ip', 'role') + queryset = Secret.objects.select_related('device__primary_ip', 'role')\ + .prefetch_related('role__users', 'role__groups') serializer_class = SecretSerializer filter_class = SecretFilter - permission_classes = [SecretViewPermission] renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [FreeRADIUSClientsRenderer] def get(self, request, private_key=None, *args, **kwargs): @@ -74,7 +69,8 @@ class SecretListView(generics.GenericAPIView): master_key = uk.get_master_key(private_key) if master_key is not None: for s in queryset: - s.decrypt(master_key) + if s.decryptable_by(request.user): + s.decrypt(master_key) else: return Response( {'error': ERR_PRIVKEY_INVALID}, @@ -91,9 +87,9 @@ class SecretDetailView(generics.GenericAPIView): e.g. curl -X GET http://netbox/api/secrets/secrets/123/ --data-binary "@/path/to/private_key" """ - queryset = Secret.objects.select_related('device__primary_ip', 'role') + queryset = Secret.objects.select_related('device__primary_ip', 'role')\ + .prefetch_related('role__users', 'role__groups') serializer_class = SecretSerializer - permission_classes = [SecretViewPermission] renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [FreeRADIUSClientsRenderer] def get(self, request, pk, *args, **kwargs): @@ -114,14 +110,14 @@ class SecretDetailView(generics.GenericAPIView): {'error': ERR_USERKEY_INACTIVE}, status=status.HTTP_400_BAD_REQUEST ) - master_key = uk.get_master_key(private_key) - if master_key is not None: + if secret.decryptable_by(request.user): + master_key = uk.get_master_key(private_key) + if master_key is None: + return Response( + {'error': ERR_PRIVKEY_INVALID}, + status=status.HTTP_400_BAD_REQUEST + ) secret.decrypt(master_key) - else: - return Response( - {'error': ERR_PRIVKEY_INVALID}, - status=status.HTTP_400_BAD_REQUEST - ) serializer = self.get_serializer(secret) return Response(serializer.data) diff --git a/netbox/secrets/migrations/0004_auto_20160407_1548.py b/netbox/secrets/migrations/0004_auto_20160407_1548.py new file mode 100644 index 000000000..d2f7e1c5d --- /dev/null +++ b/netbox/secrets/migrations/0004_auto_20160407_1548.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.1 on 2016-04-07 15:48 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0007_alter_validators_add_error_messages'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('secrets', '0003_auto_20160321_1524'), + ] + + operations = [ + migrations.AlterModelOptions( + name='secret', + options={'ordering': ['device', 'role', 'name']}, + ), + migrations.AddField( + model_name='secretrole', + name='groups', + field=models.ManyToManyField(blank=True, related_name='secretroles', to='auth.Group'), + ), + migrations.AddField( + model_name='secretrole', + name='users', + field=models.ManyToManyField(blank=True, related_name='secretroles', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index 270f93501..e9c49665d 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -4,7 +4,7 @@ from Crypto.PublicKey import RSA from django.conf import settings from django.contrib.auth.hashers import make_password, check_password -from django.contrib.auth.models import User +from django.contrib.auth.models import Group, User from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models @@ -164,6 +164,8 @@ class SecretRole(models.Model): """ name = models.CharField(max_length=50, unique=True) slug = models.SlugField(unique=True) + users = models.ManyToManyField(User, related_name='secretroles', blank=True) + groups = models.ManyToManyField(Group, related_name='secretroles', blank=True) class Meta: ordering = ['name'] @@ -189,9 +191,6 @@ class Secret(models.Model): class Meta: ordering = ['device', 'role', 'name'] - permissions = ( - ('view_secret', "Can view secrets"), - ) def __init__(self, *args, **kwargs): self.plaintext = kwargs.pop('plaintext', None) @@ -279,3 +278,9 @@ class Secret(models.Model): if not self.hash: raise Exception("Hash has not been generated for this secret.") return check_password(plaintext, self.hash, preferred=SecretValidationHasher()) + + def decryptable_by(self, user): + """ + Check whether the given user has permission to decrypt this Secret. + """ + return user in self.role.users.all() or user.groups.filter(pk__in=self.role.groups.all()).exists()