Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #429 from glogiotatidis/stronghold

Stronghold
  • Loading branch information...
commit 165f47ce6b3a618a48ca3440856168bef171140f 2 parents 32358d9 + 0ccade4
@Sancus Sancus authored
Showing with 267 additions and 213 deletions.
  1. +21 −0 apps/common/decorators.py
  2. +33 −49 apps/common/middleware.py
  3. 0  apps/common/tests/__init__.py
  4. 0  apps/common/{tests.py → tests/init.py}
  5. +55 −0 apps/common/tests/test_stronghold.py
  6. +29 −0 apps/common/tests/test_urls.py
  7. +2 −2 apps/groups/tests/test_groups.py
  8. +2 −2 apps/groups/tests/test_languages.py
  9. +1 −1  apps/groups/tests/test_middleware.py
  10. +2 −2 apps/groups/tests/test_skills.py
  11. +6 −10 apps/groups/urls.py
  12. +12 −18 apps/groups/views.py
  13. +2 −2 apps/phonebook/forms.py
  14. +1 −1  apps/phonebook/tests/test_edit_profile.py
  15. +7 −7 apps/phonebook/tests/test_invites.py
  16. +1 −1  apps/phonebook/tests/test_modelform.py
  17. +1 −1  apps/phonebook/tests/test_search.py
  18. +0 −18 apps/phonebook/tests/test_trailing_slash_middleware.py
  19. +5 −5 apps/phonebook/tests/test_views.py
  20. +18 −16 apps/phonebook/urls.py
  21. +34 −46 apps/phonebook/views.py
  22. +2 −2 apps/users/tests/test_api.py
  23. +1 −1  apps/users/tests/test_basket.py
  24. +1 −1  apps/users/tests/test_incomplete_profiles.py
  25. +3 −3 apps/users/tests/tests.py
  26. +2 −2 apps/users/urls.py
  27. +3 −8 apps/users/views.py
  28. +3 −3 media/js/groups.js
  29. +12 −7 settings/default.py
  30. +8 −5 urls.py
