Esempio n. 1
0
class ValidationTest(unittest.TestCase):
    def setUp(self):
        token = "12345"
        self.validator = RequestValidator(token)

        self.uri = "https://mycompany.com/myapp.php?foo=1&bar=2"
        self.params = {
            "CallSid": "CA1234567890ABCDE",
            "Digits": "1234",
            "From": "+14158675309",
            "To": "+18005551212",
            "Caller": "+14158675309",
        }
        self.expected = "RSOYDt4T1cUTdK1PDd93/VVr8B8="
        self.body = "{\"property\": \"value\", \"boolean\": true}"
        self.bodyHash = "Ch/3Y02as7ldtcmi3+lBbkFQKyg6gMfPGWMmMvluZiA="
        self.encodedBodyHash = self.bodyHash.replace("+", "%2B").replace(
            "=", "%3D")
        self.uriWithBody = self.uri + "&bodySHA256=" + self.encodedBodyHash

    def test_compute_signature_bytecode(self):
        expected = b(self.expected)
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=False)
        assert_equal(signature, expected)

    def test_compute_signature_unicode(self):
        expected = u(self.expected)
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=True)
        assert_equal(signature, expected)

    def test_compute_hash_bytecode(self):
        expected = b(self.bodyHash)
        body_hash = self.validator.compute_hash(self.body, utf=False)

        assert_equal(expected, body_hash)

    def test_compute_hash_unicode(self):
        expected = u(self.bodyHash)
        body_hash = self.validator.compute_hash(self.body, utf=True)

        assert_equal(expected, body_hash)

    def test_validation(self):
        assert_true(
            self.validator.validate(self.uri, self.params, self.expected))

    def test_validation_removes_port_on_https(self):
        uri = self.uri.replace(".com", ".com:1234")
        assert_true(self.validator.validate(uri, self.params, self.expected))

    def test_validation_of_body_succeeds(self):
        uri = self.uriWithBody
        is_valid = self.validator.validate(uri, self.body,
                                           "afcFvPLPYT8mg/JyIVkdnqQKa2s=")
        assert_true(is_valid)
Esempio n. 2
0
class ValidationTest(unittest.TestCase):

    def setUp(self):
        token = "1c892n40nd03kdnc0112slzkl3091j20"
        self.validator = RequestValidator(token)

        self.uri = "http://www.postbin.org/1ed898x"
        self.params = {
            "AccountSid": "AC9a9f9392lad99kla0sklakjs90j092j3",
            "ApiVersion": "2010-04-01",
            "CallSid": "CAd800bb12c0426a7ea4230e492fef2a4f",
            "CallStatus": "ringing",
            "Called": "+15306384866",
            "CalledCity": "OAKLAND",
            "CalledCountry": "US",
            "CalledState": "CA",
            "CalledZip": "94612",
            "Caller": "+15306666666",
            "CallerCity": "SOUTH LAKE TAHOE",
            "CallerCountry": "US",
            "CallerName": "CA Wireless Call",
            "CallerState": "CA",
            "CallerZip": "89449",
            "Direction": "inbound",
            "From": "+15306666666",
            "FromCity": "SOUTH LAKE TAHOE",
            "FromCountry": "US",
            "FromState": "CA",
            "FromZip": "89449",
            "To": "+15306384866",
            "ToCity": "OAKLAND",
            "ToCountry": "US",
            "ToState": "CA",
            "ToZip": "94612",
        }

    def test_compute_signature_bytecode(self):
        expected = b("fF+xx6dTinOaCdZ0aIeNkHr/ZAA=")
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=False)
        assert_equal(signature, expected)

    def test_compute_signature_unicode(self):
        expected = u("fF+xx6dTinOaCdZ0aIeNkHr/ZAA=")
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=True)
        assert_equal(signature, expected)

    def test_validation(self):
        expected = "fF+xx6dTinOaCdZ0aIeNkHr/ZAA="
        assert_true(self.validator.validate(self.uri, self.params, expected))

    def test_validation_removes_port_on_https(self):
        self.uri = "https://www.postbin.org:1234/1ed898x"
        expected = "Y7MeICc5ECftd1G11Fc8qoxAn0A="
        assert_true(self.validator.validate(self.uri, self.params, expected))
