Updated permissions for contribution stats (bug 685382)

This commit is contained in:
Rob Hudson 2012-03-02 00:43:12 -08:00
Родитель 2ab1124b17
Коммит 24f9689f61
4 изменённых файлов: 109 добавлений и 55 удалений

Просмотреть файл

@ -1,4 +1,4 @@
from datetime import date, timedelta
import datetime
from django.core.management import call_command
@ -105,12 +105,12 @@ class TestIndexLatest(amo.tests.ESTestCase):
es = True
def test_index_latest(self):
latest = date.today() - timedelta(days=5)
latest = datetime.date.today() - datetime.timedelta(days=5)
UpdateCount.index({'date': latest})
self.refresh('update_counts')
start = latest.strftime('%Y-%m-%d')
finish = date.today().strftime('%Y-%m-%d')
finish = datetime.date.today().strftime('%Y-%m-%d')
with mock.patch('stats.cron.call_command') as call:
cron.index_latest_stats()
call.assert_called_with('index_stats', addons=None,

Просмотреть файл

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
import csv
import datetime
from datetime import date, timedelta
from decimal import Decimal
import json
@ -38,8 +37,11 @@ class StatsTest(amo.tests.ESTestCase):
def get_view_response(self, view, **kwargs):
view_args = self.url_args.copy()
head = kwargs.pop('head', False)
view_args.update(kwargs)
url = reverse(view, kwargs=view_args)
if head:
return self.client.head(url, follow=True)
return self.client.get(url, follow=True)
def views_gen(self, **kwargs):
@ -54,13 +56,13 @@ class StatsTest(amo.tests.ESTestCase):
def public_views_gen(self, **kwargs):
# all views are potentially public, except for contributions
for view, args in self.views_gen(**kwargs):
if view.find('stats.contributions') != 0:
if not view.startswith('stats.contributions'):
yield (view, args)
def private_views_gen(self, **kwargs):
# only contributions views are always private
for view, args in self.views_gen(**kwargs):
if view.find('stats.contributions') == 0:
if view.startswith('stats.contributions'):
yield (view, args)
@ -82,46 +84,80 @@ class ESStatsTest(StatsTest):
class TestSeriesSecurity(ESStatsTest):
"""Tests to make sure all restricted data remains restricted."""
def test_private_addon(self):
"""Ensure 403 for all series of an addon with private stats."""
# First as a logged in user with no special permissions
self.login_as_visitor()
for view, kwargs in self.views_gen(format='json'):
response = self.get_view_response(view, **kwargs)
eq_(response.status_code, 403,
'unexpected http status for %s' % view)
def _check_it(self, views, status):
for view, kwargs in views:
response = self.get_view_response(view, head=True, **kwargs)
eq_(response.status_code, status,
'unexpected http status for %s. got %s. expected %s' % (
view, response.status_code, status))
# Test view when user added to appropriate group.
def test_private_addon_no_groups(self):
# Logged in but no groups
self.login_as_visitor()
self._check_it(self.views_gen(format='json'), 403)
def test_private_addon_stats_group(self):
# Logged in with stats group.
user = UserProfile.objects.get(email='nobodyspecial@mozilla.com')
group = Group.objects.create(name='Stats', rules='Stats:View')
GroupUser.objects.create(user=user, group=group)
for view, kwargs in self.views_gen(format='json'):
response = self.get_view_response(view, **kwargs)
eq_(response.status_code, 200,
'unexpected http status for %s' % view)
# Again as an unauthenticated user
self.client.logout()
for view, kwargs in self.views_gen(format='json'):
response = self.get_view_response(view, **kwargs)
eq_(response.status_code, 403,
'unexpected http status for %s' % view)
def test_public_addon(self):
"""Ensure 403 for sensitive series of an addon with public stats."""
# First as a logged in user with no special permissions
self.login_as_visitor()
for view, kwargs in self.private_views_gen(addon_id=5, format='json'):
response = self.get_view_response(view, **kwargs)
eq_(response.status_code, 403,
'unexpected http status for %s' % view)
# Again as an unauthenticated user
self._check_it(self.public_views_gen(format='json'), 200)
self._check_it(self.private_views_gen(format='json'), 403)
def test_private_addon_contrib_stats_group(self):
# Logged in with stats and contrib stats group.
user = UserProfile.objects.get(email='nobodyspecial@mozilla.com')
group1 = Group.objects.create(name='Stats', rules='Stats:View')
GroupUser.objects.create(user=user, group=group1)
group2 = Group.objects.create(name='Contribution Stats',
rules='ContributionStats:View')
GroupUser.objects.create(user=user, group=group2)
self.login_as_visitor()
self._check_it(self.public_views_gen(format='json'), 200)
self._check_it(self.private_views_gen(format='json'), 200)
def test_private_addon_anonymous(self):
# Not logged in
self.client.logout()
for view, kwargs in self.private_views_gen(addon_id=5, format='json'):
response = self.get_view_response(view, **kwargs)
eq_(response.status_code, 403,
'unexpected http status for %s' % view)
self._check_it(self.views_gen(format='json'), 403)
def test_public_addon_no_groups(self):
# Logged in but no groups
self.login_as_visitor()
self._check_it(self.public_views_gen(addon_id=5, format='json'), 200)
self._check_it(self.private_views_gen(addon_id=5, format='json'), 403)
def test_public_addon_stats_group(self):
# Logged in with stats group.
user = UserProfile.objects.get(email='nobodyspecial@mozilla.com')
group = Group.objects.create(name='Stats', rules='Stats:View')
GroupUser.objects.create(user=user, group=group)
self.login_as_visitor()
self._check_it(self.public_views_gen(addon_id=5, format='json'), 200)
self._check_it(self.private_views_gen(addon_id=5, format='json'), 403)
def test_public_addon_contrib_stats_group(self):
# Logged in with stats and contrib stats group.
user = UserProfile.objects.get(email='nobodyspecial@mozilla.com')
group1 = Group.objects.create(name='Stats', rules='Stats:View')
GroupUser.objects.create(user=user, group=group1)
group2 = Group.objects.create(name='Contribution Stats',
rules='ContributionStats:View')
GroupUser.objects.create(user=user, group=group2)
self.login_as_visitor()
self._check_it(self.public_views_gen(addon_id=5, format='json'), 200)
self._check_it(self.private_views_gen(addon_id=5, format='json'), 200)
def test_public_addon_anonymous(self):
# Not logged in
self.client.logout()
self._check_it(self.public_views_gen(addon_id=5, format='json'), 200)
self._check_it(self.private_views_gen(addon_id=5, format='json'), 403)
class _TestCSVs(StatsTest, amo.tests.TestCase):
@ -253,12 +289,12 @@ class _TestCSVs(StatsTest, amo.tests.TestCase):
def test_no_cache(self):
"""Test that the csv or json is not caching, due to lack of data."""
self.url_args = {'start': '20200101', 'end': '20200130', 'addon_id': 4}
response = self.get_view_response('stats.versions_series',
response = self.get_view_response('stats.versions_series', head=True,
group='day', format='csv')
eq_(response["cache-control"], 'max-age=0')
self.url_args = {'start': '20200101', 'end': '20200130', 'addon_id': 4}
response = self.get_view_response('stats.versions_series',
response = self.get_view_response('stats.versions_series', head=True,
group='day', format='json')
eq_(response["cache-control"], 'max-age=0')
@ -278,7 +314,7 @@ class TestCacheControl(StatsTest, amo.tests.TestCase):
"""Tests we set cache control headers"""
def _test_cache_control(self):
response = self.get_view_response('stats.downloads_series',
response = self.get_view_response('stats.downloads_series', head=True,
group='month', format='json')
assert response.get('cache-control', '').startswith('max-age='), (
'Bad or no cache-control: %r' % response.get('cache-control', ''))
@ -402,7 +438,7 @@ class TestResponses(ESStatsTest):
])
def test_usage_by_os_csv(self):
r = self.get_view_response('stats.os_series', group='day',
r = self.get_view_response('stats.os_series', head=True, group='day',
format='csv')
eq_(r.status_code, 200)
@ -720,8 +756,8 @@ class TestSite(amo.tests.TestCase):
def tests_period_day(self, _site_query):
_site_query.return_value = ['.', '.']
start = (date.today() - timedelta(days=3))
end = date.today()
start = (datetime.date.today() - datetime.timedelta(days=3))
end = datetime.date.today()
self.client.get(reverse('stats.site.new',
args=['day', start.strftime('%Y%m%d'),
end.strftime('%Y%m%d'), 'json']))
@ -742,5 +778,6 @@ class TestSite(amo.tests.TestCase):
def tests_no_date(self, _site_query):
_site_query.return_value = ['.', '.']
self.client.get(reverse('stats.site', args=['json', 'date']))
eq_(_site_query.call_args[0][1], date.today() - timedelta(days=365))
eq_(_site_query.call_args[0][2], date.today())
eq_(_site_query.call_args[0][1],
datetime.date.today() - datetime.timedelta(days=365))
eq_(_site_query.call_args[0][2], datetime.date.today())

Просмотреть файл

@ -249,15 +249,26 @@ def check_stats_permission(request, addon, for_contributions=False):
Raises PermissionDenied if user is not allowed.
"""
if for_contributions or not addon.public_stats:
# only authenticated admins and authors
if (request.user.is_authenticated() and (
acl.action_allowed(request, 'Stats', 'View') or
addon.has_author(request.amo_user))):
return
elif addon.public_stats:
# non-contributions, public: everybody can view
# If public, non-contributions: everybody can view.
if addon.public_stats and not for_contributions:
return
# Everything else requires an authenticated user.
if not request.user.is_authenticated():
raise PermissionDenied
if not for_contributions:
# Only authors and Stats Viewers allowed.
if (addon.has_author(request.amo_user) or
acl.action_allowed(request, 'Stats', 'View')):
return
else: # For contribution stats.
# Only authors and Contribution Stats Viewers.
if (addon.has_author(request.amo_user) or
acl.action_allowed(request, 'ContributionStats', 'View')):
return
raise PermissionDenied

Просмотреть файл

@ -0,0 +1,6 @@
UPDATE groups SET name=CONCAT(name, ' (OLD)') WHERE name='Contributions Stats Viewers';
INSERT INTO groups (id, name, rules, notes, created, modified) VALUES
(50050, 'Contribution Stats Viewers', 'ContributionStats:View', '', NOW(), NOW());
INSERT INTO groups_users (
SELECT NULL, 50050, groups_users.user_id FROM groups, groups_users
WHERE groups.id=groups_users.group_id AND groups.name='Contributions Stats Viewers' AND groups.id < 50000);