Skip to content
Open
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
5 changes: 5 additions & 0 deletions docs/docs/waveforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ per-station granularity. To do that, add a new restriction in the admin
interface. As soon as a restriction has been added it will be considered
protected and only users that are part of the restriction will still be able
to access them.
Restrictions can also be defined with a single asterisk (`*`) in the station
(or network) code field, to make the restriction apply to all stations across a
specific network (to apply to all networks across a specific station code). Use
a single asterisk in *both* network and station code fields to add a
restriction on *all* stations.

![Add waveform restriction](./images/add_waveform_restriction.png)

Expand Down
132 changes: 132 additions & 0 deletions src/jane/fdsnws/tests/test_station_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,138 @@ def test_restrictions(self):
**auth_headers)
self.assertEqual(response.status_code, 204)

def test_restrictions_asterisk_network_and_station(self):
"""
Tests if the waveform restrictions actually work as expected.
"""
# No restrictions currently apply - we should get something.
response = self.client.get('/fdsnws/station/1/query')
self.assertEqual(response.status_code, 200)
self.assertTrue('OK' in response.reason_phrase)
inv = obspy.read_inventory(io.BytesIO(response.getvalue()))
self.assertEqual(inv.get_contents()["stations"],
["BW.ALTM (Beilngries, Bavaria, BW-Net)"])

# add restriction on all stations
r = Restriction.objects.get_or_create(network="*", station="*")[0]
r.users.add(User.objects.filter(username='random')[0])
r.save()

# Now the same query should no longer return something as the
# station has been restricted.
response = self.client.get('/fdsnws/station/1/query')
self.assertEqual(response.status_code, 204)

# The correct user can still get the stations.
response = self.client.get('/fdsnws/station/1/queryauth',
**self.valid_auth_headers)
self.assertEqual(response.status_code, 200)
self.assertTrue('OK' in response.reason_phrase)
inv = obspy.read_inventory(io.BytesIO(response.getvalue()))
self.assertEqual(inv.get_contents()["stations"],
["BW.ALTM (Beilngries, Bavaria, BW-Net)"])

# Make another user that has not been added to this restriction - he
# should not be able to retrieve it.
self.client.logout()
User.objects.get_or_create(
username='some_dude', password=make_password('some_dude'))[0]
credentials = base64.b64encode(b'some_dude:some_dude')
auth_headers = {
'HTTP_AUTHORIZATION': 'Basic ' + credentials.decode("ISO-8859-1")
}
response = self.client.get('/fdsnws/station/1/queryauth',
**auth_headers)
self.assertEqual(response.status_code, 204)

def test_restrictions_asterisk_network(self):
"""
Tests if the waveform restrictions actually work as expected.
"""
# No restrictions currently apply - we should get something.
response = self.client.get('/fdsnws/station/1/query')
self.assertEqual(response.status_code, 200)
self.assertTrue('OK' in response.reason_phrase)
inv = obspy.read_inventory(io.BytesIO(response.getvalue()))
self.assertEqual(inv.get_contents()["stations"],
["BW.ALTM (Beilngries, Bavaria, BW-Net)"])

# add restriction on ALTM stations
r = Restriction.objects.get_or_create(network="*", station="ALTM")[0]
r.users.add(User.objects.filter(username='random')[0])
r.save()

# Now the same query should no longer return something as the
# station has been restricted.
response = self.client.get('/fdsnws/station/1/query')
self.assertEqual(response.status_code, 204)

# The correct user can still get the stations.
response = self.client.get('/fdsnws/station/1/queryauth',
**self.valid_auth_headers)
self.assertEqual(response.status_code, 200)
self.assertTrue('OK' in response.reason_phrase)
inv = obspy.read_inventory(io.BytesIO(response.getvalue()))
self.assertEqual(inv.get_contents()["stations"],
["BW.ALTM (Beilngries, Bavaria, BW-Net)"])

# Make another user that has not been added to this restriction - he
# should not be able to retrieve it.
self.client.logout()
User.objects.get_or_create(
username='some_dude', password=make_password('some_dude'))[0]
credentials = base64.b64encode(b'some_dude:some_dude')
auth_headers = {
'HTTP_AUTHORIZATION': 'Basic ' + credentials.decode("ISO-8859-1")
}
response = self.client.get('/fdsnws/station/1/queryauth',
**auth_headers)
self.assertEqual(response.status_code, 204)

def test_restrictions_asterisk_station(self):
"""
Tests if the waveform restrictions actually work as expected.
"""
# No restrictions currently apply - we should get something.
response = self.client.get('/fdsnws/station/1/query')
self.assertEqual(response.status_code, 200)
self.assertTrue('OK' in response.reason_phrase)
inv = obspy.read_inventory(io.BytesIO(response.getvalue()))
self.assertEqual(inv.get_contents()["stations"],
["BW.ALTM (Beilngries, Bavaria, BW-Net)"])

# add restriction on all BW-network stations
r = Restriction.objects.get_or_create(network="BW", station="*")[0]
r.users.add(User.objects.filter(username='random')[0])
r.save()