class ValidationTest(unittest.TestCase):

    def setUp(self):
        token = "12345"
        self.validator = RequestValidator(token)

        self.uri = "https://mycompany.com/myapp.php?foo=1&bar=2"
        self.params = {
            "CallSid": "CA1234567890ABCDE",
            "Digits": "1234",
            "From": "+14158675309",
            "To": "+18005551212",
            "Caller": "+14158675309",
        }
        self.expected = "RSOYDt4T1cUTdK1PDd93/VVr8B8="
        self.body = "{\"property\": \"value\", \"boolean\": true}"
        self.bodyHash = "Ch/3Y02as7ldtcmi3+lBbkFQKyg6gMfPGWMmMvluZiA="
        self.encodedBodyHash = self.bodyHash.replace("+", "%2B").replace("=", "%3D")
        self.uriWithBody = self.uri + "&bodySHA256=" + self.encodedBodyHash

    def test_compute_signature_bytecode(self):
        expected = b(self.expected)
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=False)
        assert_equal(signature, expected)

    def test_compute_signature_unicode(self):
        expected = u(self.expected)
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=True)
        assert_equal(signature, expected)

    def test_compute_hash_bytecode(self):
        expected = b(self.bodyHash)
        body_hash = self.validator.compute_hash(self.body, utf=False)

        assert_equal(expected, body_hash)

    def test_compute_hash_unicode(self):
        expected = u(self.bodyHash)
        body_hash = self.validator.compute_hash(self.body, utf=True)

        assert_equal(expected, body_hash)

    def test_validation(self):
        assert_true(self.validator.validate(self.uri, self.params, self.expected))

    def test_validation_removes_port_on_https(self):
        uri = self.uri.replace(".com", ".com:1234")
        assert_true(self.validator.validate(uri, self.params, self.expected))

    def test_validation_of_body_succeeds(self):
        uri = self.uriWithBody
        is_valid = self.validator.validate(uri, self.body, "afcFvPLPYT8mg/JyIVkdnqQKa2s=")
        assert_true(is_valid)
class ValidationTest(unittest.TestCase):
    def setUp(self):
        token = "1c892n40nd03kdnc0112slzkl3091j20"
        self.validator = RequestValidator(token)

        self.uri = "http://www.postbin.org/1ed898x"
        self.params = {
            "AccountSid": "AC9a9f9392lad99kla0sklakjs90j092j3",
            "ApiVersion": "2010-04-01",
            "CallSid": "CAd800bb12c0426a7ea4230e492fef2a4f",
            "CallStatus": "ringing",
            "Called": "+15306384866",
            "CalledCity": "OAKLAND",
            "CalledCountry": "US",
            "CalledState": "CA",
            "CalledZip": "94612",
            "Caller": "+15306666666",
            "CallerCity": "SOUTH LAKE TAHOE",
            "CallerCountry": "US",
            "CallerName": "CA Wireless Call",
            "CallerState": "CA",
            "CallerZip": "89449",
            "Direction": "inbound",
            "From": "+15306666666",
            "FromCity": "SOUTH LAKE TAHOE",
            "FromCountry": "US",
            "FromState": "CA",
            "FromZip": "89449",
            "To": "+15306384866",
            "ToCity": "OAKLAND",
            "ToCountry": "US",
            "ToState": "CA",
            "ToZip": "94612",
        }

    def test_compute_signature_bytecode(self):
        expected = b("fF+xx6dTinOaCdZ0aIeNkHr/ZAA=")
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=False)
        assert_equal(signature, expected)

    def test_compute_signature_unicode(self):
        expected = u("fF+xx6dTinOaCdZ0aIeNkHr/ZAA=")
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=True)
        assert_equal(signature, expected)

    def test_validation(self):
        expected = "fF+xx6dTinOaCdZ0aIeNkHr/ZAA="
        assert_true(self.validator.validate(self.uri, self.params, expected))

    def test_validation_removes_port_on_https(self):
        self.uri = "https://www.postbin.org:1234/1ed898x"
        expected = "Y7MeICc5ECftd1G11Fc8qoxAn0A="
        assert_true(self.validator.validate(self.uri, self.params, expected))
