Commit 17640ac0 authored by Florian Fuchs's avatar Florian Fuchs

added auth decorator that checks that the logged-in user is either a superuser or the list owner.

parent 3373d4e3
# -*- 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/>.
"""Postorius view decorators."""
from django.core.exceptions import PermissionDenied
from postorius.models import (Domain, List, Member, MailmanUser,
MailmanApiError, Mailman404Error)
def list_owner_required(fn):
"""Check if the logged in user is the list owner of the given list.
Assumes that the request object is the first arg and that fqdn_listname
is present in kwargs.
"""
def wrapper(*args, **kwargs):
user = args[0].user
fqdn_listname = kwargs['fqdn_listname']
if not user.is_authenticated():
raise PermissionDenied
if user.is_superuser:
return fn(*args, **kwargs)
mlist = List.objects.get_or_404(fqdn_listname=fqdn_listname)
if user.email not in mlist.members:
raise PermissionDenied
else:
return fn(*args, **kwargs)
return wrapper
......@@ -34,6 +34,8 @@ along with Postorius. If not, see <http://www.gnu.org/licenses/>.
* added developer documentation
* added test helper utils
* all code now conform to PEP8
* themes: removed obsolete MAILMAN_THEME settings from templates, contexts, file structure; contributed by Richard Wackerbarth (LP: 1043258)
* added access control for list owners
1.0 alpha 1 -- "Space Farm"
......
......@@ -86,65 +86,8 @@ objects retreived from mailman.client, like:</p>
<li>Memberships</li>
<li>Addresses</li>
</ul>
<div class="section" id="module-postorius.tests.test_utils">
<span id="mocking-mailman-client-objects"></span><h4>Mocking mailman.client objects<a class="headerlink" href="#module-postorius.tests.test_utils" title="Permalink to this headline"></a></h4>
<div class="section" id="domains">
<h5>Domains<a class="headerlink" href="#domains" title="Permalink to this headline"></a></h5>
<p><tt class="docutils literal"><span class="pre">postorius.tests.utils.create_mock_domain</span></tt> creates a mock domain object:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">properties</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">contact_address</span><span class="o">=</span><span class="s">&#39;postmaster@example.org&#39;</span><span class="p">,</span>
<span class="gp">... </span> <span class="n">description</span><span class="o">=</span><span class="s">&#39;Example dot Org&#39;</span><span class="p">,</span>
<span class="gp">... </span> <span class="n">mail_host</span><span class="o">=</span><span class="s">&#39;example.org&#39;</span><span class="p">,</span>
<span class="gp">... </span> <span class="n">url_host</span><span class="o">=</span><span class="s">&#39;www.example.org&#39;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mock_domain</span> <span class="o">=</span> <span class="n">create_mock_domain</span><span class="p">(</span><span class="n">properties</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_domain</span>
<span class="go">&lt;MagicMock name=&#39;Domain&#39; id=&#39;...&#39;&gt;</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_domain</span><span class="o">.</span><span class="n">contact_address</span>
<span class="go">postmaster@example.org</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_domain</span><span class="o">.</span><span class="n">description</span>
<span class="go">Example dot Org</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_domain</span><span class="o">.</span><span class="n">mail_host</span>
<span class="go">example.org</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_domain</span><span class="o">.</span><span class="n">url_host</span>
<span class="go">www.example.org</span>
</pre></div>
</div>
</div>
<div class="section" id="mailing-lists">
<h5>Mailing Lists<a class="headerlink" href="#mailing-lists" title="Permalink to this headline"></a></h5>
<p><tt class="docutils literal"><span class="pre">postorius.tests.utils.create_mock_list</span></tt> creates a mock list object:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">properties</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">fqdn_listname</span><span class="o">=</span><span class="s">&#39;testlist@example.org&#39;</span><span class="p">,</span>
<span class="gp">... </span> <span class="n">mail_host</span><span class="o">=</span><span class="s">&#39;example.org&#39;</span><span class="p">,</span>
<span class="gp">... </span> <span class="n">list_name</span><span class="o">=</span><span class="s">&#39;testlist&#39;</span><span class="p">,</span>
<span class="gp">... </span> <span class="n">display_name</span><span class="o">=</span><span class="s">&#39;Test List&#39;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mock_list</span> <span class="o">=</span> <span class="n">create_mock_list</span><span class="p">(</span><span class="n">properties</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_list</span>
<span class="go">&lt;MagicMock name=&#39;List&#39; id=&#39;...&#39;&gt;</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_list</span><span class="o">.</span><span class="n">fqdn_listname</span>
<span class="go">testlist@example.org</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_list</span><span class="o">.</span><span class="n">mail_host</span>
<span class="go">example.org</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_list</span><span class="o">.</span><span class="n">list_name</span>
<span class="go">testlist</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_list</span><span class="o">.</span><span class="n">display_name</span>
<span class="go">Test List</span>
</pre></div>
</div>
</div>
<div class="section" id="memberships">
<h5>Memberships<a class="headerlink" href="#memberships" title="Permalink to this headline"></a></h5>
<p><tt class="docutils literal"><span class="pre">postorius.tests.utils.create_mock_list</span></tt> creates a mock list object:</p>
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">properties</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">fqdn_listname</span><span class="o">=</span><span class="s">&#39;testlist@example.org&#39;</span><span class="p">,</span>
<span class="gp">... </span> <span class="n">address</span><span class="o">=</span><span class="s">&#39;les@example.org&#39;</span><span class="p">,)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">mock_member</span> <span class="o">=</span> <span class="n">create_mock_member</span><span class="p">(</span><span class="n">properties</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_member</span>
<span class="go">&lt;MagicMock name=&#39;Member&#39; id=&#39;...&#39;&gt;</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_member</span><span class="o">.</span><span class="n">fqdn_listname</span>
<span class="go">testlist@example.org</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">print</span> <span class="n">mock_member</span><span class="o">.</span><span class="n">address</span>
<span class="go">les@example.org</span>
</pre></div>
</div>
</div>
<div class="section" id="mocking-mailman-client-objects">
<h4>Mocking mailman.client objects<a class="headerlink" href="#mocking-mailman-client-objects" title="Permalink to this headline"></a></h4>
</div>
</div>
</div>
......@@ -162,12 +105,7 @@ objects retreived from mailman.client, like:</p>
<li><a class="reference internal" href="#testing">Testing</a><ul>
<li><a class="reference internal" href="#running-the-tests">Running the tests</a></li>
<li><a class="reference internal" href="#mocking">Mocking</a><ul>
<li><a class="reference internal" href="#module-postorius.tests.test_utils">Mocking mailman.client objects</a><ul>
<li><a class="reference internal" href="#domains">Domains</a></li>
<li><a class="reference internal" href="#mailing-lists">Mailing Lists</a></li>
<li><a class="reference internal" href="#memberships">Memberships</a></li>
</ul>
</li>
<li><a class="reference internal" href="#mocking-mailman-client-objects">Mocking mailman.client objects</a></li>
</ul>
</li>
</ul>
......
......@@ -52,20 +52,9 @@
<h1 id="index">Index</h1>
<div class="genindex-jumpbox">
<a href="#P"><strong>P</strong></a>
| <a href="#T"><strong>T</strong></a>
<a href="#T"><strong>T</strong></a>
</div>
<h2 id="P">P</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="development.html#module-postorius.tests.test_utils">postorius.tests.test_utils (module)</a>
</dt>
</dl></td>
</tr></table>
<h2 id="T">T</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
......
......@@ -36,6 +36,9 @@
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="setup.html" title="Installation"
accesskey="N">next</a> |</li>
......@@ -79,6 +82,8 @@ along with Postorius. If not, see &lt;<a class="reference external" href="http:/
<li>added developer documentation</li>
<li>added test helper utils</li>
<li>all code now conform to PEP8</li>
<li>themes: removed obsolete MAILMAN_THEME settings from templates, contexts, file structure; contributed by Richard Wackerbarth (LP: 1043258)</li>
<li>added access control for list owners</li>
</ul>
</div>
<div class="section" id="alpha-1-space-farm">
......@@ -154,6 +159,9 @@ Daniel Mizyrycki</li>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="setup.html" title="Installation"
>next</a> |</li>
......
......@@ -53,35 +53,20 @@
<h1>Python Module Index</h1>
<div class="modindex-jumpbox">
<a href="#cap-p"><strong>p</strong></a> |
<a href="#cap-t"><strong>t</strong></a>
</div>
<table class="indextable modindextable" cellspacing="0" cellpadding="2">
<tr class="pcap"><td></td><td>&nbsp;</td><td></td></tr>
<tr class="cap" id="cap-p"><td></td><td>
<strong>p</strong></td><td></td></tr>
<tr>
<td><img src="_static/minus.png" class="toggler"
id="toggle-1" style="display: none" alt="-" /></td>
<td>
<tt class="xref">postorius</tt></td><td>
<em></em></td></tr>
<tr class="cg-1">
<td></td>
<td>&nbsp;&nbsp;&nbsp;
<a href="development.html#module-postorius.tests.test_utils"><tt class="xref">postorius.tests.test_utils</tt></a></td><td>
<em></em></td></tr>
<tr class="pcap"><td></td><td>&nbsp;</td><td></td></tr>
<tr class="cap" id="cap-t"><td></td><td>
<strong>t</strong></td><td></td></tr>
<tr>
<td><img src="_static/minus.png" class="toggler"
id="toggle-2" style="display: none" alt="-" /></td>
id="toggle-1" style="display: none" alt="-" /></td>
<td>
<tt class="xref">tests</tt></td><td>
<em></em></td></tr>
<tr class="cg-2">
<tr class="cg-1">
<td></td>
<td>&nbsp;&nbsp;&nbsp;
<a href="using.html#module-tests.tests"><tt class="xref">tests.tests</tt></a></td><td>
......
......@@ -35,6 +35,7 @@ along with Postorius. If not, see <http://www.gnu.org/licenses/>.
* added test helper utils
* all code now conform to PEP8
* themes: removed obsolete MAILMAN_THEME settings from templates, contexts, file structure; contributed by Richard Wackerbarth (LP: 1043258)
* added access control for list owners
1.0 alpha 1 -- "Space Farm"
......
......@@ -17,9 +17,11 @@
from postorius.tests import test_utils
from postorius.tests.test_list_members import *
from postorius.tests.test_auth_decorators import *
__test__ = {
"Test Utils": test_utils,
"List members": ListMembersViewTest,
"List Owner Required": ListOwnerRequiredTest,
#"Doctest": tests,
}
# -*- 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/>.
from django.contrib.auth.models import AnonymousUser, User
from django.core.exceptions import PermissionDenied
from django.utils import unittest
from mock import patch
from postorius.auth.decorators import list_owner_required
from postorius.models import (Domain, List, Member, MailmanUser,
MailmanApiError, Mailman404Error)
from mailman.client import Client
@list_owner_required
def dummy_function(request, fqdn_listname):
return True
class ListOwnerRequiredTest(unittest.TestCase):
"""Tests the list_owner_required auth decorator."""
def setUp(self):
from django.test.client import RequestFactory
from postorius.tests.utils import create_mock_list
self.request_factory = RequestFactory()
# create a mock list with members
list_name = 'foolist@example.org'
list_id = 'foolist.example.org'
self.mock_list = create_mock_list(dict(
fqdn_listname=list_name,
list_id=list_id))
@patch.object(Client, 'get_list')
def test_not_authenticated(self, mock_get_list):
"""Should raise PermissionDenied if user is not authenticated."""
mock_get_list.return_value = self.mock_list
request = self.request_factory.get('/lists/foolist@example.org/'
'settings/')
request.user = AnonymousUser()
self.assertRaises(PermissionDenied, dummy_function, request,
fqdn_listname='foolist@example.org')
@patch.object(Client, 'get_list')
def test_superuser(self, mock_get_list):
"""Should call the dummy method, if user is superuser."""
mock_get_list.return_value = self.mock_list
request = self.request_factory.get('/lists/foolist@example.org/'
'settings/')
request.user = User.objects.create_superuser('su1', 'su@sodo.org',
'pwd')
return_value = dummy_function(request,
fqdn_listname='foolist@example.org')
self.assertEqual(return_value, True)
@patch.object(Client, 'get_list')
def test_non_list_owner(self, mock_get_list):
"""Should raise PermissionDenied user is not a list owner."""
# prepare mock list object
self.mock_list.members = ['geddy@rush.it']
mock_get_list.return_value = self.mock_list
# prepare request
request = self.request_factory.get('/lists/foolist@example.org/'
'settings/')
request.user = User.objects.create_user('les c', 'les@primus.org',
'pwd')
self.assertRaises(PermissionDenied, dummy_function, request,
fqdn_listname='foolist@example.org')
@patch.object(Client, 'get_list')
def test_non_list_owner(self, mock_get_list):
"""Should raise PermissionDenied user is not a list owner."""
# prepare mock list object
self.mock_list.members = ['les@primus.org']
mock_get_list.return_value = self.mock_list
# prepare request
request = self.request_factory.get('/lists/foolist@example.org/'
'settings/')
request.user = User.objects.create_user('les cl', 'les@primus.org',
'pwd')
return_value = dummy_function(request,
fqdn_listname='foolist@example.org')
self.assertEqual(return_value, True)
......@@ -16,6 +16,7 @@
# Postorius. If not, see <http://www.gnu.org/licenses/>.
from django.contrib.auth.models import AnonymousUser, User
from django.core.exceptions import PermissionDenied
from django.utils import unittest
from mock import patch
......@@ -61,20 +62,19 @@ class ListMembersViewTest(unittest.TestCase):
'/lists/foolist@example.org/members/')
# anonymous users should be redirected
request.user = AnonymousUser()
response = ListMembersView.as_view()(request,
'foolist@example.org')
self.assertEqual(response.status_code, 302)
self.assertRaises(PermissionDenied, ListMembersView.as_view(),
request, fqdn_listname='foolist@example.org')
# logged in users should be redirected
request.user = User.objects.create_user('les', 'les@primus.org',
'pwd')
response = ListMembersView.as_view()(request,
'foolist@example.org')
self.assertEqual(response.status_code, 302)
self.assertRaises(PermissionDenied, ListMembersView.as_view(),
request, fqdn_listname='foolist@example.org')
# superusers should get the page
request.user = User.objects.create_superuser('su', 'su@sodo.org',
'pwd')
response = ListMembersView.as_view()(request,
'foolist@example.org')
response = ListMembersView.as_view()(
request,
fqdn_listname='foolist@example.org')
self.assertEqual(response.status_code, 200)
def tearDown(self):
......
......@@ -40,9 +40,10 @@ from django.utils.translation import gettext as _
from django.views.generic import TemplateView
from mailman.client import Client
from models import (Domain, List, Member, MailmanUser, MailmanApiError,
Mailman404Error)
from forms import *
from postorius.models import (Domain, List, Member, MailmanUser,
MailmanApiError, Mailman404Error)
from postorius.forms import *
from postorius.auth.decorators import list_owner_required
from urllib2 import HTTPError
......@@ -101,8 +102,7 @@ class ListMembersView(TemplateView):
def get_list(self, fqdn_listname):
return List.objects.get_or_404(fqdn_listname=fqdn_listname)
@method_decorator(login_required(login_url='/accounts/login/'))
@method_decorator(user_passes_test(lambda u: u.is_superuser))
@method_decorator(list_owner_required)
def get(self, request, fqdn_listname):
return render_to_response('postorius/lists/members.html',
{'list': self.get_list(fqdn_listname)},
......@@ -193,7 +193,7 @@ def list_index(request, template='postorius/lists/index.html'):
context_instance=RequestContext(request))
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def list_metrics(request, fqdn_listname=None, option=None,
template='postorius/lists/metrics.html'):
"""
......@@ -386,8 +386,7 @@ def list_subscriptions(request, option=None, fqdn_listname=None,
context_instance=RequestContext(request))
@login_required
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def list_delete(request, fqdn_listname):
"""Deletes a list but asks for confirmation first.
"""
......@@ -411,7 +410,7 @@ def list_delete(request, fqdn_listname):
context_instance=RequestContext(request))
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def list_held_messages(request, fqdn_listname):
"""Shows a list of held messages.
"""
......@@ -424,7 +423,7 @@ def list_held_messages(request, fqdn_listname):
context_instance=RequestContext(request))
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def accept_held_message(request, fqdn_listname, msg_id):
"""Accepts a held message.
"""
......@@ -440,7 +439,7 @@ def accept_held_message(request, fqdn_listname, msg_id):
return redirect('list_held_messages', the_list.fqdn_listname)
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def discard_held_message(request, fqdn_listname, msg_id):
"""Accepts a held message.
"""
......@@ -456,7 +455,7 @@ def discard_held_message(request, fqdn_listname, msg_id):
return redirect('list_held_messages', the_list.fqdn_listname)
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def defer_held_message(request, fqdn_listname, msg_id):
"""Accepts a held message.
"""
......@@ -472,7 +471,7 @@ def defer_held_message(request, fqdn_listname, msg_id):
return redirect('list_held_messages', the_list.fqdn_listname)
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def reject_held_message(request, fqdn_listname, msg_id):
"""Accepts a held message.
"""
......@@ -488,8 +487,7 @@ def reject_held_message(request, fqdn_listname, msg_id):
return redirect('list_held_messages', the_list.fqdn_listname)
@login_required
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def list_settings(request, fqdn_listname=None, visible_section=None,
visible_option=None,
template='postorius/lists/settings.html'):
......@@ -558,8 +556,7 @@ def list_settings(request, fqdn_listname=None, visible_section=None,
context_instance=RequestContext(request))
@login_required
@user_passes_test(lambda u: u.is_superuser)
@list_owner_required
def mass_subscribe(request, fqdn_listname=None,
template='postorius/lists/mass_subscribe.html'):
"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment