|
0
|
1 |
import time |
|
|
2 |
import datetime |
|
|
3 |
|
|
|
4 |
from django import forms |
|
|
5 |
from django.forms.util import ErrorDict |
|
|
6 |
from django.conf import settings |
|
|
7 |
from django.contrib.contenttypes.models import ContentType |
|
|
8 |
from models import Comment |
|
|
9 |
from django.utils.encoding import force_unicode |
|
|
10 |
from django.utils.hashcompat import sha_constructor |
|
|
11 |
from django.utils.text import get_text_list |
|
|
12 |
from django.utils.translation import ungettext, ugettext_lazy as _ |
|
|
13 |
|
|
|
14 |
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH', 3000) |
|
|
15 |
|
|
|
16 |
class CommentSecurityForm(forms.Form): |
|
|
17 |
""" |
|
|
18 |
Handles the security aspects (anti-spoofing) for comment forms. |
|
|
19 |
""" |
|
|
20 |
content_type = forms.CharField(widget=forms.HiddenInput) |
|
|
21 |
object_pk = forms.CharField(widget=forms.HiddenInput) |
|
|
22 |
timestamp = forms.IntegerField(widget=forms.HiddenInput) |
|
|
23 |
security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput) |
|
|
24 |
|
|
|
25 |
def __init__(self, target_object, data=None, initial=None): |
|
|
26 |
self.target_object = target_object |
|
|
27 |
if initial is None: |
|
|
28 |
initial = {} |
|
|
29 |
initial.update(self.generate_security_data()) |
|
|
30 |
super(CommentSecurityForm, self).__init__(data=data, initial=initial) |
|
29
|
31 |
|
|
0
|
32 |
def security_errors(self): |
|
|
33 |
"""Return just those errors associated with security""" |
|
|
34 |
errors = ErrorDict() |
|
|
35 |
for f in ["honeypot", "timestamp", "security_hash"]: |
|
|
36 |
if f in self.errors: |
|
|
37 |
errors[f] = self.errors[f] |
|
|
38 |
return errors |
|
|
39 |
|
|
|
40 |
def clean_security_hash(self): |
|
|
41 |
"""Check the security hash.""" |
|
|
42 |
security_hash_dict = { |
|
|
43 |
'content_type' : self.data.get("content_type", ""), |
|
|
44 |
'object_pk' : self.data.get("object_pk", ""), |
|
|
45 |
'timestamp' : self.data.get("timestamp", ""), |
|
|
46 |
} |
|
|
47 |
expected_hash = self.generate_security_hash(**security_hash_dict) |
|
|
48 |
actual_hash = self.cleaned_data["security_hash"] |
|
|
49 |
if expected_hash != actual_hash: |
|
|
50 |
raise forms.ValidationError("Security hash check failed.") |
|
|
51 |
return actual_hash |
|
|
52 |
|
|
|
53 |
def clean_timestamp(self): |
|
|
54 |
"""Make sure the timestamp isn't too far (> 2 hours) in the past.""" |
|
|
55 |
ts = self.cleaned_data["timestamp"] |
|
|
56 |
if time.time() - ts > (2 * 60 * 60): |
|
|
57 |
raise forms.ValidationError("Timestamp check failed") |
|
|
58 |
return ts |
|
|
59 |
|
|
|
60 |
def generate_security_data(self): |
|
|
61 |
"""Generate a dict of security data for "initial" data.""" |
|
|
62 |
timestamp = int(time.time()) |
|
|
63 |
security_dict = { |
|
|
64 |
'content_type' : str(self.target_object._meta), |
|
|
65 |
'object_pk' : str(self.target_object._get_pk_val()), |
|
|
66 |
'timestamp' : str(timestamp), |
|
|
67 |
'security_hash' : self.initial_security_hash(timestamp), |
|
|
68 |
} |
|
|
69 |
return security_dict |
|
|
70 |
|
|
|
71 |
def initial_security_hash(self, timestamp): |
|
|
72 |
""" |
|
|
73 |
Generate the initial security hash from self.content_object |
|
|
74 |
and a (unix) timestamp. |
|
|
75 |
""" |
|
|
76 |
|
|
|
77 |
initial_security_dict = { |
|
|
78 |
'content_type' : str(self.target_object._meta), |
|
|
79 |
'object_pk' : str(self.target_object._get_pk_val()), |
|
|
80 |
'timestamp' : str(timestamp), |
|
|
81 |
} |
|
|
82 |
return self.generate_security_hash(**initial_security_dict) |
|
|
83 |
|
|
|
84 |
def generate_security_hash(self, content_type, object_pk, timestamp): |
|
|
85 |
"""Generate a (SHA1) security hash from the provided info.""" |
|
|
86 |
info = (content_type, object_pk, timestamp, settings.SECRET_KEY) |
|
|
87 |
return sha_constructor("".join(info)).hexdigest() |
|
|
88 |
|
|
|
89 |
class CommentDetailsForm(CommentSecurityForm): |
|
|
90 |
""" |
|
|
91 |
Handles the specific details of the comment (name, comment, etc.). |
|
|
92 |
""" |
|
|
93 |
name = forms.CharField(label=_("Name"), max_length=50) |
|
|
94 |
email = forms.EmailField(label=_("Email address")) |
|
|
95 |
url = forms.URLField(label=_("URL"), required=False) |
|
|
96 |
comment = forms.CharField(label=_('Comment'), widget=forms.Textarea, |
|
|
97 |
max_length=COMMENT_MAX_LENGTH) |
|
|
98 |
|
|
|
99 |
def get_comment_object(self): |
|
|
100 |
""" |
|
|
101 |
Return a new (unsaved) comment object based on the information in this |
|
|
102 |
form. Assumes that the form is already validated and will throw a |
|
|
103 |
ValueError if not. |
|
|
104 |
|
|
|
105 |
Does not set any of the fields that would come from a Request object |
|
|
106 |
(i.e. ``user`` or ``ip_address``). |
|
|
107 |
""" |
|
|
108 |
if not self.is_valid(): |
|
|
109 |
raise ValueError("get_comment_object may only be called on valid forms") |
|
29
|
110 |
|
|
0
|
111 |
CommentModel = self.get_comment_model() |
|
|
112 |
new = CommentModel(**self.get_comment_create_data()) |
|
|
113 |
new = self.check_for_duplicate_comment(new) |
|
29
|
114 |
|
|
0
|
115 |
return new |
|
29
|
116 |
|
|
0
|
117 |
def get_comment_model(self): |
|
|
118 |
""" |
|
|
119 |
Get the comment model to create with this form. Subclasses in custom |
|
|
120 |
comment apps should override this, get_comment_create_data, and perhaps |
|
|
121 |
check_for_duplicate_comment to provide custom comment models. |
|
|
122 |
""" |
|
|
123 |
return Comment |
|
29
|
124 |
|
|
0
|
125 |
def get_comment_create_data(self): |
|
|
126 |
""" |
|
|
127 |
Returns the dict of data to be used to create a comment. Subclasses in |
|
|
128 |
custom comment apps that override get_comment_model can override this |
|
|
129 |
method to add extra fields onto a custom comment model. |
|
|
130 |
""" |
|
|
131 |
return dict( |
|
|
132 |
content_type = ContentType.objects.get_for_model(self.target_object), |
|
|
133 |
object_pk = force_unicode(self.target_object._get_pk_val()), |
|
|
134 |
user_name = self.cleaned_data["name"], |
|
|
135 |
user_email = self.cleaned_data["email"], |
|
|
136 |
user_url = self.cleaned_data["url"], |
|
|
137 |
comment = self.cleaned_data["comment"], |
|
|
138 |
submit_date = datetime.datetime.now(), |
|
|
139 |
site_id = settings.SITE_ID, |
|
|
140 |
is_public = True, |
|
|
141 |
is_removed = False, |
|
|
142 |
) |
|
29
|
143 |
|
|
0
|
144 |
def check_for_duplicate_comment(self, new): |
|
|
145 |
""" |
|
|
146 |
Check that a submitted comment isn't a duplicate. This might be caused |
|
|
147 |
by someone posting a comment twice. If it is a dup, silently return the *previous* comment. |
|
|
148 |
""" |
|
29
|
149 |
possible_duplicates = self.get_comment_model()._default_manager.using( |
|
|
150 |
self.target_object._state.db |
|
|
151 |
).filter( |
|
0
|
152 |
content_type = new.content_type, |
|
|
153 |
object_pk = new.object_pk, |
|
|
154 |
user_name = new.user_name, |
|
|
155 |
user_email = new.user_email, |
|
|
156 |
user_url = new.user_url, |
|
|
157 |
) |
|
|
158 |
for old in possible_duplicates: |
|
|
159 |
if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment: |
|
|
160 |
return old |
|
29
|
161 |
|
|
0
|
162 |
return new |
|
|
163 |
|
|
|
164 |
def clean_comment(self): |
|
|
165 |
""" |
|
|
166 |
If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't |
|
|
167 |
contain anything in PROFANITIES_LIST. |
|
|
168 |
""" |
|
|
169 |
comment = self.cleaned_data["comment"] |
|
|
170 |
if settings.COMMENTS_ALLOW_PROFANITIES == False: |
|
|
171 |
bad_words = [w for w in settings.PROFANITIES_LIST if w in comment.lower()] |
|
|
172 |
if bad_words: |
|
|
173 |
plural = len(bad_words) > 1 |
|
|
174 |
raise forms.ValidationError(ungettext( |
|
|
175 |
"Watch your mouth! The word %s is not allowed here.", |
|
|
176 |
"Watch your mouth! The words %s are not allowed here.", plural) % \ |
|
|
177 |
get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in bad_words], 'and')) |
|
|
178 |
return comment |
|
|
179 |
|
|
|
180 |
class CommentForm(CommentDetailsForm): |
|
|
181 |
honeypot = forms.CharField(required=False, |
|
|
182 |
label=_('If you enter anything in this field '\ |
|
|
183 |
'your comment will be treated as spam')) |
|
|
184 |
|
|
|
185 |
def clean_honeypot(self): |
|
|
186 |
"""Check that nothing's been entered into the honeypot.""" |
|
|
187 |
value = self.cleaned_data["honeypot"] |
|
|
188 |
if value: |
|
|
189 |
raise forms.ValidationError(self.fields["honeypot"].label) |
|
|
190 |
return value |