View
21 apps/common/decorators.py
@@ -0,0 +1,21 @@
+from functools import partial
+
+
+def _set_attribute_func(function, attribute, value):
+ """Helper to set attributes to func and methods."""
+ orig_func = function
+ while isinstance(orig_func, partial):
+ orig_func = orig_func.func
+ setattr(orig_func, attribute, value)
+
+
+def allow_public(function):
+ """Allow view to be accessed by anonymous users."""
+ _set_attribute_func(function, '_allow_public', True)
+ return function
+
+
+def allow_unvouched(function):
+ """Allow view to be accessed by unvouched users."""
+ _set_attribute_func(function, '_allow_unvouched', True)
+ return function
View
82 apps/common/middleware.py
@@ -1,69 +1,51 @@
import re
-import os
from contextlib import contextmanager
-
-from django.contrib.auth.models import User
-from django.http import (HttpResponseForbidden, HttpResponseNotAllowed,
- HttpResponseRedirect, HttpResponsePermanentRedirect)
+from django.conf import settings
from django.core.urlresolvers import is_valid_path, reverse
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
+from django.contrib import messages
+from django.http import HttpResponseRedirect
from django.utils.encoding import iri_to_uri
-
-import commonware.log
-from funfactory.manage import ROOT
+from django.shortcuts import redirect
+from tower import ugettext as _
from apps.groups.models import Group, GroupAlias
-# TODO: this is hackish. Once we update mozillians to the newest playdoh layout
-error_page = __import__('%s.urls' % os.path.basename(ROOT)).urls.error_page
-log = commonware.log.getLogger('m.phonebook')
+class StrongholdMiddleware(object):
+ """Keep unvouched users out, unless explicitly allowed in.
-class PermissionDeniedMiddleware(object):
- """Add a generic 40x 'not allowed' handler.
+ Inspired by https://github.com/mgrouchy/django-stronghold/
- TODO: Currently uses the 500.html error template, but in the
- future should display a more tailored-to-the-actual-error 'not
- allowed' page.
"""
- def process_response(self, request, response):
- if isinstance(response, (HttpResponseForbidden,
- HttpResponseNotAllowed)):
- if request.user.is_authenticated():
- log.debug('Permission denied middleware, user was '
- 'authenticated, sending 500')
- else:
- if isinstance(response, (HttpResponseForbidden)):
- log.debug('Response was forbidden')
- elif isinstance(response, (HttpResponseNotAllowed)):
- log.debug('Response was not allowed')
- return error_page(request, 500, status=response.status_code)
- return response
+ def __init__(self):
+ self.exceptions = getattr(settings, 'STRONGHOLD_EXCEPTIONS', [])
+ def process_view(self, request, view_func, view_args, view_kwargs):
+ for view_url in self.exceptions:
+ if re.match(view_url, request.path):
+ return None
-class RemoveSlashMiddleware(object):
- """Middleware that tries to remove a trailing slash if there was a 404.
+ allow_public = getattr(view_func, '_allow_public', None)
+ if allow_public:
+ return None
- If the response is a 404 because url resolution failed, we'll look
- for a better url without a trailing slash.
+ if not request.user.is_authenticated():
+ messages.warning(request, _('You must be logged in to continue.'))
+ return login_required(view_func)(request, *view_args,
+ **view_kwargs)
- Cribbed from kitsune:
- https://github.com/mozilla/kitsune/blob/master/apps/sumo/middleware.py
+ if request.user.userprofile.is_vouched:
+ return None
- """
+ allow_unvouched = getattr(view_func, '_allow_unvouched', None)
+ if allow_unvouched:
+ return None
- def process_response(self, request, response):
- if (response.status_code == 404
- and request.path_info.endswith('/')
- and not is_valid_path(request.path_info)
- and is_valid_path(request.path_info[:-1])):
- # Use request.path because we munged app/locale in path_info.
- newurl = request.path[:-1]
- if request.GET:
- with safe_query_string(request):
- newurl += '?' + request.META['QUERY_STRING']
- return HttpResponsePermanentRedirect(newurl)
- return response
+ messages.error(request, _('You must be vouched to continue.'))
+ return redirect('home')
class UsernameRedirectionMiddleware(object):
@@ -79,7 +61,9 @@ def process_response(self, request, response):
and not request.path_info.startswith('/u/')
and not is_valid_path(request.path_info)
and User.objects.filter(
- username__iexact=request.path_info[1:].strip('/')).exists()):
+ username__iexact=request.path_info[1:].strip('/')).exists()
+ and request.user.is_authenticated()
+ and request.user.userprofile.is_vouched):
newurl = '/u' + request.path_info
if request.GET:
View
0  apps/common/tests/__init__.py
No changes.
View
0  apps/common/tests.py → apps/common/tests/init.py
File renamed without changes
View
55 apps/common/tests/test_stronghold.py
@@ -0,0 +1,55 @@
+from django.core.urlresolvers import reverse
+from django.test.utils import override_settings
+from nose.tools import eq_
+
+from apps.common.tests.init import ESTestCase
+from apps.common.decorators import allow_public, allow_unvouched
+
+
+class TestDecorators(ESTestCase):
+
+ def test_allow_public_decorator(self):
+
+ def foo():
+ pass
+
+ eq_(getattr(foo, '_allow_public', None), None)
+ allow_public(foo)
+ self.assertTrue(foo._allow_public)
+
+ def test_allow_unvouched_decorator(self):
+
+ def foo():
+ pass
+ eq_(getattr(foo, '_allow_unvouched', None), None)
+ allow_unvouched(foo)
+ self.assertTrue(foo._allow_unvouched)
+
+
+class TestStrongholdMiddleware(ESTestCase):
+ """Stronghold Testcases."""
+ urls = 'apps.common.tests.test_urls'
+
+ def test_stronghold(self):
+ """Test stronhold middleware functionality."""
+ self.excepted_results = {
+ 'vouched': {'vouched': True, 'unvouched': False, 'public': False},
+ 'unvouched': {'vouched': True, 'unvouched': True, 'public': False},
+ 'public': {'vouched': True, 'unvouched': True, 'public': True},
+ 'excepted': {'vouched': True, 'unvouched': True, 'public': True}}
+
+ self.clients = {
+ 'vouched': self.mozillian_client,
+ 'unvouched': self.pending_client,
+ 'public': self.client}
+
+ for url in self.excepted_results:
+ for user, client in self.clients.items():
+ r_url = reverse(url, prefix='/en-US/')
+ response = client.get(r_url, follow=True)
+ if self.excepted_results[url][user]:
+ eq_(response.content, 'Hi!')
+
+ else:
+ eq_(len(response.redirect_chain), 2)
+ eq_(len(response.context['messages']), 1)
View
29 apps/common/tests/test_urls.py
@@ -0,0 +1,29 @@
+from django.conf import settings
+from django.conf.urls.defaults import patterns, url
+from django.http import HttpResponse
+from apps.common.decorators import allow_public, allow_unvouched
+
+from urls import urlpatterns
+
+def vouched(request):
+ return HttpResponse('Hi!')
+
+
+@allow_unvouched
+def unvouched(request):
+ return HttpResponse('Hi!')
+
+@allow_public
+def public(request):
+ return HttpResponse('Hi!')
+
+
+urlpatterns += patterns(
+ '',
+ url(r'^vouched/$', vouched, name='vouched'),
+ url(r'^unvouched/$', unvouched, name='unvouched'),
+ url(r'^public/$', public, name='public'),
+ url(r'^excepted/$', vouched, name='excepted'))
+
+
+settings.STRONGHOLD_EXCEPTIONS += ['^/en-US/excepted/$']
View
4 apps/groups/tests/test_groups.py
@@ -8,7 +8,7 @@
from nose.tools import eq_
from pyquery import PyQuery as pq
-import common.tests
+import apps.common.tests.init
from ..cron import assign_autocomplete_to_groups
from ..helpers import stringify_groups
@@ -16,7 +16,7 @@
from ..utils import merge_groups
-class GroupTest(common.tests.ESTestCase):
+class GroupTest(apps.common.tests.init.ESTestCase):
"""Test the group/grouping system."""
def setUp(self):
View
4 apps/groups/tests/test_languages.py
@@ -5,13 +5,13 @@
from funfactory.urlresolvers import reverse
from nose.tools import eq_
-import common.tests
+import apps.common.tests.init
from ..cron import assign_autocomplete_to_groups
from ..models import AUTO_COMPLETE_COUNT, Language
-class LanguagesTest(common.tests.ESTestCase):
+class LanguagesTest(apps.common.tests.init.ESTestCase):
def test_autocomplete_api(self):
self.client.login(email=self.mozillian.email)
View
2  apps/groups/tests/test_middleware.py
@@ -1,7 +1,7 @@
from django.core.urlresolvers import reverse
from nose.tools import eq_
-from common.tests import ESTestCase
+from apps.common.tests.init import ESTestCase
from ..models import Group, GroupAlias
View
4 apps/groups/tests/test_skills.py
@@ -5,13 +5,13 @@
from funfactory.urlresolvers import reverse
from nose.tools import eq_
-import common.tests
+import apps.common.tests.init
from ..cron import assign_autocomplete_to_groups
from ..models import AUTO_COMPLETE_COUNT, Skill
-class SkillsTest(common.tests.ESTestCase):
+class SkillsTest(apps.common.tests.init.ESTestCase):
def test_autocomplete_api(self):
self.client.login(email=self.mozillian.email)
View
16 apps/groups/urls.py
@@ -1,20 +1,16 @@
from django.conf.urls.defaults import patterns, url
-from django.contrib import admin
-admin.autodiscover()
-
import views
from models import Group, Skill, Language
urlpatterns = patterns('',
- url('^groups$', views.index, name='group_index'),
- url('^group/(?P<url>[^/]+)$', views.show, name='group'),
- url('^group/(?P<url>[^/]+)/toggle$', views.toggle,
- name='group_toggle'),
- url('^groups/search$', views.search,
+ url('^groups/$', views.index, name='group_index'),
+ url('^group/(?P<url>[-\w]+)/$', views.show, name='group'),
+ url('^group/(?P<url>[-\w]+)/toggle/$', views.toggle, name='group_toggle'),
+ url('^groups/search/$', views.search,
dict(searched_object=Group), name='group_search'),
- url('^skills/search$', views.search,
+ url('^skills/search/$', views.search,
dict(searched_object=Skill), name='skill_search'),
- url('^languages/search$', views.search,
+ url('^languages/search/$', views.search,
dict(searched_object=Language), name='language_search'),
)
View
30 apps/groups/views.py
@@ -1,9 +1,8 @@
import json
-from django.contrib.auth.decorators import login_required
from django.core.paginator import EmptyPage, Paginator, PageNotAnInteger
from django.db.models import Count
-from django.http import Http404, HttpResponse
+from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.cache import cache_control, never_cache
from django.views.decorators.http import require_POST
@@ -11,16 +10,14 @@
import commonware.log
from funfactory.urlresolvers import reverse
+from apps.common.decorators import allow_unvouched
from apps.groups.models import Group, Skill
from apps.phonebook import forms
-from apps.phonebook.views import vouch_required
-from apps.users.models import UserProfile
from apps.users.tasks import update_basket_task
log = commonware.log.getLogger('m.groups')
-@login_required
def index(request):
"""Lists all public groups (in use) on Mozillians."""
paginator = Paginator(Group.objects.all(), forms.PAGINATION_LIMIT)
@@ -37,25 +34,23 @@ def index(request):
return render(request, 'groups/index.html', data)
-@login_required
+@allow_unvouched
@cache_control(must_revalidate=True, max_age=3600)
def search(request, searched_object=Group):
- """Simple wildcard search for a group using a GET parameter."""
- data = dict(search=True)
- data['groups'] = list(searched_object.search(request.GET
- .get('term')).values_list('name', flat=True))
+ """Simple wildcard search for a group using a GET parameter.
- if request.is_ajax():
- return HttpResponse(json.dumps(data['groups']),
+ Used for group/skill/language auto-completion.
+
+ """
+ term = request.GET.get('term', None)
+ if request.is_ajax() and term:
+ groups = searched_object.search(term).values_list('name', flat=True)
+ return HttpResponse(json.dumps(list(groups)),
mimetype='application/json')
- if searched_object == Group:
- return render(request, 'groups/index.html', data)
- # Raise a 404 if this is a Skill page that isn't ajax
- raise Http404
+ return redirect('home')
-@vouch_required
@never_cache
def show(request, url):
"""List all vouched users with this group."""
@@ -105,7 +100,6 @@ def show(request, url):
@require_POST
-@vouch_required
def toggle(request, url):
"""Toggle the current user's membership of a group."""
group = get_object_or_404(Group, url=url)
View
4 apps/phonebook/forms.py
@@ -20,8 +20,8 @@
class SearchForm(happyforms.Form):
q = forms.CharField(widget=forms.HiddenInput, required=False)
limit = forms.CharField(widget=forms.HiddenInput, required=False)
- include_non_vouched = forms.BooleanField(label=_lazy(u'Include non-vouched'),
- required=False)
+ include_non_vouched = forms.BooleanField(
+ label=_lazy(u'Include non-vouched'), required=False)
def clean_limit(self):
"""Validate that this limit is numeric and greater than 1."""
View
2  apps/phonebook/tests/test_edit_profile.py
@@ -6,7 +6,7 @@
from funfactory.urlresolvers import reverse
from nose.tools import eq_
-from apps.common.tests import ESTestCase, user
+from apps.common.tests.init import ESTestCase, user
ASSERTION = 'asldkfjasldfka'
View
14 apps/phonebook/tests/test_invites.py
@@ -5,13 +5,13 @@
from nose.tools import eq_
from pyquery import PyQuery as pq
-import apps.common.tests
+import apps.common.tests.init
from apps.common.browserid_mock import mock_browserid
from ..models import Invite
-class InviteFlowTest(apps.common.tests.ESTestCase):
+class InviteFlowTest(apps.common.tests.init.ESTestCase):
fake_email = 'mr.fusion@gmail.com'
fake_email2 = 'mrs.fusion@gmail.com'
@@ -116,7 +116,7 @@ def test_send_invite_flow(self):
False)
-class InviteEdgeTest(apps.common.tests.ESTestCase):
+class InviteEdgeTest(apps.common.tests.init.ESTestCase):
def test_no_reinvite(self):
"""Don't reinvite a vouched user."""
@@ -135,10 +135,10 @@ def test_unvouched_cant_invite(self):
Their stupid friends...
"""
url = reverse('invite')
- d = dict(recipient='mr.fusion@gmail.com')
- self.client.login(email=self.pending.email)
- r = self.client.post(url, d, follow=True)
- eq_(r.status_code, 403)
+ data = {'recipient': 'mr.fusion@gmail.com'}
+ response = self.pending_client.post(url, data, follow=True)
+ eq_(response.status_code, 200)
+ assert('You must be vouched to continue.' in response.content)
def create_vouched_user(email):
View
2  apps/phonebook/tests/test_modelform.py
@@ -2,7 +2,7 @@
from nose.tools import eq_
from pyquery import PyQuery as pq
-from apps.common.tests import ESTestCase, user
+from apps.common.tests.init import ESTestCase, user
class ModelForms(ESTestCase):
View
2  apps/phonebook/tests/test_search.py
@@ -2,7 +2,7 @@
from nose.tools import eq_
from pyquery import PyQuery as pq
-from apps.common.tests import ESTestCase
+from apps.common.tests.init import ESTestCase
from apps.users.models import UserProfile
from apps.groups.models import Group
View
18 apps/phonebook/tests/test_trailing_slash_middleware.py
@@ -1,18 +0,0 @@
-from funfactory.urlresolvers import reverse
-from nose.tools import eq_
-
-from apps.common.tests import ESTestCase
-
-
-class TrailingSlashMiddlewareTestCase(ESTestCase):
-
- def test_strip_trailing_slash(self):
- url = reverse('about')
- r = self.client.get(url + '/')
- self.assertRedirects(r, url, status_code=301)
-
- def test_no_trailing_slash(self):
- """Don't redirect 404s without a trailing slash."""
- # Need to use loged in client because other wise we try to log you in.
- r = self.mozillian_client.get('/en-US/ohnoez')
- eq_(404, r.status_code, '/ohnoez should be a 404.')
View
10 apps/phonebook/tests/test_views.py
@@ -8,7 +8,7 @@
from nose.tools import eq_
from pyquery import PyQuery as pq
-from apps.common.tests import ESTestCase, user
+from apps.common.tests.init import ESTestCase, user
class TestDeleteUser(ESTestCase):
@@ -133,8 +133,9 @@ def test_pending_view_own_profile(self):
def test_pending_view_other_profile(self):
"""Test view other profile by unvouched user."""
url = reverse('profile', args=[self.mozillian.username])
- response = self.pending_client.get(url)
- self.assertEqual(response.status_code, 403)
+ response = self.pending_client.get(url, follow=True)
+ self.assertEqual(response.status_code, 200)
+ assert('You must be vouched to continue' in response.content)
def test_mozillians_view_own_profile(self):
"""Test view own profile by vouched user."""
@@ -377,8 +378,7 @@ def test_localized_search_plugin(self):
# Prefixer and its locale are sticky; clear it before the next request
set_url_prefix(None)
- response = self.client.get(reverse('search_plugin',
- prefix='/fr/'))
+ response = self.client.get(reverse('search_plugin', prefix='/fr/'))
assert '/fr/search' in response.content
View
34 apps/phonebook/urls.py
@@ -1,33 +1,35 @@
from django.conf.urls.defaults import patterns, url
from django.views.generic.simple import direct_to_template
+from apps.common.decorators import allow_public
+
import views
urlpatterns = patterns('',
url('^$', views.home, name='home'),
- url('^user/edit$', views.edit_profile,
+ url('^user/edit/$', views.edit_profile,
name='profile.edit'),
- url('^confirm-delete$', views.confirm_delete,
+ url('^confirm-delete/$', views.confirm_delete,
name='profile.delete_confirm'),
- url('^delete$', views.delete, name='profile.delete'),
+ url('^delete/$', views.delete, name='profile.delete'),
url('^opensearch.xml$', views.search_plugin, name='search_plugin'),
- url('^search$', views.search, name='search'),
- url('^vouch$', views.vouch, name='vouch'),
- url('^invite$', views.invite, name='invite'),
- url('^country/(?P<country>[\w ]+)$', views.list_country,
+ url('^search/$', views.search, name='search'),
+ url('^vouch/$', views.vouch, name='vouch'),
+ url('^invite/$', views.invite, name='invite'),
+ url('^country/(?P<country>[A-Za-z]+)/$', views.list_mozillians_in_location,
name='list_country'),
- url('^country/(?P<country>[A-Za-z]+)/city/(?P<city>.+)$',
- views.list_country, name='list_city'),
+ url('^country/(?P<country>[A-Za-z]+)/city/(?P<city>.+)/$',
+ views.list_mozillians_in_location, name='list_city'),
url(('^country/(?P<country>[A-Za-z]+)/'
- 'region/(?P<region>[\w\' ]+)/city/(?P<city>[\w\' ]+)$'),
- views.list_country, name='list_region_city'),
- url('^country/(?P<country>[A-Za-z]+)/region/(?P<region>.+)$',
- views.list_country, name='list_region'),
+ 'region/(?P<region>.+)/city/(?P<city>.+)/$'),
+ views.list_mozillians_in_location, name='list_region_city'),
+ url('^country/(?P<country>[A-Za-z]+)/region/(?P<region>.+)/$',
+ views.list_mozillians_in_location, name='list_region'),
# Static pages need csrf for browserID post to work
- url('^about$', direct_to_template, {'template': 'phonebook/about.html'},
- name='about'),
- url(r'^u/(?P<username>[\w.@+-]+)$',
+ url('^about/$', allow_public(direct_to_template),
+ {'template': 'phonebook/about.html'}, name='about'),
+ url(r'^u/(?P<username>[\w.@+-]+)/$',
views.profile, name='profile'),
)
View
80 apps/phonebook/views.py
@@ -1,11 +1,8 @@
-from functools import wraps
-
from django.contrib import messages
from django.contrib.auth import logout
-from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
-from django.http import Http404, HttpResponseForbidden, HttpResponseRedirect
+from django.http import Http404, HttpResponseForbidden
from django.shortcuts import redirect, render, get_object_or_404
from django.views.decorators.cache import cache_page, never_cache
from django.views.decorators.http import require_POST
@@ -14,6 +11,7 @@
from funfactory.urlresolvers import reverse
from tower import ugettext as _
+from apps.common.decorators import allow_public, allow_unvouched
from apps.groups.helpers import stringify_groups
from apps.groups.models import Group
from apps.users.models import COUNTRIES, UserProfile
@@ -22,28 +20,12 @@
import forms
from models import Invite
-
log = commonware.log.getLogger('m.phonebook')
-
BAD_VOUCHER = 'Unknown Voucher'
-def vouch_required(f):
- """If a user is not vouched they get a 403."""
-
- @login_required
- @wraps(f)
- def wrapped(request, *args, **kwargs):
- if request.user.get_profile().is_vouched:
- return f(request, *args, **kwargs)
- else:
- log.warning('vouch_required forbidding access')
- return HttpResponseForbidden(_('You must be vouched to do this.'))
-
- return wrapped
-
-
@never_cache
+@allow_public
def home(request):
if request.user.is_authenticated():
profile = request.user.get_profile()
@@ -55,30 +37,38 @@ def home(request):
return render(request, 'phonebook/home.html')
+@allow_unvouched
@never_cache
-@login_required
def profile(request, username):
"""View a profile by username."""
- user = get_object_or_404(User, username=username)
- vouch_form = None
- profile = user.get_profile()
+ if username == request.user.username:
+ # wants to see own
+ data = {'shown_user': request.user,
+ 'profile': request.user.userprofile,
+ 'vouch_form': None}
+ return render(request, 'phonebook/profile.html', data)
- if not profile.is_complete:
- raise Http404()
+ if not request.user.userprofile.is_vouched:
+ messages.error(request, _('You must be vouched to continue.'))
+ return redirect('home')
- if not request.user.userprofile.is_vouched and request.user != user:
- log.warning('vouch_required forbidding access')
- return HttpResponseForbidden(_('You must be vouched to do this.'))
+ user = get_object_or_404(User, username=username)
+ if not user.userprofile.is_complete:
+ raise Http404()
- if not profile.is_vouched and request.user.get_profile().is_vouched:
- vouch_form = forms.VouchForm(initial=dict(vouchee=profile.pk))
+ vouch_form = None
+ if (not user.userprofile.is_vouched
+ and request.user.userprofile.is_vouched):
+ vouch_form = forms.VouchForm(initial=dict(vouchee=user.userprofile.pk))
- data = dict(shown_user=user, profile=profile, vouch_form=vouch_form)
+ data = {'shown_user': user,
+ 'profile': user.userprofile,
+ 'vouch_form': vouch_form}
return render(request, 'phonebook/profile.html', data)
+@allow_unvouched
@never_cache
-@login_required
def edit_profile(request):
"""Edit user profile view."""
# Don't user request.user
@@ -121,8 +111,8 @@ def edit_profile(request):
return render(request, 'phonebook/edit_profile.html', d, status=status)
+@allow_unvouched
@never_cache
-@login_required
def confirm_delete(request):
"""Display a confirmation page asking the user if they want to
leave.
@@ -131,8 +121,8 @@ def confirm_delete(request):
return render(request, 'phonebook/confirm_delete.html')
+@allow_unvouched
@never_cache
-@login_required
@require_POST
def delete(request):
user_profile = request.user.get_profile()
@@ -143,7 +133,6 @@ def delete(request):
return redirect(reverse('home'))
-@vouch_required
def search(request):
num_pages = 0
limit = None
@@ -195,6 +184,7 @@ def search(request):
return render(request, 'phonebook/search.html', d)
+@allow_public
@cache_page(60 * 60 * 168) # 1 week.
def search_plugin(request):
"""Render an OpenSearch Plugin."""
@@ -202,7 +192,6 @@ def search_plugin(request):
content_type='application/opensearchdescription+xml')
-@vouch_required
def invite(request):
profile = request.user.userprofile
invite_form = forms.InviteForm(request.POST or None,
@@ -210,12 +199,13 @@ def invite(request):
if request.method == 'POST' and invite_form.is_valid():
invite = invite_form.save()
invite.send(sender=profile)
- return render(request, 'phonebook/invited.html', {'recipient': invite.recipient })
+ return render(request, 'phonebook/invited.html',
+ {'recipient': invite.recipient})
- return render(request, 'phonebook/invite.html', {'invite_form':invite_form})
+ return render(request, 'phonebook/invite.html',
+ {'invite_form': invite_form})
-@vouch_required
@require_POST
def vouch(request):
"""Vouch a user."""
@@ -234,12 +224,10 @@ def vouch(request):
return HttpResponseForbidden
-@vouch_required
-def list_country(request, country, region=None, city=None):
+def list_mozillians_in_location(request, country, region=None, city=None):
country = country.lower()
- country_name = COUNTRIES.get(country, None)
- queryset = (UserProfile.objects.exclude(full_name='')
- .filter(is_vouched=True).filter(country=country))
+ country_name = COUNTRIES.get(country, country)
+ queryset = UserProfile.objects.filter(country=country)
if city:
queryset = queryset.filter(city__iexact=city)
if region:
View
4 apps/users/tests/test_api.py
@@ -8,8 +8,8 @@
from product_details import product_details
from apps.api.models import APIApp
-from common.tests import ESTestCase, user
-from groups.models import Group, Language, Skill
+from apps.common.tests.init import ESTestCase, user
+from apps.groups.models import Group, Language, Skill
from ..cron import index_all_profiles
View
2  apps/users/tests/test_basket.py
@@ -1,7 +1,7 @@
from django.conf import settings
from funfactory.urlresolvers import reverse
-from common.tests import ESTestCase
+from apps.common.tests.init import ESTestCase
from mock import patch
View
2  apps/users/tests/test_incomplete_profiles.py
@@ -3,7 +3,7 @@
from mock import patch
from nose.tools import eq_
-from apps.common.tests import ESTestCase, user
+from apps.common.tests.init import ESTestCase, user
from apps.groups.models import Group
View
6 apps/users/tests/tests.py
@@ -7,9 +7,9 @@
from product_details import product_details
from pyquery import PyQuery as pq
-from common import browserid_mock
-from common.tests import ESTestCase, user
-from groups.models import Group
+from apps.common import browserid_mock
+from apps.common.tests.init import ESTestCase, user
+from apps.groups.models import Group
from ..helpers import calculate_username, validate_username
from ..models import UserProfile, UsernameBlacklist
View
4 apps/users/urls.py
@@ -10,7 +10,7 @@
urlpatterns = patterns('',
- url(r'^logout$', views.logout, name='logout'),
+ url(r'^logout/$', views.logout, name='logout'),
url('^browserid/verify/', views.BrowserID.as_view(),
name='browserid_verify'),
- url(r'^register$', views.register, name='register'))
+ url(r'^register/$', views.register, name='register'))
View
11 apps/users/views.py
@@ -8,6 +8,7 @@
from funfactory.urlresolvers import reverse
from tower import ugettext as _
+from apps.common.decorators import allow_public, allow_unvouched
from apps.phonebook.forms import RegisterForm, UserForm
from apps.phonebook.models import Invite
@@ -17,6 +18,7 @@
get_invite = lambda c: Invite.objects.get(code=c, redeemed=None)
+@allow_unvouched
def logout(request, **kwargs):
"""Logout view that wraps Django's logout but always redirects.
@@ -49,6 +51,7 @@ def get_failure_url(self):
return self.failure_url
+@allow_public
def register(request):
"""Registers Users.
@@ -115,11 +118,3 @@ def _update_invites(request):
invite.redeemed = datetime.datetime.now()
invite.redeemer = redeemer
invite.save()
-
-
-def _set_already_exists_error(form):
- msg = _('Someone has already registered an account with %(email)s.')
- data = dict(email=form.cleaned_data['email'])
- del form.cleaned_data['email']
- error = _(msg % data)
- form._errors['email'] = form.error_class([error])
View
6 media/js/groups.js
@@ -12,7 +12,7 @@
singleField: true,
singleFieldDelimiter: ',',
removeConfirmation: true,
- tagSource: app.localeUrl('groups/search'),
+ tagSource: app.localeUrl('groups/search/'),
triggerKeys: ['enter', 'comma', 'tab']
});
$('#id_skills').tagit({
@@ -25,7 +25,7 @@
singleField: true,
singleFieldDelimiter: ',',
removeConfirmation: true,
- tagSource: app.localeUrl('skills/search'),
+ tagSource: app.localeUrl('skills/search/'),
triggerKeys: ['enter', 'comma', 'tab']
});
$('#id_languages').tagit({
@@ -38,7 +38,7 @@
singleField: true,
singleFieldDelimiter: ',',
removeConfirmation: true,
- tagSource: app.localeUrl('languages/search'),
+ tagSource: app.localeUrl('languages/search/'),
triggerKeys: ['enter', 'comma', 'tab']
});
}
View
19 settings/default.py
@@ -136,15 +136,15 @@
MIDDLEWARE_CLASSES = list(base.MIDDLEWARE_CLASSES) + [
'commonware.response.middleware.StrictTransportMiddleware',
'csp.middleware.CSPMiddleware',
- 'common.middleware.PermissionDeniedMiddleware',
- 'common.middleware.RemoveSlashMiddleware',
- 'common.middleware.UsernameRedirectionMiddleware',
- 'common.middleware.OldGroupRedirectionMiddleware',
- 'common.middleware.GroupAliasRedirectionMiddleware',
+
'django_statsd.middleware.GraphiteMiddleware',
'django_statsd.middleware.GraphiteRequestTimingMiddleware',
'django_statsd.middleware.TastyPieRequestTimingMiddleware',
-]
+
+ 'common.middleware.StrongholdMiddleware',
+ 'common.middleware.UsernameRedirectionMiddleware',
+ 'common.middleware.OldGroupRedirectionMiddleware',
+ 'common.middleware.GroupAliasRedirectionMiddleware']
# StrictTransport
STS_SUBDOMAINS = True
@@ -250,7 +250,7 @@
'https://login.persona.org',)
CSP_FONT_SRC = ("'self'", 'https://www.mozilla.org')
CSP_REPORT_ONLY = True
-CSP_REPORT_URI = '/csp/report'
+CSP_REPORT_URI = '/csp/report/'
ES_DISABLED = True
ES_HOSTS = ['127.0.0.1:9200']
@@ -285,3 +285,8 @@ def _allowed_hosts():
host = host.rsplit(':', 1)[0] # Remove port
return [host]
ALLOWED_HOSTS = lazy(_allowed_hosts, list)()
+
+STRONGHOLD_EXCEPTIONS = ['^%s' % MEDIA_URL,
+ '^/admin/',
+ '^/browserid/verify/',
+ '^/api']
View
13 urls.py
@@ -6,6 +6,8 @@
from django.views.generic.base import TemplateView
from django.views.i18n import javascript_catalog
+from apps.common.decorators import allow_public
+
admin.autodiscover()
@@ -35,7 +37,8 @@ def error_page(request, template, status=None):
# Admin URLs.
url(r'^admin/', include(admin.site.urls)),
- url(r'^jsi18n/$', cache_page(60 * 60 * 24 * 365)(javascript_catalog),
+ url(r'^jsi18n/$',
+ allow_public(cache_page(60 * 60 * 24 * 365)(javascript_catalog)),
{'domain': 'javascript', 'packages': ['mozillians']}, name='jsi18n'),
)
@@ -45,12 +48,12 @@ def error_page(request, template, status=None):
# Remove leading and trailing slashes so the regex matches.
media_url = settings.MEDIA_URL.lstrip('/').rstrip('/')
urlpatterns += patterns('',
- (r'^%s/(?P<path>.*)$' % media_url, 'django.views.static.serve',
+ (r'^%s/(?P<path>.*)' % media_url, 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT}),
# Add the 404, 500, and csrf pages for testing
- (r'^404$', handler404),
- (r'^500$', handler500),
- (r'^csrf$', handler_csrf),
+ (r'^404/$', handler404),
+ (r'^500/$', handler500),
+ (r'^csrf/$', handler_csrf),
url(r'^test/qunit/$', TemplateView.as_view(template_name='qunit.html'),
name="qunit_test"),
Please sign in to comment.
Something went wrong with that request. Please try again.