Skip to content

Commit

Permalink
Improve time series data access control system (fixes #487)
Browse files Browse the repository at this point in the history
  • Loading branch information
aptiko committed Apr 17, 2023
1 parent 7367e91 commit 9cf480c
Show file tree
Hide file tree
Showing 24 changed files with 396 additions and 241 deletions.
7 changes: 6 additions & 1 deletion doc/dev/database.rst
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ Station and its related models

.. attribute:: enhydris.models.Station.overseer

The overseers is the person responsible for the meteorological
The overseer is the person responsible for the meteorological
station in the past. In the case of manual (not automatic)
stations, this means the weather observers. This is a simple text
field.
Expand Down Expand Up @@ -394,6 +394,11 @@ Time series and related models
it contains an appropriate time step as a `pandas "frequency"
string`_, e.g. "10min", "H", "M", "Y".

.. attribute:: enhydris.models.Timeseries.publicly_available

Specifies whether anonymous users can download the time series
data.

.. _pandas "frequency" string: https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects

.. method:: enhydris.models.Timeseries.get_data(start_date=None, end_date=None, timezone=None)
Expand Down
47 changes: 36 additions & 11 deletions doc/general/install.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. _install:
. _install:

==============================
Installation and configuration
Expand Down Expand Up @@ -250,16 +250,41 @@ These are the settings available to Enhydris, in addition to the
set to ``False`` (the default), only privileged users are allowed to
add/edit/remove data from the db.

See also :data:`ENHYDRIS_OPEN_CONTENT`.

.. data:: ENHYDRIS_OPEN_CONTENT

If set to ``True``, users who haven't logged on can view timeseries
data and station file (e.g. image) content. Otherwise, only logged on
users can do so. Logged on users can always view everything.

When this setting is ``False``, ``REGISTRATION_OPEN`` must obviously
also be set to ``False``.
See also :data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE` and
:data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS`.

.. data:: ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE

Time series have a
:attr:`enhydris.models.Timeseries.publicly_available` attribute which
specifies whether anonymous users can download the time series data.
If the attribute is ``False``, only logged on users have this
permission (and, again, this depends on
:data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS`). The setting
specifies the default value for the attribute, that is, whether by
default the related checkbox in the form is checked or not. The
default for the setting is ``True``, but it is recommended to
explicitly set it.

.. data:: ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS

If this is ``False`` (the default), all logged on users have
permission to download the time series data for all time series (for
anonymous user there's a
:attr:`enhydris.models.Timeseries.publicly_available` attribute for
each individual time series; see also
:data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE). Note that if you want
all logged on users to have such permission, but the general public
not to, you must also make sure that :data:`REGISTRATION_OPEN` is
``False``.

If :data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS` is ``True``,
logged on users can only view time series data for which they have
specifically been given permission. By default, only the station
owner and maintainers have such access, but they can specify which
other users also have access. Permission to view time series data
applies to all time series of a station. Individual time series can
again be marked as publicly available.

.. data:: ENHYDRIS_MAP_BASE_LAYERS

Expand Down
25 changes: 22 additions & 3 deletions doc/general/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,27 @@ Upgrading from 3.0
4. Login as a superuser and go to the dashboard.
5. Go to "Sites" and make sure they're set correctly.
6. In the settings, make sure ``SITE_ID`` is set correctly.
7. Update the database with ``python manage.py migrate``. This will put
7. In the settings, remove setting `ENHYDRIS_OPEN_CONTENT` and instead
set :data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE` and
:data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS`. Note that, during
migration, the new :attr:`publicly_available` attribute will be set
on existing time series to the value of
:data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE`.
8. Update the database with ``python manage.py migrate``. This will put
all stations to the site specified with ``SITE_ID``, and will add
all users to the group whose name is the domain of the current site
(the group will be created automatically if it does not exist).
8. If you have been using a single database to power many sites, then:
9. If you have been using a single database to power many sites, then:
* In the settings, make sure :data:`ENHYDRIS_SITES_FOR_NEW_STATIONS`
is set correctly. Restart the server if necessary.
* Logon as a superuseri and go to the dashboard
* Logon as a superuser and go to the dashboard
* Go to each of the stations that used to be specified by
``ENHYDRIS_SITE_STATION_FILTER`` and make sure the "Sites" field
is set correctly.
* If any users need to be able to log on to a different site from
the one where you performed the database update, go to each of
these users and put them in the appropriate groups.
10. Restart the server.

Changes from 3.0
----------------
Expand Down Expand Up @@ -94,6 +101,18 @@ If you discover errors after upgrading, fixing them is similar:
1. Download the time series that have the problem.
2. Upload the time series, specifying an appropriate time zone.

Permissions
^^^^^^^^^^^

Several things have been changed in the permissions model. Setting
``ENHYDRIS_OPEN_CONTENT`` has been abolished and replaced with the new
settings :data:`ENHYDRIS_DEFAULT_PUBLICLY_AVAILABLE` and
:data:`ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS`. These two settings
together can do much more than what ``ENHYDRIS_OPEN_CONTENT`` did.
Another important difference is that these new settings apply only
to time series data, not to gentity files, so gentity files are now
always publicly available.

Version 3.0
===========

Expand Down
3 changes: 3 additions & 0 deletions enhydris/admin/station.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ class TimeseriesInline(InlinePermissionsMixin, nested_admin.NestedStackedInline)
extra = 1
fields = (
("type", "time_step"),
"publicly_available",
("data", "default_timezone", "replace_or_append"),
)

Expand Down Expand Up @@ -373,6 +374,8 @@ def _get_permissions_fields(self, request, obj):
permissions_fields.append("creator")
if request.user.has_perm("enhydris.change_station_maintainers", obj):
permissions_fields.append("maintainers")
if settings.ENHYDRIS_ENABLE_TIMESERIES_DATA_VIEWERS:
permissions_fields.append("timeseries_data_viewers")
if permissions_fields:
self._fieldsets.append(
(
Expand Down
10 changes: 0 additions & 10 deletions enhydris/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,3 @@ def has_object_permission(self, request, view, obj):
return request.user.has_perm(
"enhydris.change_station", obj.timeseries_group.gentity.gpoint.station
)


class CanAccessGentityFileContent(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return request.user.has_perm("enhydris.view_gentityfile_content", obj)
else:
return request.user.has_perm(
"enhydris.change_station", obj.gentity.gpoint.station
)
32 changes: 0 additions & 32 deletions enhydris/api/tests/test_views/test_gentity_file.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from unittest.mock import MagicMock, mock_open, patch

from django.contrib.auth.models import User
from django.db.models.fields.files import FieldFile
from django.test.utils import override_settings
from rest_framework.test import APITestCase

from model_mommy import mommy
Expand Down Expand Up @@ -50,7 +48,6 @@ def test_detail_returns_nothing_if_wrong_station(self):
self.assertEqual(r.status_code, 404)


@override_settings(ENHYDRIS_OPEN_CONTENT=True)
class GentityFileContentTestCase(APITestCase):
def setUp(self):
# Mocking. We mock several things in Django and Python so that:
Expand Down Expand Up @@ -85,35 +82,6 @@ def test_content_type(self):
self.assertEqual(self.response["Content-Type"], "image/jpeg")


@override_settings(ENHYDRIS_OPEN_CONTENT=False)
@patch("enhydris.api.views.open", mock_open(read_data="ABCDEF"))
@patch("os.path.getsize", return_value=6)
class GentityFileContentPermissionsTestCase(APITestCase):
def setUp(self):
self.station = mommy.make(models.Station)
self.gentity_file = mommy.make(models.GentityFile, gentity=self.station)
self.url = "/api/stations/{}/files/{}/content/".format(
self.station.id, self.gentity_file.id
)
self.saved_fieldfile_file = FieldFile.file
self.filemock = MagicMock(**{"return_value.name": "some_image.jpg"})
FieldFile.file = property(self.filemock, MagicMock(), MagicMock())

def tearDown(self):
FieldFile.file = self.saved_fieldfile_file

def test_anonymous_user_is_denied(self, m):
self.response = self.client.get(self.url)
self.assertEqual(self.response.status_code, 401)

def test_logged_on_user_is_ok(self, m):
self.user1 = mommy.make(User, is_active=True, is_superuser=False)
self.client.force_authenticate(user=self.user1)
self.response = self.client.get(self.url)
self.assertEqual(self.response.status_code, 200)


@override_settings(ENHYDRIS_OPEN_CONTENT=True)
class GentityFileContentWithoutFileTestCase(APITestCase):
def setUp(self):
# Mommy creates a GentityFile without an associated file, so the
Expand Down

0 comments on commit 9cf480c

Please sign in to comment.