Repository: getsentry/sentry

Issue:
Error processing 'rule_notify' on 'QuotaMailPlugin': 'int' object has no attribute 'lower'
https://beta.getsentry.com/sentry/sentry/group/56738068/

```
AttributeError: 'int' object has no attribute 'lower'
(11 additional frame(s) were not displayed)
...
  File "sentry/interfaces/user.py", line 99, in to_email_html
    return render_to_string('sentry/partial/interfaces/user_email.html', context)
  File "sentry/web/helpers.py", line 122, in render_to_string
    return loader.render_to_string(template, context)
  File "templatetag_sugar/node.py", line 19, in render
    return self.function(context, *args, **kwargs)
  File "sentry/templatetags/sentry_helpers.py", line 314, in gravatar_url
    return get_gravatar_url(email, size, default)
  File "sentry/utils/avatar.py", line 18, in get_gravatar_url
    hashlib.md5(email.lower()).hexdigest())

Stacktrace (most recent call last):

Error processing 'rule_notify' on 'QuotaMailPlugin': 'int' object has no attribute 'lower'

File "sentry/partial/interfaces/user_email.html", line 7

  <tr>
    {% if user_email %}
      <td style="width:84px;padding-top:10px;vertical-align:top">
        <img src="{% gravatar_url user_email size 64 %}">
      </td>
    {% endif %}
    <td>
```



Discussion:

Resolving patch:
diff --git a/src/sentry/interfaces/user.py b/src/sentry/interfaces/user.py
--- a/src/sentry/interfaces/user.py
+++ b/src/sentry/interfaces/user.py
@@ -15,6 +15,19 @@
 from ipaddr import IPAddress
 
 
+def validate_email(value, required=True):
+    if not required and not value:
+        return
+
+    if not isinstance(value, basestring):
+        raise TypeError('object of type %r is not an email address' % type(value).__name__)
+
+    # safe to assume an email address at least has a @ in it.
+    if '@' not in value:
+        raise ValueError('malformed email address')
+    return value
+
+
 def validate_ip(value, required=True):
     if not required and not value:
         return
@@ -51,7 +64,7 @@ def to_python(cls, data):
 
         kwargs = {
             'id': trim(data.pop('id', None), 128),
-            'email': trim(data.pop('email', None), 128),
+            'email': trim(validate_email(data.pop('email', None), False), 128),
             'username': trim(data.pop('username', None), 128),
             'ip_address': validate_ip(data.pop('ip_address', None), False),
         }


Test patch:
diff --git a/tests/sentry/interfaces/test_user.py b/tests/sentry/interfaces/test_user.py
--- a/tests/sentry/interfaces/test_user.py
+++ b/tests/sentry/interfaces/test_user.py
@@ -33,6 +33,23 @@ def test_serialize_behavior(self):
             'data': {'favorite_color': 'brown'}
         }
 
+    def test_invalid_ip_address(self):
+        with self.assertRaises(Exception):
+            User.to_python(dict(
+                ip_address='abc',
+            ))
+
+    def test_invalid_email_address(self):
+        with self.assertRaises(Exception):
+            User.to_python(dict(
+                email=1,
+            ))
+
+        with self.assertRaises(Exception):
+            User.to_python(dict(
+                email='foo',
+            ))
+
     def test_serialize_unserialize_behavior(self):
         result = type(self.interface).to_python(self.interface.to_json())
         assert result.to_json() == self.interface.to_json()