diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c2df7f8d0..fcbf20578e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
This changelog format was introduced in NAV 5.4.0. Older changelogs can be
found in the [HISTORY](HISTORY) file.
+## [Unreleased]
+
+## Fixed
+
+- Merge two fixes from the 5.4.x stable series that never actually made it into the 5.5 series:
+ - Metric values of 0.0 are evaluated correctly by threshold rules [#2447](https://github.com/Uninett/nav/issues/2447)
+ - Validate maintenance calendar input form to avoid e-mail spam from bots scanning for vulnerabilities [#2420](https://github.com/Uninett/nav/issues/2420)
+
+
## [5.5.2] - 2022-11-10
### Fixed
diff --git a/python/nav/web/maintenance/forms.py b/python/nav/web/maintenance/forms.py
index 6b041165d3..2ef1961ad9 100644
--- a/python/nav/web/maintenance/forms.py
+++ b/python/nav/web/maintenance/forms.py
@@ -13,11 +13,11 @@
# details. You should have received a copy of the GNU General Public License
# along with NAV. If not, see .
#
+from datetime import date
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms_foundation.layout import Layout, Row, Column, Field
-
from nav.models.fields import INFINITY
@@ -72,3 +72,63 @@ class MaintenanceAddSingleNetbox(forms.Form):
a custom variable-checker"""
netboxid = forms.IntegerField(required=True)
+
+
+def _get_current_year():
+ return date.today().year
+
+
+def _get_current_month():
+ return date.today().month
+
+
+class MaintenanceCalendarForm(forms.Form):
+ """A form used for displaying a maintenance calendar"""
+
+ year = forms.IntegerField(
+ initial=_get_current_year, required=True, min_value=2000, max_value=2100
+ )
+ month = forms.IntegerField(
+ initial=_get_current_month, required=True, min_value=1, max_value=12
+ )
+
+ @property
+ def cleaned_year(self):
+ """Returns the cleaned year value if valid, current year otherwise"""
+ return self.cleaned_data['year'] if self.is_valid() else _get_current_year()
+
+ @property
+ def cleaned_month(self):
+ """Returns the cleaned month if valid, current month otherwise"""
+ return self.cleaned_data['month'] if self.is_valid() else _get_current_month()
+
+ @property
+ def this_month_start(self):
+ """Returns the first date of the month represented by this form instance"""
+ return date(self.cleaned_year, self.cleaned_month, 1)
+
+ @property
+ def next_month_start(self):
+ """Returns the first date of the month after the one represented by this form
+ instance
+ """
+ year = self.cleaned_year
+ month = self.cleaned_month + 1
+
+ if month > 12:
+ year += 1
+ month = 1
+ return date(year, month, 1)
+
+ @property
+ def previous_month_start(self):
+ """Returns the first date of the month before the one represented by this form
+ instance
+ """
+ year = self.cleaned_year
+ month = self.cleaned_month - 1
+
+ if month < 1:
+ year -= 1
+ month = 12
+ return date(year, month, 1)
diff --git a/python/nav/web/maintenance/views.py b/python/nav/web/maintenance/views.py
index 1b4a02b3c6..01d74adb8f 100644
--- a/python/nav/web/maintenance/views.py
+++ b/python/nav/web/maintenance/views.py
@@ -38,7 +38,7 @@
from nav.web.maintenance.utils import structure_component_data
from nav.web.maintenance.utils import task_form_initial, infodict_by_state
from nav.web.maintenance.utils import MaintenanceCalendar, NAVPATH, TITLE
-from nav.web.maintenance.forms import MaintenanceTaskForm
+from nav.web.maintenance.forms import MaintenanceTaskForm, MaintenanceCalendarForm
from nav.web.maintenance.forms import MaintenanceAddSingleNetbox
import nav.maintengine
@@ -55,56 +55,41 @@ def redirect_to_calendar(_request):
def calendar(request, year=None, month=None):
# If the form was used to get here, redirect to the appropriate page
if "year" in request.GET and "month" in request.GET:
- return redirect(
- "maintenance-calendar",
- year=request.GET.get("year"),
- month=request.GET.get("month"),
+ form = MaintenanceCalendarForm(data=request.GET.dict())
+ if form.is_valid():
+ return redirect(
+ "maintenance-calendar",
+ year=request.GET.get("year"),
+ month=request.GET.get("month"),
+ )
+ elif year and month:
+ form = MaintenanceCalendarForm(
+ data={'year': year, 'month': month},
)
+ else:
+ form = MaintenanceCalendarForm()
- heading = "Maintenance schedule"
- try:
- year = int(year)
- month = int(month)
- this_month_start = date(year, month, 1)
- except (TypeError, ValueError):
- year = date.today().year
- month = date.today().month
- this_month_start = date(year, month, 1)
-
- next_month = month + 1
- next_year = year
- if next_month > 12:
- next_year = year + 1
- next_month = 1
-
- prev_month = month - 1
- prev_year = year
- if prev_month < 1:
- prev_year = year - 1
- prev_month = 12
-
- prev_month_start = date(prev_year, prev_month, 1)
- next_month_start = date(next_year, next_month, 1)
tasks = (
MaintenanceTask.objects.filter(
- start_time__lt=next_month_start, end_time__gt=this_month_start
+ start_time__lt=form.next_month_start, end_time__gt=form.this_month_start
)
.exclude(state=MaintenanceTask.STATE_CANCELED)
.order_by('start_time')
)
- cal = MaintenanceCalendar(tasks).formatmonth(year, month)
+ cal = MaintenanceCalendar(tasks).formatmonth(form.cleaned_year, form.cleaned_month)
return render(
request,
'maintenance/calendar.html',
{
'active': {'calendar': True},
+ 'calendarform': form,
'navpath': NAVPATH,
'title': TITLE,
- 'heading': heading,
+ 'heading': "Maintenance schedule",
'calendar': mark_safe(cal),
- 'prev_month': prev_month_start,
- 'this_month': this_month_start,
- 'next_month': next_month_start,
+ 'prev_month': form.previous_month_start,
+ 'this_month': form.this_month_start,
+ 'next_month': form.next_month_start,
'curr_month': datetime.today(),
},
)
diff --git a/python/nav/web/templates/maintenance/calendar.html b/python/nav/web/templates/maintenance/calendar.html
index 713a27fc0e..a1f51f289b 100644
--- a/python/nav/web/templates/maintenance/calendar.html
+++ b/python/nav/web/templates/maintenance/calendar.html
@@ -11,8 +11,18 @@
@@ -26,9 +36,11 @@
-
- {{ calendar }}
+ {% if calendarform.errors %}
+ {{ calendarform.errors }}
+ {% else %}
+ {{ calendar }}
+ {% endif %}
-{% endblock %}
-
+{% endblock %}
\ No newline at end of file
diff --git a/tests/integration/web/maintenance/__init__.py b/tests/integration/web/maintenance/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/integration/web/maintenance/views_test.py b/tests/integration/web/maintenance/views_test.py
new file mode 100644
index 0000000000..78b1dc91ea
--- /dev/null
+++ b/tests/integration/web/maintenance/views_test.py
@@ -0,0 +1,25 @@
+#
+# Copyright (C) 2022 Sikt
+#
+# This file is part of Network Administration Visualized (NAV).
+#
+# NAV is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License version 3 as published by
+# the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details. You should have received a copy of the GNU General Public
+# License along with NAV. If not, see .
+#
+
+
+class TestMaintenanceCalendarView:
+ def test_calendar_renders_when_no_arguments_given(self, client):
+ response = client.get('/maintenance/', follow=True)
+ assert response.status_code == 200
+
+ def test_calendar_still_renders_when_invalid_arguments_given(self, client):
+ response = client.get('/maintenance/?year=invalid&month=invalid', follow=True)
+ assert response.status_code == 200
diff --git a/tests/unittests/web/maintenance/__init__.py b/tests/unittests/web/maintenance/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/unittests/web/maintenance/forms_test.py b/tests/unittests/web/maintenance/forms_test.py
new file mode 100644
index 0000000000..9910562d2b
--- /dev/null
+++ b/tests/unittests/web/maintenance/forms_test.py
@@ -0,0 +1,64 @@
+#
+# Copyright (C) 2022 Sikt
+#
+# This file is part of Network Administration Visualized (NAV).
+#
+# NAV is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License version 3 as published by
+# the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details. You should have received a copy of the GNU General Public
+# License along with NAV. If not, see .
+#
+from datetime import date
+
+import pytest
+
+from nav.web.maintenance.forms import MaintenanceCalendarForm
+
+
+class TestMaintenanceCalendarForm:
+ def test_cleaned_year_returns_integer(self, valid_form):
+ assert valid_form.cleaned_year == 2022
+
+ def test_cleaned_month_returns_integer(self, valid_form):
+ assert valid_form.cleaned_month == 7
+
+ def test_this_month_start_returns_the_first_day_of_the_month(self, valid_form):
+ assert valid_form.this_month_start == date(2022, 7, 1)
+
+ def test_next_month_start_returns_the_first_day_of_the_next_month(self, valid_form):
+ assert valid_form.next_month_start == date(2022, 8, 1)
+
+ def test_previous_month_start_returns_the_first_day_of_the_previous_month(
+ self, valid_form
+ ):
+ assert valid_form.previous_month_start == date(2022, 6, 1)
+
+ def test_when_month_is_december_next_month_start_returns_january(
+ self, december_form
+ ):
+ assert december_form.next_month_start == date(2023, 1, 1)
+
+ def test_when_month_is_january_previous_month_start_returns_december(
+ self, january_form
+ ):
+ assert january_form.previous_month_start == date(2021, 12, 1)
+
+
+@pytest.fixture
+def valid_form() -> MaintenanceCalendarForm:
+ return MaintenanceCalendarForm(data={'year': '2022', 'month': '7'})
+
+
+@pytest.fixture
+def december_form() -> MaintenanceCalendarForm:
+ return MaintenanceCalendarForm(data={'year': '2022', 'month': '12'})
+
+
+@pytest.fixture
+def january_form() -> MaintenanceCalendarForm:
+ return MaintenanceCalendarForm(data={'year': '2022', 'month': '1'})