Esempio n. 5
0
class ValidationTest(unittest.TestCase):
    def setUp(self):
        token = "12345"
        self.validator = RequestValidator(token)

        self.uri = "https://mycompany.com/myapp.php?foo=1&bar=2"
        self.params = {
            "CallSid": "CA1234567890ABCDE",
            "Digits": "1234",
            "From": "+14158675309",
            "To": "+18005551212",
            "Caller": "+14158675309",
        }
        self.expected = "RSOYDt4T1cUTdK1PDd93/VVr8B8="
        self.body = "{\"property\": \"value\", \"boolean\": true}"
        self.bodyHash = "0a1ff7634d9ab3b95db5c9a2dfe9416e41502b283a80c7cf19632632f96e6620"
        self.uriWithBody = self.uri + "&bodySHA256=" + self.bodyHash

    def test_compute_signature_bytecode(self):
        expected = b(self.expected)
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=False)
        assert_equal(signature, expected)

    def test_compute_signature(self):
        expected = (self.expected)
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=True)
        assert_equal(signature, expected)

    def test_compute_hash_unicode(self):
        expected = u(self.bodyHash)
        body_hash = self.validator.compute_hash(self.body)

        assert_equal(expected, body_hash)

    def test_validation(self):
        assert_true(
            self.validator.validate(self.uri, self.params, self.expected))

    def test_validation_removes_port_on_https(self):
        uri = self.uri.replace(".com", ".com:1234")
        assert_true(self.validator.validate(uri, self.params, self.expected))

    def test_validation_of_body_succeeds(self):
        uri = self.uriWithBody
        is_valid = self.validator.validate(uri, self.body,
                                           "a9nBmqA0ju/hNViExpshrM61xv4=")
        assert_true(is_valid)
class ValidationTest(unittest.TestCase):

    def setUp(self):
        token = "12345"
        self.validator = RequestValidator(token)

        self.uri = "https://mycompany.com/myapp.php?foo=1&bar=2"
        self.params = {
            "CallSid": "CA1234567890ABCDE",
            "Digits": "1234",
            "From": "+14158675309",
            "To": "+18005551212",
            "Caller": "+14158675309",
        }
        self.expected = "RSOYDt4T1cUTdK1PDd93/VVr8B8="
        self.body = "{\"property\": \"value\", \"boolean\": true}"
        self.bodyHash = "0a1ff7634d9ab3b95db5c9a2dfe9416e41502b283a80c7cf19632632f96e6620"
        self.uriWithBody = self.uri + "&bodySHA256=" + self.bodyHash

    def test_compute_signature_bytecode(self):
        expected = b(self.expected)
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=False)
        assert_equal(signature, expected)

    def test_compute_signature(self):
        expected = (self.expected)
        signature = self.validator.compute_signature(self.uri,
                                                     self.params,
                                                     utf=True)
        assert_equal(signature, expected)

    def test_compute_hash_unicode(self):
        expected = u(self.bodyHash)
        body_hash = self.validator.compute_hash(self.body)

        assert_equal(expected, body_hash)

    def test_validation(self):
        assert_true(self.validator.validate(self.uri, self.params, self.expected))

    def test_validation_removes_port_on_https(self):
        uri = self.uri.replace(".com", ".com:1234")
        assert_true(self.validator.validate(uri, self.params, self.expected))

    def test_validation_of_body_succeeds(self):
        uri = self.uriWithBody
        is_valid = self.validator.validate(uri, self.body, "a9nBmqA0ju/hNViExpshrM61xv4=")
        assert_true(is_valid)
