From fd6df8e52a0876780b1c223a4cc129bb930b0b72 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 2 Aug 2017 11:17:57 -0400 Subject: [PATCH] Fixes #1385: Connected device API endpoint no longer requires authentication if LOGIN_REQUIRED=False --- netbox/dcim/api/views.py | 5 ++--- netbox/utilities/api.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 116c8db00..56d4221da 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -3,7 +3,6 @@ from collections import OrderedDict from rest_framework.decorators import detail_route from rest_framework.mixins import ListModelMixin -from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet @@ -21,7 +20,7 @@ from dcim import filters from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE -from utilities.api import ServiceUnavailable, WritableSerializerMixin +from utilities.api import IsAuthenticatedOrLoginNotRequired, ServiceUnavailable, WritableSerializerMixin from .exceptions import MissingFilterException from . import serializers @@ -387,7 +386,7 @@ class ConnectedDeviceViewSet(ViewSet): * `peer-device`: The name of the peer device * `peer-interface`: The name of the peer interface """ - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticatedOrLoginNotRequired] def get_view_name(self): return "Connected Device Locator" diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 5774584a6..6a515b21d 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -4,9 +4,10 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from rest_framework import authentication, exceptions +from rest_framework.compat import is_authenticated from rest_framework.exceptions import APIException from rest_framework.pagination import LimitOffsetPagination -from rest_framework.permissions import DjangoModelPermissions, SAFE_METHODS +from rest_framework.permissions import BasePermission, DjangoModelPermissions, SAFE_METHODS from rest_framework.serializers import Field, ValidationError from users.models import Token @@ -20,6 +21,10 @@ class ServiceUnavailable(APIException): default_detail = "Service temporarily unavailable, please try again later." +# +# Authentication +# + class TokenAuthentication(authentication.TokenAuthentication): """ A custom authentication scheme which enforces Token expiration times. @@ -61,6 +66,20 @@ class TokenPermissions(DjangoModelPermissions): return super(TokenPermissions, self).has_permission(request, view) +class IsAuthenticatedOrLoginNotRequired(BasePermission): + """ + Returns True if the user is authenticated or LOGIN_REQUIRED is False. + """ + def has_permission(self, request, view): + if not settings.LOGIN_REQUIRED: + return True + return request.user and is_authenticated(request.user) + + +# +# Serializers +# + class ChoiceFieldSerializer(Field): """ Represent a ChoiceField as {'value': , 'label': }. @@ -98,6 +117,10 @@ class ContentTypeFieldSerializer(Field): raise ValidationError("Invalid content type") +# +# Mixins +# + class ModelValidationMixin(object): """ Enforce a model's validation through clean() when validating serializer data. This is necessary to ensure we're @@ -119,6 +142,10 @@ class WritableSerializerMixin(object): return self.serializer_class +# +# Pagination +# + class OptionalLimitOffsetPagination(LimitOffsetPagination): """ Override the stock paginator to allow setting limit=0 to disable pagination for a request. This returns all objects