def receive_mail(request): try: msg = base64.b64decode(request.raw_post_data) except TypeError: return forbidden(request) msg = email.message_from_string(msg) addr = msg.get("To") if not addr: return forbidden(request) try: values, tamper_check = base64.b64decode(addr).split("\0") except TypeError: return forbidden(request) if hash_val(values) != tamper_check: return forbidden(request) try: values = json.loads(values) except ValueError: return forbidden(request) # The Discussion Form wants this hashed value as a tamper-proof check values['parent_id_sig'] = hash_val(values['parent_id']) try: user = User.objects.get(email=values.pop("user")) group = Group.objects.get(slug=values.pop("group")) parent_disc = Discussion.objects.get(group=group, id=values['parent_id']) except (User.DoesNotExist, Group.DoesNotExist, Discussion.DoesNotExist), e: return forbidden(request)
def __init__(self, *args, **kwargs): super(DiscussionCreateForm, self).__init__(*args, **kwargs) # If a parent_id was passed in, sign it if 'parent_id' in self.initial: self.fields['parent_id_sig'].initial = hash_val( self.initial.get('parent_id')) self.fields['subject'].widget = forms.HiddenInput()
def __init__(self, sender, *args, **kwargs): forms.Form.__init__(self, *args, **kwargs) # If a parent_id was passed in, sign it if 'parent_id' in self.initial: self.fields['parent_id_sig'].initial = hash_val(self.initial.get('parent_id')) self.fields['subject'].widget = forms.HiddenInput() self.sender = sender
def email_extra_headers(self, user_object): """ The Messaging system will look for this method to call while constructing emails to send; we build a custom Reply-To header that encodes the discussion's group and thread ID, and the email address of the recipient we're sending to, so that email replies can use the address to find what discussion to try to append to. We encode the recipient's email address so that we can allow users to send email replies from an address other than the one that received it -- e.g. if I have multiple addresses going to the same email account and prefer to send with a specific one. A secret-key-hash of the JSONified data is joined to the data itself, and the resulting string (base64-encoded) is used as the Reply-To value. This way, when receiving email responses, we can use the hashed value to confirm that the user did not tamper with or manually construct the address to send to. This is important because we will need to trust the inbound email as coming from the user it claims to be coming from. """ if self.disallow_replies: # If we're not allowing replies, just use the system defaults # and don't build a magic reply-to header. return None value = json.dumps(dict(parent_id=self.thread_id, user=user_object.email, group=self.group.slug)) value = "%s\0%s" % (value, hash_val(value)) value = base64.b64encode(value) return {"Reply-To": "%s@%s" % (value, settings.SMTP_HTTP_RELAY_DOMAIN)}
def render_message(self, content_object, email, user_object, extra_params=None): recipient_message = RecipientMessage.objects.create(message=self, recipient=email, token=hash_val([email, datetime.datetime.now()])) domain = Site.objects.get_current().domain params = {"content_object": content_object, "domain": domain, "recipient": user_object if user_object else email } if extra_params: params.update(extra_params) extra_headers = self.extra_headers(content_object, user_object) context = template.Context(params) # render the body and subject template with the given, template subject = template.Template(self.subject).render(context) body = template.Template(self.body).render(context) replacer = LinkReplacer(recipient_message=recipient_message) body = re.sub(URL_REGEX, replacer.replace_link, body) open_link = '<img src="http://%s%s"></img>' % (domain, reverse("message_open", args=[recipient_message.token])) # insert an open tracking image into the body body += open_link msg = EmailMessage(subject, body, from_email=None, to=[email], headers=extra_headers) msg.content_subtype = "html" return msg
def parse_arg(self, code, parser): tmp_dir = make_tmp_dir(self._data_dir) js_path = hash_val(code) + '.js' js_path = os.path.join(tmp_dir, js_path) write(js_path, code) ast_path = parser.parse(js_path) return tmp_dir, ast_path
def render_message(self, content_object, email, user_object, extra_params=None): recipient_message = RecipientMessage.objects.create( message=self, recipient=email, token=hash_val([email, datetime.datetime.now()])) domain = Site.objects.get_current().domain params = { "content_object": content_object, "domain": domain, "recipient": user_object if user_object else email } if extra_params: params.update(extra_params) context = template.Context(params) # render the body and subject template with the given, template subject = template.Template(self.subject).render(context) body = template.Template(self.body).render(context) replacer = LinkReplacer(recipient_message=recipient_message) body = re.sub(URL_REGEX, replacer.replace_link, body) open_link = '<img src="http://%s%s"></img>' % ( domain, reverse("message_open", args=[recipient_message.token])) # insert an open tracking image into the body body += open_link msg = EmailMessage(subject, body, None, [email]) msg.content_subtype = "html" return msg
def clean_parent_id(self): """Verify the parent_id_sig""" parent_id = self.cleaned_data['parent_id'] if parent_id: sig_check = hash_val(parent_id) if parent_id and sig_check <> self.data['parent_id_sig']: raise forms.ValidationError('Parent ID is currupted') return parent_id
def clean_parent_id(self): """Verify the parent_id_sig""" parent_id = self.cleaned_data["parent_id"] if parent_id: sig_check = hash_val(parent_id) if parent_id and sig_check <> self.data["parent_id_sig"]: raise forms.ValidationError(_("Parent ID is currupted")) return parent_id
def test_invalid_email(self): self.client.login(username="******", password="******") response = self.client.post(self.url, {"emails": "invalid_email", "content_type": self.post_content_type.pk, "object_pk": self.post.pk, "signature": hash_val((self.post_content_type, self.post.pk,)), "token": "81yuksdfkq2ro2i", "next": "/login/"}, follow=True) self.failUnlessEqual(response.template[0].name, "registration/login.html") message = iter(response.context["messages"]).next() self.failUnless("error" in message.tags)
def replace_link(self, match_obj): # for each unique link in the body, create a Message link to track the clicks ml = MessageLink.objects.create( recipient_message=self.recipient_message, link=match_obj.group(0), token=hash_val([self.count, datetime.datetime.now()])) self.count += 1 return "http://%s%s" % (Site.objects.get_current().domain, reverse("message_click", args=[ml.token]))
def test_invalid_email_list(self): self.client.login(username="******", password="******") response = self.client.post(self.event_guests_invite_url, {"emails": "[email protected] [email protected]", "note": "", "rsvp_notification": "", "copy_me": "", "signature": hash_val((self.event_content_type, self.event.pk,)),}, follow=True) self.failUnlessEqual(response.template[0].name, "events/guests_invite.html") errors = response.context["form"].errors self.failUnlessEqual(len(errors), 1) self.failUnless("emails" in errors)
def render_message(self, content_object, email, user_object, extra_params=None): recipient_message = RecipientMessage.objects.create(message=self, recipient=email, token=hash_val([email, datetime.datetime.now()])) domain = Site.objects.get_current().domain params = {"content_object": content_object, "domain": domain, "recipient": user_object if user_object else email } if extra_params: params.update(extra_params) extra_headers = self.extra_headers(content_object, user_object) from_email = None if extra_headers: from_email = extra_headers.pop('From', None) context = template.Context(params) # decide what language to try to use for localized emails language = None subject = self.subject body = self.body if user_object: if hasattr(user_object, 'user') and user_object.user is not None: _user = user_object.user else: _user = user_object if _user and hasattr(_user, 'get_profile'): _profile = _user.get_profile() language = _profile.language if language is not None: from rah_locale.models import TranslatedMessage try: _localized = TranslatedMessage.objects.get( message=self, language=language) except TranslatedMessage.DoesNotExist: pass else: subject = _localized.subject body = _localized.body # render the body and subject template with the given, template subject = template.Template(subject).render(context) subject = html_unescape(subject) body = template.Template(body).render(context) replacer = LinkReplacer(recipient_message=recipient_message) body = re.sub(URL_REGEX, replacer.replace_link, body) open_link = '<img src="http://%s%s"></img>' % (domain, reverse("message_open", args=[recipient_message.token])) # insert an open tracking image into the body body += open_link msg = EmailMessage(subject, body, from_email=from_email, to=[email], headers=extra_headers) msg.content_subtype = "html" return msg
def test_valid_default_invite(self): self.client.login(username="******", password="******") response = self.client.post(self.url, {"emails": "*****@*****.**", "content_type": "", "object_pk": "", "signature": hash_val((self.post_content_type, self.post.pk,)), "token": "81yuksdfkq2ro2i", "next": "/login/"}, follow=True) email = mail.outbox.pop() self.failUnlessEqual(email.to, ["*****@*****.**"]) self.failUnlessEqual(len(email.subject), 35) self.failUnlessEqual(response.template[0].name, "registration/login.html") message = iter(response.context["messages"]).next() self.failUnless("success" in message.tags)
def test_duplicate_email(self): self.client.login(username="******", password="******") self.failUnlessEqual(self.event.guest_set.all().count(), 7) response = self.client.post(self.event_guests_invite_url, {"emails": "*****@*****.**", "note": "", "content_type": self.event_content_type.pk, "object_pk": self.event.pk, "signature": hash_val((self.event_content_type, self.event.pk,)),}, follow=True) self.failUnlessEqual(response.template[0].name, "events/detail.html") email = mail.outbox.pop() self.failUnlessEqual(email.to, ["*****@*****.**"]) self.failUnlessEqual(len(email.subject), 35) event = response.context["event"] guests = event.guest_set.all() self.failUnlessEqual(len(guests), 7)
def clean(self): if "content_type" in self.cleaned_data and "object_pk" in self.cleaned_data and "signature" in self.cleaned_data: content_type = self.cleaned_data["content_type"] object_pk = self.cleaned_data["object_pk"] if content_type or object_pk: try: target = content_type.get_object_for_this_type(pk=object_pk) except ObjectDoesNotExist: raise forms.ValidationError("No object found matching %s" % object_pk) except ValueError: raise forms.ValidationError("Invalid parameters %s, %s" % (content_type, object_pk)) if hash_val((content_type, object_pk,)) != self.cleaned_data["signature"]: raise forms.ValidationError("Signature has been currupted") return self.cleaned_data
def test_multiple_invite(self): self.client.login(username="******", password="******") self.failUnlessEqual(self.event.guest_set.all().count(), 7) response = self.client.post(self.event_guests_invite_url, {"emails": "[email protected],[email protected]", "note": "", "content_type": self.event_content_type.pk, "object_pk": self.event.pk, "signature": hash_val((self.event_content_type, self.event.pk,)),}, follow=True) self.failUnlessEqual(response.template[0].name, "events/detail.html") email = mail.outbox.pop() self.failUnlessEqual(email.to, ["*****@*****.**"]) self.failUnlessEqual(len(email.subject), 35) email = mail.outbox.pop() self.failUnlessEqual(email.to, ["*****@*****.**"]) self.failUnlessEqual(len(email.subject), 35) event = response.context["event"] guests = event.guest_set.all() self.failUnlessEqual(len(guests), 9) jon = guests[7] self.failUnlessEqual(jon.contributor.first_name, "") self.failUnlessEqual(jon.contributor.email, "*****@*****.**") eric = guests[8] self.failUnlessEqual(eric.contributor.first_name, "") self.failUnlessEqual(eric.contributor.email, "*****@*****.**")
def setUp(self): self.group_user = User.objects.create_user(username="******", email="*****@*****.**", password="******") self.group_manager = User.objects.create_user(username="******", email="*****@*****.**", password="******") self.group = Group.objects.get(slug="yankees") GroupUsers.objects.create(group=self.group, user=self.group_user) GroupUsers.objects.create(group=self.group, user=self.group_manager, is_manager=True) from utils import hash_val self.discs = { 'd1': {"subject": "tc1 subject", "body": "tc1 body"}, 'd2': {"subject": "tc2 subject", "body": "tc2 body", "parent_id": 1, "parent_id_sig": hash_val(1)} } self.urls = { 'group_disc_create': reverse("group_disc_create", kwargs={"group_slug": self.group.slug}), 'group_disc_list': reverse("group_disc_list", kwargs={"group_slug": self.group.slug}), 'd1': reverse("group_disc_detail", kwargs={"group_slug": self.group.slug, "disc_id": 1}), 'd1_remove': reverse("group_disc_remove", kwargs={"group_slug": self.group.slug, "disc_id": 1}), 'd1_approve': reverse("group_disc_approve", kwargs={"group_slug": self.group.slug, "disc_id": 1}), 'd2': reverse("group_disc_detail", kwargs={"group_slug": self.group.slug, "disc_id": 2}), 'd2_remove': reverse("group_disc_remove", kwargs={"group_slug": self.group.slug, "disc_id": 2}), }
def replace_link(self, match_obj): # for each unique link in the body, create a Message link to track the clicks ml = MessageLink.objects.create(recipient_message=self.recipient_message, link=match_obj.group(0), token=hash_val([self.count, datetime.datetime.now()])) self.count += 1 return "http://%s%s" % (Site.objects.get_current().domain, reverse("message_click", args=[ml.token]))
def __init__(self, *args, **kwargs): form = super(InviteForm, self).__init__(*args, **kwargs) self.fields["signature"].initial = hash_val((self.instance.content_type, self.instance.object_pk,))
def __init__(self, *args, **kwargs): super(DiscussionCreateForm, self).__init__(*args, **kwargs) # If a parent_id was passed in, sign it if "parent_id" in self.initial: self.fields["parent_id_sig"].initial = hash_val(self.initial.get("parent_id")) self.fields["subject"].widget = forms.HiddenInput()