Esempio n. 7
0
class TwilioClient:
    def __init__(self, account_sid, auth_token):
        self.client = Client(account_sid, auth_token)
        self.request_validator = RequestValidator(auth_token)

    def compute_signature(self, uri, params):
        """proxy twilio.RequestValidator.compute_signature()"""
        return self.request_validator.compute_signature(uri, params)

    def validate_request(self, uri, params, signature):
        """proxy twilio.RequestValidator.validate()"""
        return self.request_validator.validate(uri, params, signature)

    def send_sms(self, to="", from_="", body=""):
        try:
            message = self.client.messages.create(
                to=to,
                from_=from_,
                body=body
            )
        except TwilioRestException:
            # TODO log this original exception for debugging
            raise TwilioClientException('Failed to send SMS')

        return message
Esempio n. 8
0
    def call(self,
             to,
             path="/voice/",
             from_="+15556667777",
             extra_params=None):
        params = {
            "CallSid": "CAtesting",
            "AccountSid": "ACxxxxx",
            "To": to,
            "From": from_,
            "Direction": "inbound",
            "FromCity": "BROOKLYN",
            "FromState": "NY",
            "FromCountry": "US",
            "FromZip": "55555"
        }

        if extra_params:
            for k, v in extra_params.items():
                params[k] = v

        HTTP_HOST = "example.com"
        validator = RequestValidator("yyyyyyyy")
        absolute_url = "http://{0}{1}".format(HTTP_HOST, path)
        signature = validator.compute_signature(absolute_url, params)

        return self.post(path,
                         params,
                         HTTP_X_TWILIO_SIGNATURE=signature,
                         HTTP_HOST=HTTP_HOST)
Esempio n. 9
0
def test_mms(client, contact_table, unit_table, mock_viper):
    # MMS messages in Australia are delivered to twilio as an SMS with
    # a link.
    # Below is a captured Telstra message, delivered via Twilio originating
    # from an Optus network mobile. I am unsure if they will all be in the
    # Telstra format.
    # Desired behaviour is to send an error SMS reply, do not send a page.
    telstra_mms = "You have a new MMS Picture or Video Message. To view your message, go to http://telstra.com/mmsview & enter User ID:61437867737 and Password:048jnd within 14 days"

    data = {'Body': telstra_mms, 'From': '123'}
    token = 'ABC'
    page.os.environ['TWILIO_AUTH_TOKEN'] = token
    validator = RequestValidator(token)
    sig = validator.compute_signature('http://localhost/', data)
    headers = {'X-TWILIO-SIGNATURE': sig}

    contact_table.put_item({
        'phone_number': {
            'S': '123'
        },
        'unit': {
            'S': 'TestData'
        },
        'member_id': {
            'N': 12345
        },
    })
    unit_table.put_item({'name': {'S': 'TestData'}, 'capcode': {'N': 1111}})

    bounce = client.post('/', data=data, headers=headers)
    assert (bounce.status_code == 200)
    assert (b"This service does not support MMS messages" in bounce.data)
    assert (mock_viper.mock_calls == [])
Esempio n. 10
0
def test_twilio_check(client):
    token = "AABBCCDD"
    data = {'Body': 'Twilio check body', 'From': '1234567890'}

    # Monkey patch the app's environment hash
    page.os.environ['TWILIO_AUTH_TOKEN'] = token

    validator = RequestValidator(token)
    sig = validator.compute_signature('http://localhost/', data)

    # TODO: Incoming number is +61...

    # Good request
    # Response will be an TWIML error response
    # Don't care, Twilio auth check passed
    good_headers = {'X-TWILIO-SIGNATURE': sig}
    good = client.post('/', data=data, headers=good_headers)
    assert (good.status_code == 200)

    # Request missing headers
    missing = client.post('/', data=data, headers={})
    assert (missing.status_code == 403)

    # Request with bad header
    bad_headers = {'X-TWILIO-SIGNATURE': 'RABBITHAT'}
    bad = client.post('/', data=data, headers=bad_headers)
    assert (bad.status_code == 403)

    # Request signature with bad url
    sig = validator.compute_signature('http://otherhost/', data)
    bad_url_headers = {'X-TWILIO-SIGNATURE': sig}
    bad_url = client.post('/', data=data, headers=bad_url_headers)
    assert (bad_url.status_code == 403)

    # Request signature with bad data
    extra_data = data.copy()
    extra_data['flubble'] = 'wubble'
    sig = validator.compute_signature('http://localhost/', extra_data)
    bad_data_headers = {'X-TWILIO-SIGNATURE': sig}
    bad_data = client.post('/', data=data, headers=bad_data_headers)
    assert (bad_data.status_code == 403)

    # Request signature with messed up token
    page.os.environ[
        'TWILIO_AUTH_TOKEN'] = "I laugh at them because they're all the same"
    bad_token = client.post('/', data=data, headers=good_headers)
    assert (bad_token.status_code == 403)
