Skip to content

Commit

Permalink
Bug 574276: Implemented the nickname blacklist.
Browse files Browse the repository at this point in the history
 - added the BlacklistedNickname model to the users app
 - updated the user registration form to check for blacklisted nicknames
 - admin add form for blacklisted nicknames
 - tests
  • Loading branch information
chenba committed Jul 26, 2010
1 parent b889ab2 commit 26fd705
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 8 deletions.
39 changes: 37 additions & 2 deletions apps/users/admin.py
@@ -1,7 +1,11 @@
from django.contrib import admin
from django.contrib import admin, messages
from django.db.utils import IntegrityError

import jingo

from access.admin import GroupUserInline
from .models import UserProfile
from .models import UserProfile, BlacklistedNickname
from .users import forms


class UserAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -32,4 +36,35 @@ class UserAdmin(admin.ModelAdmin):
)


class BlacklistedNicknameAdmin(admin.ModelAdmin):
list_display = search_fields = ('nickname',)

def add_view(self, request, form_url='', extra_context=None):
"""Override the default admin add view for bulk add."""
form = forms.BlacklistedNicknameAddForm()
if request.method == 'POST':
form = forms.BlacklistedNicknameAddForm(request.POST)
if form.is_valid():
inserted = 0
duplicates = 0
for n in form.cleaned_data['nicknames'].splitlines():
try:
BlacklistedNickname.objects.create(nickname=n)
inserted += 1
except IntegrityError:
# note: unless we manage the transactions manually,
# we do lose a primary id here
duplicates += 1
msg = '%s new nicknames added to the blacklist.' % (inserted)
if duplicates:
msg += ' %s duplicates were ignored.' % (duplicates)
messages.success(request, msg)
# Default django admin change list view does not print messages
# no redirect for now
# return http.HttpResponseRedirect(reverse(
# 'admin:users_blacklistednickname_changelist'))
return jingo.render(request, 'admin/blacklisted_nickname/add.html',
{'form': form})

admin.site.register(UserProfile, UserAdmin)
admin.site.register(BlacklistedNickname, BlacklistedNicknameAdmin)
58 changes: 58 additions & 0 deletions apps/users/fixtures/users/test_backends.json
Expand Up @@ -96,5 +96,63 @@
"modified": "2009-09-24 20:11:30",
"notifyevents": 1
}
},
{
"pk": 5349055,
"model": "auth.user",
"fields": {
"username": "testo@example.com",
"first_name": "Barry",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"last_login": "2010-07-23 00:43:32",
"groups": [],
"user_permissions": [],
"password": "sha512$9ec347fc67fc17c99a9dd43d1431a5959011acf247fb2128228c9a04208d1991$edcc677c7045ee2a379286e2ad6d99bd58af6b4a2117e1e4c2743923a9a297ce56d5311fe51baa4584c5483af0eaedc57700fbb4b719c0a84597c1609536ca4d",
"email": "testo@example.com",
"date_joined": "2010-07-03 23:03:11"
}
},
{
"pk": 5349055,
"model": "users.userprofile",
"fields": {
"sandboxshown": false,
"display_collections_fav": false,
"display_collections": false,
"occupation": "",
"confirmationcode": "",
"location": "",
"picture_type": "",
"averagerating": "",
"homepage": "",
"email": "testo@example.com",
"notifycompat": true,
"bio": 1124105,
"firstname": "Barry",
"deleted": false,
"lastname": "",
"emailhidden": false,
"user": 5349055,
"password": "sha512$9ec347fc67fc17c99a9dd43d1431a5959011acf247fb2128228c9a04208d1991$edcc677c7045ee2a379286e2ad6d99bd58af6b4a2117e1e4c2743923a9a297ce56d5311fe51baa4584c5483af0eaedc57700fbb4b719c0a84597c1609536ca4d",
"nickname": "barry",
"resetcode_expires": "2010-07-23 00:37:10",
"resetcode": "",
"created": "2010-07-03 23:03:11",
"notes": "",
"modified": "2010-07-23 00:37:10",
"notifyevents": true
}
},
{
"pk": 1,
"model": "users.blacklistednickname",
"fields": {
"nickname": "IE6Fan",
"modified": "2010-07-21 23:32:05",
"created": "2010-07-21 23:32:05"
}
}
]
34 changes: 31 additions & 3 deletions apps/users/forms.py
@@ -1,11 +1,13 @@
import os

