Intercom: Add support for HMAC authentication of identified users

Documentation:
https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product
This commit is contained in:
Pi Delport 2018-08-22 18:43:07 +02:00
parent 47cf9aac3e
commit 4b4f26f54e
3 changed files with 119 additions and 1 deletions

View file

@ -3,10 +3,14 @@ intercom.io template tags and filters.
"""
from __future__ import absolute_import
import hashlib
import hmac
import json
import time
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import disable_html, get_required_setting, \
@ -24,6 +28,34 @@ TRACKING_CODE = """
register = Library()
def _hashable_bytes(data): # type: (AnyStr) -> bytes
"""
Coerce strings to hashable bytes.
"""
if isinstance(data, bytes):
return data
elif isinstance(data, str):
return data.encode('ascii') # Fail on anything non-ASCII.
else:
raise TypeError(data)
def intercom_user_hash(data): # type: (AnyStr) -> Optional[str]
"""
Return a SHA-256 HMAC `user_hash` as expected by Intercom, if configured.
Return None if the `INTERCOM_HMAC_SECRET_KEY` setting is not configured.
"""
if getattr(settings, 'INTERCOM_HMAC_SECRET_KEY', None):
return hmac.new(
key=_hashable_bytes(settings.INTERCOM_HMAC_SECRET_KEY),
msg=_hashable_bytes(data),
digestmod=hashlib.sha256,
).hexdigest()
else:
return None
@register.tag
def intercom(parser, token):
"""
@ -73,6 +105,16 @@ class IntercomNode(Node):
else:
params['created_at'] = None
# Generate a user_hash HMAC to verify the user's identity, if configured.
# (If both user_id and email are present, the user_id field takes precedence.)
# See:
# https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product
user_hash_data = params.get('user_id', params.get('email')) # type: Optional[str]
if user_hash_data:
user_hash = intercom_user_hash(str(user_hash_data)) # type: Optional[str]
if user_hash is not None:
params.setdefault('user_hash', user_hash)
return params
def render(self, context):

View file

@ -9,7 +9,7 @@ from django.http import HttpRequest
from django.template import Context
from django.test.utils import override_settings
from analytical.templatetags.intercom import IntercomNode
from analytical.templatetags.intercom import IntercomNode, intercom_user_hash
from analytical.tests.utils import TagTestCase
from analytical.utils import AnalyticalException
@ -103,6 +103,65 @@ class IntercomTagTestCase(TagTestCase):
}))
self.assertTrue('"email": "explicit"' in r, r)
@override_settings(INTERCOM_HMAC_SECRET_KEY='secret')
def test_user_hash__without_user_details(self):
"""
No `user_hash` without `user_id` or `email`.
"""
attrs = IntercomNode()._get_custom_attrs(Context())
self.assertEqual({
'created_at': None,
}, attrs)
@override_settings(INTERCOM_HMAC_SECRET_KEY='secret')
def test_user_hash__with_user(self):
"""
'user_hash' of default `user_id`.
"""
user = User.objects.create(
email='test@example.com',
) # type: User
attrs = IntercomNode()._get_custom_attrs(Context({'user': user}))
self.assertEqual({
'created_at': int(user.date_joined.timestamp()),
'email': 'test@example.com',
'name': '',
'user_hash': intercom_user_hash(str(user.pk)),
'user_id': user.pk,
}, attrs)
@override_settings(INTERCOM_HMAC_SECRET_KEY='secret')
def test_user_hash__with_explicit_user_id(self):
"""
'user_hash' of context-provided `user_id`.
"""
attrs = IntercomNode()._get_custom_attrs(Context({
'intercom_email': 'test@example.com',
'intercom_user_id': '5',
}))
self.assertEqual({
'created_at': None,
'email': 'test@example.com',
# HMAC for user_id:
'user_hash': 'd3123a7052b42272d9b520235008c248a5aff3221cc0c530b754702ad91ab102',
'user_id': '5',
}, attrs)
@override_settings(INTERCOM_HMAC_SECRET_KEY='secret')
def test_user_hash__with_explicit_email(self):
"""
'user_hash' of context-provided `email`.
"""
attrs = IntercomNode()._get_custom_attrs(Context({
'intercom_email': 'test@example.com',
}))
self.assertEqual({
'created_at': None,
'email': 'test@example.com',
# HMAC for email:
'user_hash': '49e43229ee99dca2565241719b8341b04e71dd4de0628f991b5bea30a526e153',
}, attrs)
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
def test_render_internal_ip(self):
req = HttpRequest()

View file

@ -138,6 +138,23 @@ passed to Intercom automatically. See :ref:`identifying-visitors`.
.. _intercom-internal-ips:
Verifying identified users
--------------------------
Intercom supports HMAC authentication of users identified by user ID or email, in order to prevent impersonation.
For more information, see `Enable identity verification on your web product`_ in the Intercom documentation.
To enable this, configure your Intercom account's HMAC secret key::
INTERCOM_HMAC_SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXX'
(You can find this secret key under the "Identity verification" section of your Intercom account settings page.)
.. _`Enable identity verification on your web product`: https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product
Internal IP addresses
---------------------