Esempio n. 11
0
class ValidationTest(unittest.TestCase):
    def setUp(self):
        token = "12345"
        self.validator = RequestValidator(token)

        self.uri = "https://mycompany.com/myapp.php?foo=1&bar=2"
        self.params = {
            "CallSid": "CA1234567890ABCDE",
            "Digits": "1234",
            "From": "+14158675309",
            "To": "+18005551212",
            "Caller": "+14158675309",
        }
        self.expected = "RSOYDt4T1cUTdK1PDd93/VVr8B8="
        self.body = "{\"property\": \"value\", \"boolean\": true}"
        self.bodyHash = "0a1ff7634d9ab3b95db5c9a2dfe9416e41502b283a80c7cf19632632f96e6620"
        self.uriWithBody = self.uri + "&bodySHA256=" + self.bodyHash

    def test_compute_signature(self):
        expected = (self.expected)
        signature = self.validator.compute_signature(self.uri, self.params)
        assert_equal(signature, expected)

    def test_compute_hash_unicode(self):
        expected = self.bodyHash
        body_hash = self.validator.compute_hash(self.body)

        assert_equal(expected, body_hash)

    def test_validation(self):
        assert_true(
            self.validator.validate(self.uri, self.params, self.expected))

    def test_validation_removes_port_on_https(self):
        uri = self.uri.replace(".com", ".com:1234")
        assert_true(self.validator.validate(uri, self.params, self.expected))

    def test_validation_removes_port_on_http(self):
        expected = "Zmvh+3yNM1Phv2jhDCwEM3q5ebU="  # hash of http uri with port 1234
        uri = self.uri.replace(".com", ".com:1234").replace("https", "http")
        assert_true(self.validator.validate(uri, self.params, expected))

    def test_validation_adds_port_on_https(self):
        expected = "kvajT1Ptam85bY51eRf/AJRuM3w="  # hash of uri with port 443
        assert_true(self.validator.validate(self.uri, self.params, expected))

    def test_validation_adds_port_on_http(self):
        uri = self.uri.replace("https", "http")
        expected = "0ZXoZLH/DfblKGATFgpif+LLRf4="  # hash of uri with port 80
        assert_true(self.validator.validate(uri, self.params, expected))

    def test_validation_of_body_succeeds(self):
        uri = self.uriWithBody
        is_valid = self.validator.validate(uri, self.body,
                                           "a9nBmqA0ju/hNViExpshrM61xv4=")
        assert_true(is_valid)
Esempio n. 12
0
    def lookup(self, to, from_, params, path="/lookup/", extra_params=None):
        if extra_params:
            for k, v in extra_params.items():
                params[k] = v

        HTTP_HOST = "example.com"
        validator = RequestValidator("yyyyyyyy")
        absolute_url = "http://{0}{1}".format(HTTP_HOST, path)
        signature = validator.compute_signature(absolute_url, params)
        return self.post(path,
                         params,
                         HTTP_X_TWILIO_SIGNATURE=signature,
                         HTTP_HOST=HTTP_HOST)