# Now the same query should no longer return something as the
# station has been restricted.
response = self.client.get('/fdsnws/station/1/query')
self.assertEqual(response.status_code, 204)

# The correct user can still get the stations.
response = self.client.get('/fdsnws/station/1/queryauth',
**self.valid_auth_headers)
self.assertEqual(response.status_code, 200)
self.assertTrue('OK' in response.reason_phrase)
inv = obspy.read_inventory(io.BytesIO(response.getvalue()))
self.assertEqual(inv.get_contents()["stations"],
["BW.ALTM (Beilngries, Bavaria, BW-Net)"])

# Make another user that has not been added to this restriction - he
# should not be able to retrieve it.
self.client.logout()
User.objects.get_or_create(
username='some_dude', password=make_password('some_dude'))[0]
credentials = base64.b64encode(b'some_dude:some_dude')
auth_headers = {
'HTTP_AUTHORIZATION': 'Basic ' + credentials.decode("ISO-8859-1")
}
response = self.client.get('/fdsnws/station/1/queryauth',
**auth_headers)
self.assertEqual(response.status_code, 204)


class Station1LiveServerTestCase(LiveServerTestCase):
"""
Expand Down
19 changes: 17 additions & 2 deletions src/jane/stationxml/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,23 @@ def filter_queryset_user_does_not_have_permission(self, queryset,
pass
elif model_type == "index":
for restriction in restrictions:
queryset = queryset.exclude(json__network=restriction.network,
json__station=restriction.station)
kwargs = {}
# XXX in principle this could be handled simply by using a
# regex field lookup on the json field below, but in Django <
# 1.11 there's a bug so the regex lookup doesn't work, see
# django/django#6929
if restriction.network == '*' and restriction.station == '*':
# if both network and station are '*' then all stations are
# restricted
return queryset.none()
elif restriction.network == '*':
kwargs['json__station'] = restriction.station
elif restriction.station == '*':
kwargs['json__network'] = restriction.network
else:
kwargs['json__network'] = restriction.network
kwargs['json__station'] = restriction.station
queryset = queryset.exclude(**kwargs)
else:
raise NotImplementedError()
return queryset
Expand Down
31 changes: 31 additions & 0 deletions src/jane/waveforms/migrations/0003_auto_20200131_1056.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2020-01-31 10:56
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('waveforms', '0002_auto_20160706_1508'),
]

operations = [
migrations.AlterField(
model_name='restriction',
name='network',
field=models.CharField(db_index=True, help_text='Use a single asterisk/star (*) to affect all station codes. Use a single asterisk/star in both fields, to restrict all network/station code combinations.', max_length=2),
),
migrations.AlterField(
model_name='restriction',
name='station',
field=models.CharField(db_index=True, help_text='Use a single asterisk/star (*) to affect all station codes. Use a single asterisk/star in both fields, to restrict all network/station code combinations.', max_length=5),
),
migrations.AlterField(
model_name='restriction',
name='users',
field=models.ManyToManyField(db_index=True, help_text='The restriction defined by network/station code above will apply to all users that are not added here.', to=settings.AUTH_USER_MODEL),
),
]
14 changes: 11 additions & 3 deletions src/jane/waveforms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,17 @@ class Restriction(models.Model):

Waveforms are generally seen as public if not listed here.
"""
network = models.CharField(max_length=2, db_index=True)
station = models.CharField(max_length=5, db_index=True)
users = models.ManyToManyField(User, db_index=True)
help_text = ('Use a single asterisk/star (*) to affect all station '
'codes. Use a single asterisk/star in both fields, to '
'restrict all network/station code combinations.')
network = models.CharField(max_length=2, db_index=True,
help_text=help_text)
station = models.CharField(max_length=5, db_index=True,
help_text=help_text)
users = models.ManyToManyField(
User, db_index=True,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this has to be indexed - each index slows down the DB and I assume the other restrictions would already cut down the query enough so the user queries would be cheap? Not sure to be honest - might be worth to benchmark for a large database but the waveform table will be by far the biggest one so it has to reasonably fast.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didnt change the indexing, just added the help text.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw: changing the help_text requires a makemigrations too - even if it doesn't touch the database schema

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh.. on my local jane the help text shows up immediately..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure - its working right away - but I bet you get also a new warning that a schemamigration is required ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where should this warning pop up? at least in the debug mode runserver I dont see anything..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

python manage.py migrate

should show something like

  Your models have changes that are not yet reflected in a migration, and so won't be applied.
  Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also commit the migrations.

Copy link
Collaborator Author

@megies megies Sep 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh, the whole migrations stuff is still beyond me, I actually get errors due to some plugin changes when running python manage.py migrate, so hard to tell about warnings. usually I just scrap the whole DB and fill it in new right now (it's still pretty sandboxy)

help_text='The restriction defined by network/station code above will '
'apply to all users that are not added here.')
comment = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
created_by = models.ForeignKey(User, null=True, editable=False,
Expand Down