Source code for helpscout.web_hook

# -*- coding: utf-8 -*-
# Copyright 2017-TODAY LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).

__all__ = [
    'HelpScoutWebHook',
    'WebHookEvent',
]


import hmac
import json
import properties

from base64 import b64encode
from hashlib import sha1
from six import string_types

from ..models import Conversation, Customer, Rating, WebHook, WebHookEvent

from ..exceptions import HelpScoutSecurityException


[docs]class HelpScoutWebHook(WebHook): """This provides the ability to easily create & process web hook events. """ helpscout = properties.Instance( 'The authenticated HelpScout object. Used for create.', # This cannot be defined as a HelpScout object due to circular depends instance_class=object, )
[docs] def create(self): """Create the web hook on HelpScout.""" assert self.helpscout return self.helpscout.WebHook.create(self)
[docs] def receive(self, event_type, signature, data_str): """Receive a web hook for the event and signature. Args: event_type (str): Name of the event that was received (from the request ``X-HelpScout-Event`` header). signature (str): The signature that was received, which serves as authentication (from the request ``X-HelpScout-Signature`` header). data_str (str): The raw data that was posted by HelpScout to the web hook. This must be the raw string, because if it is parsed with JSON it will lose its ordering and not pass signature validation. Raises: helpscout.exceptions.HelpScoutSecurityException: If an invalid signature is provided, and ``raise_if_invalid`` is ``True``. Returns: helpscout.web_hook.WebHookEvent: The authenticated web hook request. """ if not self.validate_signature(signature, data_str): raise HelpScoutSecurityException( 'The signature provided by this request was invalid.', ) return HelpScoutWebHookEvent( event_type=event_type, record=json.loads(data_str), )
[docs] def validate_signature(self, signature, data, encoding='utf8'): """Validate the signature for the provided data. Args: signature (str or bytes or bytearray): Signature that was provided for the request. data (str or bytes or bytearray): Data string to validate against the signature. encoding (str, optional): If a string was provided for ``data`` or ``signature``, this is the character encoding. Returns: bool: Whether the signature is valid for the provided data. """ if isinstance(data, string_types): data = bytearray(data, encoding) if isinstance(signature, string_types): signature = bytearray(signature, encoding) secret_key = bytearray(self.secret_key, 'utf8') hashed = hmac.new(secret_key, data, sha1) encoded = b64encode(hashed.digest()) return encoded.strip() == signature.strip()
class HelpScoutWebHookEvent(WebHookEvent): """This represents an authenticated web hook request. Note that this object is meant to represent an authenticated Web Hook, and therefore is completely naive of all things authentication. Any request authentication/validation should happen in the ``WebHook``. """ # Map the event prefixes to their corresponding data models. EVENT_PREFIX_TO_MODEL = { 'convo': Conversation, 'customer': Customer, 'satisfaction': Rating, } def __init__(self, *args, **kwargs): """Parse raw record data if required. Args: record (dict or BaseModel): The record data that was received for the request. If it is a ``dict``, the data will be parsed using the proper model's ``from_api`` method. """ if isinstance(kwargs.get('record'), dict): prefix, _ = kwargs['event_type'].split('.', 1) model = self.EVENT_PREFIX_TO_MODEL[prefix] kwargs['record'] = model.from_api(**kwargs['record']) super(WebHookEvent, self).__init__(*args, **kwargs)