Esempio n. 13
0
def test_db_lookups(client, contact_table, unit_table, mock_viper):
    data = {'Body': 'Twilio check body', 'From': '1234567890'}
    token = "AABBCCDD"
    page.os.environ['TWILIO_AUTH_TOKEN'] = token
    validator = RequestValidator(token)
    sig = validator.compute_signature('http://localhost/', data)
    headers = {'X-TWILIO-SIGNATURE': sig}

    not_auth = client.post('/', data=data, headers=headers)
    assert (not_auth.status_code == 200)
    assert (b"You are not authorised to use this service" in not_auth.data)

    contact_table.put_item({
        'phone_number': {
            'S': '1234567890'
        },
        'unit': {
            'S': 'TestData'
        },
        'name': {
            'S': 'John Smith'
        },
        'member_id': {
            'N': 12345
        },
        'permissions': {
            'S': '{}'
        }
    })

    not_unit = client.post('/', data=data, headers=headers)
    assert (not_unit.status_code == 200)
    assert (b"You are not authorised to use this service" not in not_unit.data)
    assert (b"Error retrieving unit details" in not_unit.data)
    assert (mock_viper.mock_calls == [])

    unit_table.put_item({'name': {'S': 'TestData'}, 'capcode': {'N': 1111}})

    success = client.post('/', data=data, headers=headers)
    assert (success.status_code == 204)
    assert (b"You are not authorised to use this service" not in success.data)
    assert (b"Error retrieving unit details" not in success.data)
    assert (len(success.data) == 0)

    expected_calls = [
        call(ses_id=None, ses_password=None),
        call().send(1111, data['Body'])
    ]
    assert (mock_viper.mock_calls == expected_calls)
Esempio n. 14
0
class TwilioMuxer:
    def __init__(self, twilio_auth_token: str, muxer_url: str, config: Config):
        self.validator = RequestValidator(twilio_auth_token)
        self.muxer_url = muxer_url
        self.config = config

    def mux_request(
            self, request_body: str,
            request_headers: Dict[str,
                                  str]) -> Tuple[int, str, Dict[str, str]]:
        parsed_body = dict(parse_qsl(request_body, keep_blank_values=True))

        request_valid = self.validator.validate(
            self.muxer_url,
            parsed_body,
            request_headers.get("x-twilio-signature",
                                request_headers.get("X-Twilio-Signature")),
        )

        if not request_valid:
            raise RuntimeError(f"Invalid Twilio signature")

        request_body_normalized = " ".join(
            parsed_body.get("Body", "").translate(
                str.maketrans("", "",
                              string.punctuation)).strip().lower().split())

        for keyword, config in self.config.keywords.items():
            if keyword == request_body_normalized:
                print(f"Normalized {request_body_normalized} -> {keyword}")
            elif config.alternates and request_body_normalized in config.alternates:
                print(
                    f"Normalized alternate {request_body_normalized} -> {keyword}"
                )
            else:
                continue
            request_body_normalized = keyword
            # clean up downstream request too
            parsed_body["Body"] = keyword
            break

        request_config = self.config.keywords.get(request_body_normalized,
                                                  self.config.default)

        def make_downstream_request(url: str) -> Optional[requests.Response]:
            downstream_headers = {
                k: v
                for k, v in request_headers.items()
                if k.lower() in PRESERVE_HEADERS
            }

            downstream_headers[
                "X-Twilio-Signature"] = self.validator.compute_signature(
                    url, parsed_body)

            try:
                result = requests.post(url,
                                       data=parsed_body,
                                       headers=downstream_headers)
            except Exception as e:
                logging.exception(f"Request failed to downstream {url}")
                sentry_sdk.capture_exception(e)
                return None

            try:
                result.raise_for_status()
            except Exception as e:
                logging.exception(
                    f"Request to downstream {url} return status code {result.status_code}"
                )
                sentry_sdk.capture_exception(e)

            # We return result whether or not raise_for_status() errored -- we're
            # just doing raise_for_status so we can capture errors; we always want
            # to return the result
            return result

        print(f"Making requests to downstreams: {request_config.downstreams}")
        with concurrent.futures.ThreadPoolExecutor() as executor:
            results = list(
                executor.map(make_downstream_request,
                             request_config.downstreams))

        print(f"Downstream responses: {results}")
        print(f"Taking result from responder: {request_config.responder}")

        if request_config.responder is None:
            return 200, "<Response></Response>", {
                "Content-Type": "application/xml"
            }

        result = results[request_config.responder]
        if result is None:
            return 500, "<Response></Response>", {
                "Content-Type": "application/xml"
            }

        return (
            result.status_code,
            result.text,
            {
                "Content-Type":
                result.headers.get("Content-Type", "application/xml")
            },
        )