Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion netbox/extras/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,7 @@ def get(self, request, **kwargs):
# Markdown
#

class RenderMarkdownView(View):
class RenderMarkdownView(LoginRequiredMixin, View):

def post(self, request):
form = forms.RenderMarkdownForm(request.POST)
Expand Down
11 changes: 0 additions & 11 deletions netbox/netbox/middleware.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
import uuid
from urllib import parse

from django.conf import settings
from django.contrib import auth, messages
Expand Down Expand Up @@ -33,16 +32,6 @@ def __call__(self, request):
# Assign a random unique ID to the request. This will be used for change logging.
request.id = uuid.uuid4()

# Enforce the LOGIN_REQUIRED config parameter. If true, redirect all non-exempt unauthenticated requests
# to the login page.
if (
settings.LOGIN_REQUIRED and
not request.user.is_authenticated and
not request.path_info.startswith(settings.AUTH_EXEMPT_PATHS)
):
login_url = f'{settings.LOGIN_URL}?next={parse.quote(request.get_full_path_info())}'
return HttpResponseRedirect(login_url)

# Enable the event_tracking context manager and process the request.
with event_tracking(request):
response = self.get_response(request)
Expand Down
9 changes: 0 additions & 9 deletions netbox/netbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,15 +502,6 @@ def _setting(name, default=None):
('users', 'user'),
)

# All URLs starting with a string listed here are exempt from login enforcement
AUTH_EXEMPT_PATHS = (
f'/{BASE_PATH}api/',
f'/{BASE_PATH}graphql/',
f'/{BASE_PATH}login/',
f'/{BASE_PATH}oauth/',
f'/{BASE_PATH}metrics',
)

# All URLs starting with a string listed here are exempt from maintenance mode enforcement
MAINTENANCE_EXEMPT_PATHS = (
f'/{BASE_PATH}admin/',
Expand Down
11 changes: 6 additions & 5 deletions netbox/netbox/views/generic/feature_views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.contrib import messages
from django.db import transaction
Expand All @@ -12,7 +13,7 @@
from extras.models import JournalEntry
from extras.tables import JournalEntryTable
from utilities.permissions import get_permission_for_model
from utilities.views import GetReturnURLMixin, ViewTab
from utilities.views import ConditionalLoginRequiredMixin, GetReturnURLMixin, ViewTab
from .base import BaseMultiObjectView

__all__ = (
Expand All @@ -24,7 +25,7 @@
)


class ObjectChangeLogView(View):
class ObjectChangeLogView(ConditionalLoginRequiredMixin, View):
"""
Present a history of changes made to a particular object. The model class must be passed as a keyword argument
when referencing this view in a URL path. For example:
Expand Down Expand Up @@ -77,7 +78,7 @@ def get(self, request, model, **kwargs):
})


class ObjectJournalView(View):
class ObjectJournalView(ConditionalLoginRequiredMixin, View):
"""
Show all journal entries for an object. The model class must be passed as a keyword argument when referencing this
view in a URL path. For example:
Expand Down Expand Up @@ -138,7 +139,7 @@ def get(self, request, model, **kwargs):
})


class ObjectJobsView(View):
class ObjectJobsView(ConditionalLoginRequiredMixin, View):
"""
Render a list of all Job assigned to an object. For example:

Expand Down Expand Up @@ -191,7 +192,7 @@ def get(self, request, model, **kwargs):
})


class ObjectSyncDataView(View):
class ObjectSyncDataView(LoginRequiredMixin, View):

def post(self, request, model, **kwargs):
"""
Expand Down
3 changes: 2 additions & 1 deletion netbox/netbox/views/htmx.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404
Expand All @@ -6,7 +7,7 @@
from django.views.generic import View


class ObjectSelectorView(View):
class ObjectSelectorView(LoginRequiredMixin, View):
template_name = 'htmx/object_selector.html'

def get(self, request):
Expand Down
5 changes: 3 additions & 2 deletions netbox/netbox/views/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from netbox.tables import SearchTable
from utilities.htmx import htmx_partial
from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.views import ConditionalLoginRequiredMixin

__all__ = (
'HomeView',
Expand All @@ -28,7 +29,7 @@
Link = namedtuple('Link', ('label', 'viewname', 'permission', 'count'))


class HomeView(View):
class HomeView(ConditionalLoginRequiredMixin, View):
template_name = 'home.html'

def get(self, request):
Expand Down Expand Up @@ -62,7 +63,7 @@ def get(self, request):
})


class SearchView(View):
class SearchView(ConditionalLoginRequiredMixin, View):

def get(self, request):
results = []
Expand Down
18 changes: 15 additions & 3 deletions netbox/utilities/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Iterable

from django.conf import settings
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import ImproperlyConfigured
from django.urls import reverse
Expand All @@ -13,6 +14,7 @@
from .permissions import resolve_permission

__all__ = (
'ConditionalLoginRequiredMixin',
'ContentTypePermissionRequiredMixin',
'GetRelatedModelsMixin',
'GetReturnURLMixin',
Expand All @@ -27,10 +29,20 @@
# View Mixins
#

class ContentTypePermissionRequiredMixin(AccessMixin):
class ConditionalLoginRequiredMixin(AccessMixin):
"""
Similar to Django's LoginRequiredMixin, but enforces authentication only if LOGIN_REQUIRED is True.
"""
def dispatch(self, request, *args, **kwargs):
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)


class ContentTypePermissionRequiredMixin(ConditionalLoginRequiredMixin):
"""
Similar to Django's built-in PermissionRequiredMixin, but extended to check model-level permission assignments.
This is related to ObjectPermissionRequiredMixin, except that is does not enforce object-level permissions,
This is related to ObjectPermissionRequiredMixin, except that it does not enforce object-level permissions,
and fits within NetBox's custom permission enforcement system.

additional_permissions: An optional iterable of statically declared permissions to evaluate in addition to those
Expand Down Expand Up @@ -63,7 +75,7 @@ def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)


class ObjectPermissionRequiredMixin(AccessMixin):
class ObjectPermissionRequiredMixin(ConditionalLoginRequiredMixin):
"""
Similar to Django's built-in PermissionRequiredMixin, but extended to check for both model-level and object-level
permission assignments. If the user has only object-level permissions assigned, the view's queryset is filtered
Expand Down