From 5dfcca96c870544b9ba66c83ad239bfefc8e6941 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 6 May 2020 15:17:06 -0400 Subject: [PATCH 1/4] Post-release version bump --- netbox/netbox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index a4d9ff618..ce352ebda 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -16,7 +16,7 @@ from django.core.validators import URLValidator # Environment setup # -VERSION = '2.8.2' +VERSION = '2.8.3-dev' # Hostname HOSTNAME = platform.node() From 3711283de53bfc7950ab56e6312f76578ee2d344 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 6 May 2020 23:43:46 -0400 Subject: [PATCH 2/4] Extend ViewTestCases to get and list objects as a non-authenticated user --- netbox/utilities/testing/testcases.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index de8b93232..d10bb025a 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -164,6 +164,13 @@ class ViewTestCases: response = self.client.get(instance.get_absolute_url()) self.assertHttpStatus(response, 200) + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_list_objects_anonymous(self): + # Make the request as an unauthenticated user + self.client.logout() + response = self.client.get(self.model.objects.first().get_absolute_url()) + self.assertHttpStatus(response, 200) + class CreateObjectViewTestCase(ModelViewTestCase): """ Create a single new instance. @@ -287,6 +294,13 @@ class ViewTestCases: self.assertHttpStatus(response, 200) self.assertEqual(response.get('Content-Type'), 'text/csv') + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_list_objects_anonymous(self): + # Make the request as an unauthenticated user + self.client.logout() + response = self.client.get(self._get_url('list')) + self.assertHttpStatus(response, 200) + class BulkCreateObjectsViewTestCase(ModelViewTestCase): """ Create multiple instances using a single form. Expects the creation of three new instances by default. From 5c1adf9e3775968842f6c0deddd7291ac924b554 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 6 May 2020 23:44:06 -0400 Subject: [PATCH 3/4] Fixes #4593: Fix AttributeError exception when viewing object lists as a non-authenticated user --- docs/release-notes/version-2.8.md | 8 ++++++++ netbox/extras/views.py | 14 ++++++++++---- netbox/templates/utilities/obj_list.html | 2 +- netbox/utilities/paginator.py | 7 +++++-- netbox/utilities/views.py | 8 +++++++- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/docs/release-notes/version-2.8.md b/docs/release-notes/version-2.8.md index e75bf4ab9..62d5e9920 100644 --- a/docs/release-notes/version-2.8.md +++ b/docs/release-notes/version-2.8.md @@ -1,5 +1,13 @@ # NetBox v2.8 +## v2.8.3 (FUTURE) + +### Bug Fixes + +* [#4593](https://github.com/netbox-community/netbox/issues/4593) - Fix AttributeError exception when viewing object lists as a non-authenticated user + +--- + ## v2.8.2 (2020-05-06) ### Enhancements diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 613e45132..1bfbb7abf 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -124,9 +124,12 @@ class ConfigContextView(PermissionRequiredMixin, View): # Determine user's preferred output format if request.GET.get('format') in ['json', 'yaml']: format = request.GET.get('format') - request.user.config.set('extras.configcontext.format', format, commit=True) - else: + if request.user.is_authenticated: + request.user.config.set('extras.configcontext.format', format, commit=True) + elif request.user.is_authenticated: format = request.user.config.get('extras.configcontext.format', 'json') + else: + format = 'json' return render(request, 'extras/configcontext.html', { 'configcontext': configcontext, @@ -181,9 +184,12 @@ class ObjectConfigContextView(View): # Determine user's preferred output format if request.GET.get('format') in ['json', 'yaml']: format = request.GET.get('format') - request.user.config.set('extras.configcontext.format', format, commit=True) - else: + if request.user.is_authenticated: + request.user.config.set('extras.configcontext.format', format, commit=True) + elif request.user.is_authenticated: format = request.user.config.get('extras.configcontext.format', 'json') + else: + format = 'json' return render(request, 'extras/object_configcontext.html', { model_name: obj, diff --git a/netbox/templates/utilities/obj_list.html b/netbox/templates/utilities/obj_list.html index 4cfa8b1ce..85ff050ed 100644 --- a/netbox/templates/utilities/obj_list.html +++ b/netbox/templates/utilities/obj_list.html @@ -5,7 +5,7 @@ {% block content %}
{% block buttons %}{% endblock %} - {% if table_config_form %} + {% if request.user.is_authenticated and table_config_form %} {% endif %} {% if permissions.add and 'add' in action_buttons %} diff --git a/netbox/utilities/paginator.py b/netbox/utilities/paginator.py index cef7c941f..cdad1f230 100644 --- a/netbox/utilities/paginator.py +++ b/netbox/utilities/paginator.py @@ -50,9 +50,12 @@ def get_paginate_count(request): if 'per_page' in request.GET: try: per_page = int(request.GET.get('per_page')) - request.user.config.set('pagination.per_page', per_page, commit=True) + if request.user.is_authenticated: + request.user.config.set('pagination.per_page', per_page, commit=True) return per_page except ValueError: pass - return request.user.config.get('pagination.per_page', settings.PAGINATE_COUNT) + if request.user.is_authenticated: + return request.user.config.get('pagination.per_page', settings.PAGINATE_COUNT) + return settings.PAGINATE_COUNT diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 3064abe4e..4b5993c5f 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -3,6 +3,7 @@ import sys from copy import deepcopy from django.contrib import messages +from django.contrib.auth.decorators import login_required from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldDoesNotExist, ValidationError from django.db import transaction, IntegrityError @@ -13,6 +14,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.template import loader from django.template.exceptions import TemplateDoesNotExist from django.urls import reverse +from django.utils.decorators import method_decorator from django.utils.html import escape from django.utils.http import is_safe_url from django.utils.safestring import mark_safe @@ -164,7 +166,10 @@ class ObjectListView(View): permissions[action] = request.user.has_perm(perm_name) # Construct the table based on the user's permissions - columns = request.user.config.get(f"tables.{self.table.__name__}.columns") + if request.user.is_authenticated: + columns = request.user.config.get(f"tables.{self.table.__name__}.columns") + else: + columns = None table = self.table(self.queryset, columns=columns) if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']): table.columns.show('pk') @@ -188,6 +193,7 @@ class ObjectListView(View): return render(request, self.template_name, context) + @method_decorator(login_required) def post(self, request): # Update the user's table configuration From af96ffb3e9d0ceb0dfa1e67b77d8628b466c3abf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 6 May 2020 23:46:52 -0400 Subject: [PATCH 4/4] Release v2.8.3 --- docs/release-notes/version-2.8.md | 2 +- netbox/netbox/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.8.md b/docs/release-notes/version-2.8.md index 62d5e9920..e3bd6b512 100644 --- a/docs/release-notes/version-2.8.md +++ b/docs/release-notes/version-2.8.md @@ -1,6 +1,6 @@ # NetBox v2.8 -## v2.8.3 (FUTURE) +## v2.8.3 (2020-05-06) ### Bug Fixes diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index ce352ebda..415c556e9 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -16,7 +16,7 @@ from django.core.validators import URLValidator # Environment setup # -VERSION = '2.8.3-dev' +VERSION = '2.8.3' # Hostname HOSTNAME = platform.node()