Florian Fuchs has proposed merging lp:~flo-fuchs/postorius/postorius_persona into lp:postorius.
Requested reviews: Terri (terriko) For more details, see: https://code.launchpad.net/~flo-fuchs/postorius/postorius_persona/+merge/201514 Hi, this change is mostly a switch from the django_social_auth library to django_browserid. It also includes some additions to the Persona audience verification process: Usually, django_browserid uses the SITE_URL setting to check if the domain of the site matches the one provided with the Persona assertion (to prevent audience spoofing). In most cases, SITE_URL might probably only contain one domain. There are however possible scenarios running Postorius/Mailman under multiple domains (say, an Apache catch-all configuration). In those cases, adding new domains to the SITE_URL setting is a redundant task, because those domains also have to be added to the Mailman DB via Postorius (or REST). This change includes a ``get_audience`` function which includes domains from the Mailman DB when verifying the audience in the Persona assertion. The function is disabled by default, but can be activated in settings.py. The code is tested (although I haven't run coverage...). But it sure won't hurt having someone else having a look at it. Thanks! Florian -- https://code.launchpad.net/~flo-fuchs/postorius/postorius_persona/+merge/201514 Your team Mailman Coders is subscribed to branch lp:postorius.
=== modified file 'src/postorius/auth/__init__.py' --- src/postorius/auth/__init__.py 2011-07-30 17:29:09 +0000 +++ src/postorius/auth/__init__.py 2014-01-13 22:13:50 +0000 @@ -0,0 +1,1 @@ +from get_site_url import get_site_url === added file 'src/postorius/auth/get_site_url.py' --- src/postorius/auth/get_site_url.py 1970-01-01 00:00:00 +0000 +++ src/postorius/auth/get_site_url.py 2014-01-13 22:13:50 +0000 @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2012 by the Free Software Foundation, Inc. +# +# This file is part of Postorius. +# +# Postorius is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Postorius 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 +# Postorius. If not, see <http://www.gnu.org/licenses/>. +import logging + +from postorius.utils import get_client +from mailmanclient import MailmanConnectionError + + +logger = logging.getLogger(__name__) + + +def get_site_url(request): + # return a list of all domains' base_urls. + try: + return [domain.base_url for domain in get_client().domains] + except MailmanConnectionError: + logger.warning('Error connecting to Mailman REST API.') + return [] === added file 'src/postorius/browserid_urls.py' --- src/postorius/browserid_urls.py 1970-01-01 00:00:00 +0000 +++ src/postorius/browserid_urls.py 2014-01-13 22:13:50 +0000 @@ -0,0 +1,16 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +from django.conf import settings +from django.conf.urls.defaults import patterns, url +from django.contrib.auth.views import logout + +from postorius.views.browserid import Verify + + +urlpatterns = patterns('', + url(r'^browserid_login/', Verify.as_view(), name='browserid_login'), + url(r'^accounts/logout/', logout, + {'next_page': getattr(settings, 'LOGOUT_REDIRECT_URL', '/')}, + name='browserid_logout') +) === modified file 'src/postorius/doc/setup.rst' --- src/postorius/doc/setup.rst 2013-03-21 00:17:22 +0000 +++ src/postorius/doc/setup.rst 2014-01-13 22:13:50 +0000 @@ -176,3 +176,53 @@ $ python manage.py collectstatic After reloading the webserver Postorius should be running! + + +Mozilla Persona +=============== + + +Postorius uses `Mozilla Persona`_ to authenticate login requests. + +.. _Mozilla Persona: + http://www.mozilla.org/en-US/persona/ + +One very important part in the Persona login process is making sure that the +Persona login token is indeed intended to be used under your domain. In order +to do that, Postorius needs to know the host name(s) it is running under: + +:: + + SITE_URL = 'https://example.com' + + # or: + + SITE_URL = [ + 'https://example.com', + 'https://example.org', + ] + + +``SITE_URL`` and multiple hosts +------------------------------- + + +If you're running the application under multiple domains and regularly add new +domains via Postorius to your Mailman database, adding those domains to +``settings.py`` every time can become a little tedious. If you set +``BROWSERID_SITE_URL_ALGO`` in your ``settings.py`` file to the following +value, it will consider all domains in your Mailman database in addition to +the ones set in ``SITE_URL``. + +:: + + BROWSERID_SITE_URL_ALGO = 'postorius.auth.get_site_url' + +.. note:: + + The ``BROWSERID_SITE_URL_ALGO`` setting is a string representing an + importable python path to a callable. If you like to add your own + customized logic to determine the domains you consider valid for use with + Persona, you can simply let this path point to any function that returns a + list of domains. + === modified file 'src/postorius/forms.py' --- src/postorius/forms.py 2013-10-22 18:52:15 +0000 +++ src/postorius/forms.py 2014-01-13 22:13:50 +0000 @@ -33,7 +33,7 @@ error_messages={'required': _('Please a domain name'), 'invalid': _('Please enter a valid domain name.')}, required=True) - web_host = forms.CharField( + web_host = forms.URLField( label=_('Web Host'), error_messages={'required': _('Please a domain name'), 'invalid': _('Please enter a valid domain name.')}, @@ -53,14 +53,6 @@ raise forms.ValidationError(_("Enter a valid Mail Host")) return mail_host - def clean_web_host(self): - web_host = self.cleaned_data['web_host'] - try: - validate_email('mail@' + web_host) - except: - raise forms.ValidationError(_("Please enter a valid Web Host")) - return web_host - class Meta: """ === modified file 'src/postorius/models.py' --- src/postorius/models.py 2013-10-22 20:01:21 +0000 +++ src/postorius/models.py 2014-01-13 22:13:50 +0000 @@ -85,7 +85,7 @@ def create(self, **kwargs): try: - method = getattr(utils.get_client(), 'create_' + self.resource_name) + method = getattr(get_client(), 'create_' + self.resource_name) print kwargs return method(**kwargs) except AttributeError, e: === modified file 'src/postorius/templates/postorius/base.html' --- src/postorius/templates/postorius/base.html 2013-07-14 20:42:05 +0000 +++ src/postorius/templates/postorius/base.html 2014-01-13 22:13:50 +0000 @@ -1,4 +1,4 @@ -{% load url from future %}{% load i18n %}{% load staticfiles %}<!doctype html> +{% load url from future %}{% load i18n %}{% load staticfiles %}{% load browserid %}<!doctype html> <!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]--><!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]--><!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]--><!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]--><!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"> <!--<![endif]--><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> @@ -13,9 +13,11 @@ <link rel="stylesheet" href="{% static 'postorius/css/bootstrap.css' %}"> <link rel="stylesheet" href="{% static 'postorius/css/style.css' %}"> + {% browserid_css %} </head> <body class="{% block body_class %}{% endblock %}"> + {% browserid_info %} <header> <div class="mm_header"> @@ -34,7 +36,7 @@ </ul> <div class="mm_loginName"> {% if user.is_authenticated %} - Logged in as: <a href="{% url 'user_profile' %}">{{ user.username }}</a> <a href="{% url 'user_logout' %}" title="{% trans 'Logout' %}" class="mm_logout"><i class="icon-off"></i></a> + Logged in as: <a href="{% url 'user_profile' %}">{{ user.username }}</a> <a href="{% url 'user_logout' %}" title="{% trans 'Logout' %}" class="mm_logout browserid-logout"><i class="icon-off"></i></a> {% else %} <a href="{% url 'user_login' %}">Login</a> {% endif %} @@ -69,4 +71,7 @@ <script src="{% static 'postorius/js/script.js' %}"></script> {% block additionaljs %}{% endblock %} + {% if not 'django.contrib.auth.backends.ModelBackend' in request.session.values %} + {% browserid_js %} + {% endif %} </body> === modified file 'src/postorius/templates/postorius/login.html' --- src/postorius/templates/postorius/login.html 2013-05-31 02:21:03 +0000 +++ src/postorius/templates/postorius/login.html 2014-01-13 22:13:50 +0000 @@ -2,8 +2,9 @@ {% load url from future %} {% load i18n %} {% load staticfiles %} +{% load browserid %} + {% block main %} - <div id="container"> <p><strong>Login with username and password or with Mozilla Persona</strong></p> @@ -17,41 +18,13 @@ </div> <div class="mm_login_b"> - <form method="post" action="{% url 'socialauth_complete' "browserid" %}"> {% csrf_token %} - <input type="hidden" name="assertion" value="" /> - <a rel="nofollow" id="browserid" href="#"><img src="{% static 'postorius/img/sign_in_blue.png' %}" alt="Login using BrowserID" /></a> - </form> - <p>Mozilla Persona is an easy way to sign into multiple websites, while still controlling your personal data. For more information <a href="http://www.mozilla.org/en-US/persona">see the mozilla website</a></p> + {% if user.is_authenticated %} + {% browserid_logout text='Logout' %} + {% else %} + {% browserid_login text='Login with<br />Mozilla Persona' color='blue' %} + {% endif %} </div> <div style="clear:both"> </div> </div> {% endblock %} - -{% block additionaljs %} -<!-- Include BrowserID JavaScript --> -<script src="https://login.persona.org/include.js" type="text/javascript"></script> -<!-- Setup click handler that receives BrowserID assertion code and sends - POST data --> -<script type="text/javascript"> - $(function () { - $('#browserid').click(function (e) { - e.preventDefault(); - var self = $(this); - - navigator.id.get(function (assertion) { - if (assertion) { - self.parent('form') - .find('input[type=hidden]') - .attr('value', assertion) - .end() - .submit(); - } else { - alert('Some error occurred'); - } - }); - }); - }); -</script> -<!-- end browserid stuff --> -{% endblock additionaljs %} === modified file 'src/postorius/tests/__init__.py' --- src/postorius/tests/__init__.py 2013-10-22 20:00:15 +0000 +++ src/postorius/tests/__init__.py 2014-01-13 22:13:50 +0000 @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License along with # Postorius. If not, see <http://www.gnu.org/licenses/>. - from postorius.tests import test_utils __test__ = { === modified file 'src/postorius/tests/mailman_api_tests/test_list_summary.py' --- src/postorius/tests/mailman_api_tests/test_list_summary.py 2013-07-07 20:17:18 +0000 +++ src/postorius/tests/mailman_api_tests/test_list_summary.py 2014-01-13 22:13:50 +0000 @@ -68,7 +68,7 @@ self.assertEqual(response.context['list'].fqdn_listname, '[email protected]') self.assertTrue('<h1>' in response.content) - self.assertTrue('<form ' not in response.content) + self.assertTrue('class="list_subscribe"' not in response.content) def test_list_summary_logged_in(self): # Response must contain list obj and the form. @@ -77,4 +77,4 @@ response = self.client.get(reverse('list_summary', args=('[email protected]', ))) self.assertEqual(response.status_code, 200) - self.assertTrue('<form ' in response.content) + self.assertTrue('class="list_subscribe"' in response.content) === added file 'src/postorius/tests/test_browserid.py' --- src/postorius/tests/test_browserid.py 1970-01-01 00:00:00 +0000 +++ src/postorius/tests/test_browserid.py 2014-01-13 22:13:50 +0000 @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012 by the Free Software Foundation, Inc. +# +# This file is part of Postorius. +# +# Postorius is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# Postorius 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 +# Postorius. If not, see <http://www.gnu.org/licenses/>. +import mock +import time +import logging + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.core.urlresolvers import reverse +from django.test.client import Client, RequestFactory +from django.test.utils import override_settings +from django.utils import unittest + +from postorius.auth import get_site_url +from postorius.tests.utils import Testobject, setup_mm, teardown_mm +from postorius.utils import get_audience, get_client + + +logger = logging.getLogger(__name__) + + +NOT_CALLABLE = None + + +class GetSiteUrlsFromAPITest(unittest.TestCase): + """Test the postorius.auth.get_site_url with a running Mailman + instance. + """ + + def setUp(self): + self.test_object = Testobject() + logger.info('setting up mm') + setup_mm(self.test_object) + + def tearDown(self): + logger.info('tearing down mm') + teardown_mm(self.test_object) + + def test_domains(self): + self.assertEqual(get_site_url(settings), []) + domain = get_client().create_domain('example.com', 'http://example.com', + 'example', '[email protected]') + self.assertEqual(get_site_url(settings), ['http://example.com']) + + +class GetAudienceTest(unittest.TestCase): + """Tests the get_audience algo. + """ + + def setUp(self): + self.factory = RequestFactory() + + @override_settings( + DEBUG=False, + SITE_URL='http://example.org', + BROWSERID_SITE_URL_ALGO=lambda settings: ['http://foo.com', + 'http://foo.org']) + def test_server_name_neither_in_site_url_or_custom_result(self): + # HTTP Host not found in SITE_URL or custom algo result. + request = self.factory.get('/', SERVER_NAME='example.com') + with self.assertRaises(ImproperlyConfigured): + get_audience(request) + + @override_settings( + DEBUG=False, + SITE_URL='http://example.com', + BROWSERID_SITE_URL_ALGO=lambda settings: ['http://foo.com/', + 'http://foo.org']) + def test_server_name_in_site_url_only(self): + # Success: HTTP Host in SITE_URL. + request = self.factory.get('/', SERVER_NAME='example.com') + audience = get_audience(request) + self.assertEqual(audience, 'http://example.com') + + @override_settings( + DEBUG=False, + SITE_URL='http://foo.com', + BROWSERID_SITE_URL_ALGO=lambda settings: ['http://example.com', + 'http://foo.org']) + def test_server_name_in_custom_algo_result_only(self): + # Success: HTTP Host in custom algo result. + request = self.factory.get('/', SERVER_NAME='example.com') + audience = get_audience(request) + self.assertEqual(audience, 'http://example.com') + + @override_settings( + DEBUG=False, + SITE_URL='http://foo.com', + BROWSERID_SITE_URL_ALGO=lambda settings: 'http://example.com') + def test_custom_algo_result_is_string(self): + # Success: Custom algo result returns string with HTTP Host. + request = self.factory.get('/', SERVER_NAME='example.com') + audience = get_audience(request) + self.assertEqual(audience, 'http://example.com') + + @override_settings( + DEBUG=False, + SITE_URL='http://foo.com', + BROWSERID_SITE_URL_ALGO=lambda settings: 1) + def test_custom_algo_result_is_int(self): + # Success: Custom algo result returns string with HTTP Host. + request = self.factory.get('/', SERVER_NAME='example.com') + with self.assertRaises(ImproperlyConfigured): + get_audience(request) + + @override_settings( + DEBUG=False, + SITE_URL='http://foo.com', + BROWSERID_SITE_URL_ALGO='foo.bar') + def test_custom_algo_is_not_importable(self): + request = self.factory.get('/', SERVER_NAME='example.com') + with self.assertRaises(ImproperlyConfigured): + get_audience(request) + + @override_settings( + DEBUG=False, + SITE_URL='http://foo.com', + BROWSERID_SITE_URL_ALGO='postorius.tests.test_browserid.NOT_CALLABLE') + def test_custom_algo_is_not_callable(self): + request = self.factory.get('/', SERVER_NAME='example.com') + with self.assertRaises(ImproperlyConfigured): + get_audience(request) + + +# Intercept HTTP call to BrowserID verify service. +mock_verify_http_req = mock.Mock( + return_value={ + u'audience': u'http://example.com', + u'email': u'[email protected]', + u'issuer': u'login.persona.org:443', + u'status': u'okay', + u'valid-until': 1311377222765}) + + +# Mock object to patch postorius.utils.get_audience +mock_postorius_get_audience = mock.Mock(return_value=['http://example.com']) + + +class VerifyViewTest(unittest.TestCase): + """Tests Postorius' override of django_browserid's Verify view.""" + + def setUp(self): + self.factory = RequestFactory() + self.client = Client() + + @override_settings( + DEBUG=False, + SESSION_COOKIE_SECURE=False, + SITE_URL='http://foo.com', + BROWSERID_SITE_URL_ALGO=lambda settings: 'http://example.com') + # Mock custom get_audience as imported in the view. + @mock.patch('postorius.views.browserid.get_audience', + mock_postorius_get_audience) + # No real requests to the validation API please. + @mock.patch('django_browserid.base._verify_http_request', + mock_verify_http_req) + def test_postorius_get_audience_is_called(self): + # Make sure that a POST request to the browserid_login URI + # calls Postorius' version of get_audience. + self.client.post(reverse('browserid_login'), + {'assertion': 'foo'}, + SERVER_NAME='example.com') + self.assertEqual(mock_postorius_get_audience.call_count, 1) + self.assertEqual(mock_verify_http_req.call_count, 1) === modified file 'src/postorius/tests/utils.py' --- src/postorius/tests/utils.py 2012-10-05 15:45:38 +0000 +++ src/postorius/tests/utils.py 2014-01-13 22:13:50 +0000 @@ -14,7 +14,13 @@ # # You should have received a copy of the GNU General Public License along with # Postorius. If not, see <http://www.gnu.org/licenses/>. +import os +import time +import shutil +import tempfile +import subprocess +from django.conf import settings from mock import patch, MagicMock @@ -72,3 +78,62 @@ for key in properties: setattr(mock_object, key, properties[key]) return mock_object + + +class Testobject: + bindir = None + vardir = None + cfgfile = None + + +def setup_mm(testobject): + os.environ['MAILMAN_TEST_BINDIR'] = settings.MAILMAN_TEST_BINDIR + bindir = testobject.bindir = os.environ.get('MAILMAN_TEST_BINDIR') + if bindir is None: + raise RuntimeError("something's not quite right") + vardir = testobject.vardir = tempfile.mkdtemp() + cfgfile = testobject.cfgfile = os.path.join(vardir, 'client_test.cfg') + with open(cfgfile, 'w') as fp: + print >> fp, """\ +[mailman] +layout: tmpdir +[paths.tmpdir] +var_dir: {vardir} +log_dir: /tmp/mmclient/logs +[runner.archive] +start: no +[runner.bounces] +start: no +[runner.command] +start: no +[runner.in] +start: no +[runner.lmtp] +start: no +[runner.news] +start: no +[runner.out] +start: no +[runner.pipeline] +start: no +[runner.retry] +start: no +[runner.virgin] +start: no +[runner.digest] +start: no +""".format(vardir=vardir) + mailman = os.path.join(bindir, 'mailman') + subprocess.call([mailman, '-C', cfgfile, 'start', '-q']) + time.sleep(3) + return testobject + + +def teardown_mm(testobject): + bindir = testobject.bindir + cfgfile = testobject.cfgfile + vardir = testobject.vardir + mailman = os.path.join(bindir, 'mailman') + subprocess.call([mailman, '-C', cfgfile, 'stop', '-q']) + shutil.rmtree(vardir) + time.sleep(3) === modified file 'src/postorius/utils.py' --- src/postorius/utils.py 2013-10-22 20:01:21 +0000 +++ src/postorius/utils.py 2014-01-13 22:13:50 +0000 @@ -18,8 +18,11 @@ import logging from django.conf import settings -from django.shortcuts import render_to_response, redirect +from django.core.exceptions import ImproperlyConfigured +from django.shortcuts import render_to_response from django.template import RequestContext +from django.utils.importlib import import_module + from mailmanclient import Client @@ -49,3 +52,98 @@ {'error': "Mailman REST API not available. " "Please start Mailman core."}, context_instance=RequestContext(request)) + + +def _get_module_attr_from_string(path): + """Returns a module attribute importable from a string.""" + index = path.rfind('.') + mod, attr = path[:index], path[index + 1:] + return getattr(import_module(mod), attr) + + +def _get_site_url_algo_result(browserid_site_url_algo, settings=None): + """Either calls or imports and calls browserid_site_url_algo (using + the settings as first parameter) and returns the result. + Raises ``ImproperlyConfigured`` if it's not callable. + + :param browserid_site_url_algo: The callable to return or str to + import. + :type browserid_site_url_algo: callable or str + :param settings: The django settings object. + :type settings: object + :returns: Result of the callable. + """ + if not callable(browserid_site_url_algo): + try: + browserid_site_url_algo = _get_module_attr_from_string( + browserid_site_url_algo) + except (ImportError, AttributeError): + raise ImproperlyConfigured('BROWSERID_SITE_URL_ALGO could not be ' + 'imported.') + if not callable(browserid_site_url_algo): + raise ImproperlyConfigured('BROWSERID_SITE_URL_ALGO could be ' + 'imported but not called.') + return browserid_site_url_algo(settings) + + +def _get_clean_url(url): + """ + Removes trailing slashes from url strings and returns them as unicode. + """ + if url[-1] == '/': + return unicode(url[:len(url)-1]) + return unicode(url) + + +def _get_url_iterator(site_url, site_url_algo_result): + """ + Returns an iterable containing url strings. + + :param site_url: SITE_URL from settings. + :type site_url: str or list. + :param site_url_algo_result: Mailman domains. + :type site_url_algo_result: list. + """ + if isinstance(site_url, basestring): + site_url = [site_url] + if isinstance(site_url_algo_result, basestring): + site_url_algo_result = [site_url_algo_result] + try: + url_iterator = iter(site_url + + list(set(site_url_algo_result) - set(site_url))) + except TypeError: + raise ImproperlyConfigured('`SITE_URL ` and `BROWSERID_SITE_URL_ALGO` ' + 'don`t return a string or an iterable') + return [_get_clean_url(url) for url in url_iterator] + + +def get_audience(request): + """ + A rewrite of django_browserid.base.get_audience. + + In addition to using settings.SITE_URL to determine the audience, it + checks for the existence of settings.BROWSERID_SITE_URL_ALGO, which + can be any callable that returns the audience(s) as a string or + list/tuple. This can be used in environments where defining the list + of domains in settings.SITE_URL is too static. + """ + req_proto = 'https://' if request.is_secure() else 'http://' + req_domain = request.get_host() + req_url = '%s%s' % (req_proto, req_domain) + site_url = getattr(settings, 'SITE_URL', None) + site_url_algo_result = _get_site_url_algo_result( + getattr(settings, 'BROWSERID_SITE_URL_ALGO', None), settings) + if not site_url and not site_url_algo_result: + if settings.DEBUG: + return req_url + else: + raise ImproperlyConfigured( + '`SITE_URL` must be set or the callable defined with' + '`BROWSERID_SITE_URL_ALGO` must return (a) valid host' + 'url(s).') + url_iterator = _get_url_iterator(site_url, site_url_algo_result) + if req_url not in url_iterator: + raise ImproperlyConfigured( + 'request `{0}`, was not found in SITE_URL `{1}`'.format(req_url, + site_url)) + return req_url === added file 'src/postorius/views/browserid.py' --- src/postorius/views/browserid.py 1970-01-01 00:00:00 +0000 +++ src/postorius/views/browserid.py 2014-01-13 22:13:50 +0000 @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2012 by the Free Software Foundation, Inc. +# +# This file is part of Postorius. +# +# Postorius is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Postorius 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 +# Postorius. If not, see <http://www.gnu.org/licenses/>. +import logging + +from django.conf import settings +from django.contrib import auth +from django.core.exceptions import ImproperlyConfigured +from django.core.urlresolvers import NoReverseMatch +from django.http import HttpResponseRedirect +from django.shortcuts import redirect +from django.views.generic.edit import BaseFormView +from django_browserid.base import BrowserIDException, sanity_checks +from django_browserid.forms import BrowserIDForm +from django_browserid.views import Verify as DjangoBrowserIdVerify +from mailmanclient import Client + +from postorius.utils import get_audience + + +logger = logging.getLogger(__name__) + + +class Verify(DjangoBrowserIdVerify): + """Overrides ``django_browserid.views.Verify``.""" + + def form_valid(self, form): + """ + Override for ``django_browserid.views.Verify.form_valid``. + The content of the function is the same as in the original + class, but it uses the custom ``get_audience`` function + imported in this module. + + Verifies the given assertion and triggers login/failure. + + :param form: + Instance of BrowserIDForm that was submitted by the user. + """ + self.assertion = form.cleaned_data['assertion'] + self.audience = get_audience(self.request) + try: + self.user = auth.authenticate( + assertion=self.assertion, + audience=self.audience + ) + except BrowserIDException as e: + return self.login_failure(e) + if self.user and self.user.is_active: + return self.login_success() + return self.login_failure()
_______________________________________________ Mailman-coders mailing list [email protected] https://mail.python.org/mailman/listinfo/mailman-coders