from django import forms
from django.contrib.auth import forms as auth_forms
from django.forms.util import ErrorList

import commonware.log
from tower import ugettext as _

from . import models
from .models import UserProfile, BlacklistedNickname

log = commonware.log.getLogger('z.users')

Expand Down Expand Up @@ -74,7 +76,7 @@ class UserRegisterForm(forms.ModelForm):
widget=forms.PasswordInput(render_value=False))

class Meta:
model = models.UserProfile
model = UserProfile

def clean_nickname(self):
"""We're breaking the rules and allowing null=True and blank=True on a
Expand Down Expand Up @@ -109,6 +111,13 @@ def clean(self):
self._errors["lastname"] = ErrorList([msg])
self._errors["nickname"] = ErrorList([msg])

# Nickname could be blacklisted
if ("nickname" not in self._errors
and nname
and BlacklistedNickname.blocked(nname)):
msg = _("This nickname is invalid.")
self._errors["nickname"] = ErrorList([msg])

return data


Expand All @@ -121,7 +130,7 @@ def __init__(self, *args, **kwargs):
super(UserEditForm, self).__init__(*args, **kwargs)

class Meta:
model = models.UserProfile
model = UserProfile
exclude = ['password']

def clean(self):
Expand Down Expand Up @@ -154,3 +163,22 @@ def save(self):
log.debug(u'User (%s) updated their profile' % amouser)

amouser.save()


class BlacklistedNicknameAddForm(forms.Form):
"""Form for adding blacklisted nickname in bulk fashion."""
nicknames = forms.CharField(widget=forms.Textarea(
attrs={'cols': 40, 'rows': 16}))

def clean(self):
super(BlacklistedNicknameAddForm, self).clean()
data = self.cleaned_data

if 'nicknames' in data:
data['nicknames'] = os.linesep.join(
[s for s in data['nicknames'].splitlines() if s])
if 'nicknames' not in data or data['nicknames'] == '':
msg = 'Please enter at least one nickname to blacklist.'
self._errors['nicknames'] = ErrorList([msg])

return data
15 changes: 15 additions & 0 deletions apps/users/models.py
Expand Up @@ -191,3 +191,18 @@ def create_django_user(self):
self.user.save()
self.save()
return self.user


class BlacklistedNickname(amo.models.ModelBase):
"""Blacklisted user nicknames."""
nickname = models.CharField(max_length=255, unique=True, default='')

def __unicode__(self):
return self.nickname

@classmethod
def blocked(cls, nick):
"""Check to see if a nickname is in the blacklist."""
# Could also cache the entire blacklist and simply check if the
# nickname is in the list here. @TODO?
return cls.uncached.only('nickname').filter(nickname=nick).exists()
48 changes: 48 additions & 0 deletions apps/users/templates/admin/blacklisted_nickname/add.html
@@ -0,0 +1,48 @@
{% extends "admin/base.html" %}
{% from 'includes/forms.html' import required %}
{% block extrahead %}
{{ super() }}
<link rel="stylesheet" href="{{ MEDIA_URL }}css/zamboni/admin-django.css">
{% endblock %}

{% block title %}{{ page_title('Add Blacklisted Nicknames') }}{% endblock %}

{% block bodyclass %}users-blacklistednickname change-form{% endblock %}

{% block content %}

<div class="breadcrumbs">
<a href="{{ url('admin:index') }}">Home</a> &rsaquo;
<a href="{{ url('admin:app_list', app_label='users') }}">Users</a> &rsaquo;
<a href="{{ url('admin:users_blacklistednickname_changelist') }}">Blacklisted nicknames</a> &rsaquo;
Add
</div>
<div id="content" class="colM">
<h2>Add Blacklisted Nicknames</h2>
<div id="content-main">
{% if messages %}
{% for message in messages %}
<div class="notification-box {{ message.tags }}">
<h2>{{ message }}</h2>
</div>
{% endfor %}
{% endif %}

{% if form %}
<form method="post" action="">
{{ csrf() }}
<p>Enter one nickname per line.</p>
<div class="form-row">
{{ form.nicknames.errors|safe }}
<label for="id_nicknames">Blacklisted Nicknames {{ required() }}</label>
{{ form.nicknames|safe }}
</div>
<div class="submit-row">
<input type="submit" name="_save" class="default" value="Save">
</div>
</form>
{% endif %}
</div>
</div>

{% endblock content %}
32 changes: 30 additions & 2 deletions apps/users/tests/test_forms.py
Expand Up @@ -8,6 +8,7 @@
import test_utils

from amo.helpers import urlparams
from amo.urlresolvers import reverse


class UserFormBase(test_utils.TestCase):
Expand Down Expand Up @@ -204,8 +205,6 @@ def test_redirect_after_login_evil(self):
'password': 'foo'}, follow=True)
self.assertRedirects(r, '/en-US/firefox/')



def test_unconfirmed_account(self):
url = self._get_login_url()
self.user_profile.confirmationcode = 'blah'
Expand Down Expand Up @@ -262,6 +261,15 @@ def test_set_unmatched_passwords(self):
'The passwords did not match.')
eq_(len(mail.outbox), 0)

def test_invalid_nickname(self):
data = {'email': 'testo@example.com',
'password': 'xxx',
'password2': 'xxx',
'nickname': 'IE6Fan', }
r = self.client.post('/en-US/firefox/users/register', data)
self.assertFormError(r, 'form', 'nickname',
'This nickname is invalid.')

def test_already_logged_in(self):
self.client.login(username='jbalogh@mozilla.com', password='foo')
r = self.client.get('/users/register', follow=True)
Expand All @@ -286,3 +294,23 @@ def test_success(self):
assert mail.outbox[0].subject.find('Please confirm your email') == 0
assert mail.outbox[0].body.find('%s/confirm/%s' %
(u.id, u.confirmationcode)) > 0


class TestBlacklistedNicknameAdminAddForm(UserFormBase):

def test_no_nicknames(self):
self.client.login(username='testo@example.com', password='foo')
url = reverse('admin:users_blacklistednickname_add')
data = {'nicknames': "\n\n", }
r = self.client.post(url, data)
msg = 'Please enter at least one nickname to blacklist.'
self.assertFormError(r, 'form', 'nicknames', msg)

def test_add(self):
self.client.login(username='testo@example.com', password='foo')
url = reverse('admin:users_blacklistednickname_add')
data = {'nicknames': "IE6Fan\nfubar\n\n", }
r = self.client.post(url, data)
msg = '1 new nicknames added to the blacklist. '
msg += '1 duplicates were ignored.'
self.assertContains(r, msg)
10 changes: 9 additions & 1 deletion apps/users/tests/test_models.py
Expand Up @@ -9,7 +9,7 @@

from addons.models import Addon, AddonUser
from reviews.models import Review
from users.models import UserProfile, get_hexdigest
from users.models import UserProfile, get_hexdigest, BlacklistedNickname


class TestUserProfile(test.TestCase):
Expand Down Expand Up @@ -141,3 +141,11 @@ def test_valid_new_password(self):
u = UserProfile()
u.set_password('sekrit')
assert u.check_password('sekrit') is True


class TestBlacklistedNickname(test.TestCase):
fixtures = ['users/test_backends']

def test_blocked(self):
eq_(BlacklistedNickname.blocked('IE6Fan'), True)
eq_(BlacklistedNickname.blocked('testo'), False)
8 changes: 8 additions & 0 deletions migrations/55-users_blacklistednickname.sql
@@ -0,0 +1,8 @@
CREATE TABLE `users_blacklistednickname` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`nickname` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `nickname` (`nickname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

0 comments on commit 26fd705

Please sign in to comment.