From 73e82be4221764fb139934eb43aeb6d29cfb4715 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 09:43:31 -0600 Subject: [PATCH 001/242] Refactor authenticate --- tests.py | 33 ++++++++++++++++++--------------- toopher/__init__.py | 35 ++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/tests.py b/tests.py index c849a51..ce9cba0 100644 --- a/tests.py +++ b/tests.py @@ -2,6 +2,7 @@ import toopher import requests import unittest +import uuid import time import werkzeug.datastructures @@ -153,25 +154,26 @@ def fn(): def test_create_authentication_request(self): api = toopher.ToopherApi('key', 'secret') + pairing_id = str(uuid.uuid4()) api.client = HttpClientMock({ 'authentication_requests/initiate': (200, - '{"id":"1", "pending":false, "granted":true, "automated":false, "reason":"its a test", "terminal":{"id":"1", "name":"test terminal"}}' + '{"id":"' + pairing_id + '", "pending":false, "granted":true, "automated":false, "reason":"its a test", "terminal":{"id":"1", "name":"test terminal"}}' ) }) - auth_request = api.authenticate('1', 'test terminal') + auth_request = api.authenticate(pairing_id, 'test terminal') self.assertEqual(api.client.last_called_method, 'POST') - self.assertEqual(api.client.last_called_data['pairing_id'], '1') + self.assertEqual(api.client.last_called_data['pairing_id'], pairing_id) self.assertEqual(api.client.last_called_data['terminal_name'], 'test terminal') def fn(): self.assertEqual(api.client.last_called_data['test_param'], '42') self.assertRaises(KeyError, fn) - api.authenticate('pairing_id', 'terminal_name', 'action_name') + api.authenticate(pairing_id, 'terminal_name', 'action_name') last_called_data = api.client.last_called_data self.assertEqual(api.client.last_called_method, 'POST') - self.assertEqual(last_called_data['pairing_id'], 'pairing_id') + self.assertEqual(last_called_data['pairing_id'], pairing_id) self.assertEqual(last_called_data['terminal_name'], 'terminal_name') self.assertEqual(last_called_data['action_name'], 'action_name') @@ -211,14 +213,15 @@ def test_pass_arbitrary_parameters_on_pair(self): def test_pass_arbitrary_parameters_on_authenticate(self): api = toopher.ToopherApi('key', 'secret') + pairing_id = str(uuid.uuid4()) api.client = HttpClientMock({ 'authentication_requests/initiate': (200, - '{"id":"1", "pending":false, "granted":true, "automated":false, "reason":"its a test", "terminal":{"id":"1", "name":"test terminal"}}' + '{"id":"' + pairing_id + '", "pending":false, "granted":true, "automated":false, "reason":"its a test", "terminal":{"id":"1", "name":"test terminal"}}' ) }) - auth_request = api.authenticate('1', 'test terminal', test_param='42') + auth_request = api.authenticate(pairing_id, 'test terminal', test_param='42') self.assertEqual(api.client.last_called_method, 'POST') - self.assertEqual(api.client.last_called_data['pairing_id'], '1') + self.assertEqual(api.client.last_called_data['pairing_id'], pairing_id) self.assertEqual(api.client.last_called_data['terminal_name'], 'test terminal') self.assertEqual(api.client.last_called_data['test_param'], '42') @@ -313,7 +316,7 @@ def test_bad_response_raises_correct_error(self): 'lol if you think this is json')}) def fn(): - api.authenticate_by_user_name('user_name', 'terminal_name') + api.authenticate('user_name', 'terminal_name') self.assertRaises(toopher.ToopherApiError, fn) def test_unrecognized_error_still_raises_error(self): @@ -324,7 +327,7 @@ def test_unrecognized_error_still_raises_error(self): 'error_message': 'what'}))}) def fn(): - api.authenticate_by_user_name('user_name', 'terminal_name') + api.authenticate('user_name', 'terminal_name') self.assertRaises(toopher.ToopherApiError, fn) class ZeroStorageTests(unittest.TestCase): @@ -381,7 +384,7 @@ def test_disabled_user_raises_correct_error(self): 'error_message': 'disabled user'}))}) def fn(): - auth_request = api.authenticate_by_user_name('disabled user', 'terminal name') + auth_request = api.authenticate('disabled user', 'terminal name') self.assertRaises(toopher.UserDisabledError, fn) def test_unknown_user_raises_correct_error(self): @@ -392,7 +395,7 @@ def test_unknown_user_raises_correct_error(self): 'error_message': 'unknown user'}))}) def fn(): - auth_request = api.authenticate_by_user_name('unknown user', 'terminal name') + auth_request = api.authenticate('unknown user', 'terminal name') self.assertRaises(toopher.UserUnknownError, fn) def test_unknown_terminal_raises_correct_error(self): @@ -403,7 +406,7 @@ def test_unknown_terminal_raises_correct_error(self): 'error_message': 'unknown terminal'}))}) def fn(): - auth_request = api.authenticate_by_user_name('user', 'unknown terminal name') + auth_request = api.authenticate('user', 'unknown terminal name') self.assertRaises(toopher.TerminalUnknownError, fn) def test_deactivated_pairing_raises_correct_error(self): @@ -414,7 +417,7 @@ def test_deactivated_pairing_raises_correct_error(self): 'error_message': 'Pairing has been deactivated'}))}) def fn(): - auth_request = api.authenticate_by_user_name('user', 'terminal name') + auth_request = api.authenticate('user', 'terminal name') self.assertRaises(toopher.PairingDeactivatedError, fn) def test_unauthorized_pairing_raises_correct_error(self): @@ -425,7 +428,7 @@ def test_unauthorized_pairing_raises_correct_error(self): 'error_message': 'Pairing has not been authorized'}))}) def fn(): - auth_request = api.authenticate_by_user_name('user', 'terminal name') + auth_request = api.authenticate('user', 'terminal name') self.assertRaises(toopher.PairingDeactivatedError, fn) class ddict(dict): diff --git a/toopher/__init__.py b/toopher/__init__.py index 5afa6e3..373f9f7 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -4,6 +4,7 @@ import hashlib import hmac import base64 +import uuid import time import requests_oauthlib import sys @@ -162,18 +163,34 @@ def get_pairing_status(self, pairing_id): result = self._request(uri, "GET") return PairingStatus(result) - def authenticate(self, pairing_id, terminal_name, action_name=None, **kwargs): - uri = self.base_url + "/authentication_requests/initiate" - params = {'pairing_id': pairing_id, - 'terminal_name': terminal_name} + def authenticate(self, id_or_username, terminal_or_otp, action_name=None, **kwargs): + url = self.base_url + "/authentication_requests/initiate" + try: + uuid.UUID(id_or_username) + params = {'pairing_id': id_or_username, + 'terminal_name': terminal_or_otp} + except: + params = {'user_name': id_or_username, + 'terminal_name_extra': terminal_or_otp} if action_name: params['action_name'] = action_name - params.update(kwargs) - result = self._request(uri, "POST", params) + result = self._request(url, "POST", params) return AuthenticationStatus(result) + # def authenticate(self, pairing_id, terminal_name, action_name=None, **kwargs): + # uri = self.base_url + "/authentication_requests/initiate" + # params = {'pairing_id': pairing_id, + # 'terminal_name': terminal_name} + # if action_name: + # params['action_name'] = action_name + # + # params.update(kwargs) + # + # result = self._request(uri, "POST", params) + # return AuthenticationStatus(result) + def get_authentication_status(self, authentication_request_id): uri = self.base_url + "/authentication_requests/" + authentication_request_id @@ -186,9 +203,9 @@ def authenticate_with_otp(self, authentication_request_id, otp): result = self._request(uri, "POST", params) return AuthenticationStatus(result) - def authenticate_by_user_name(self, user_name, terminal_name_extra, action_name=None, **kwargs): - kwargs.update(user_name=user_name, terminal_name_extra=terminal_name_extra) - return self.authenticate('', '', action_name, **kwargs) + # def authenticate_by_user_name(self, user_name, terminal_name_extra, action_name=None, **kwargs): + # kwargs.update(user_name=user_name, terminal_name_extra=terminal_name_extra) + # return self.authenticate('', '', action_name, **kwargs) def create_user_terminal(self, user_name, terminal_name, requester_terminal_id): uri = self.base_url + '/user_terminals/create' From 1adfbca1771d091d73bfebc87e352cb89d27818b Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 09:45:33 -0600 Subject: [PATCH 002/242] Refactor pair --- tests.py | 16 ++++++------ toopher/__init__.py | 62 ++++++++++++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/tests.py b/tests.py index ce9cba0..d38631a 100644 --- a/tests.py +++ b/tests.py @@ -124,7 +124,7 @@ def test_create_pairing(self): '{"id":"1", "enabled":true, "user":{"id":"1","name":"some user"}}' ) }) - pairing = api.pair('awkward turtle', 'some user') + pairing = api.pair('some user', 'awkward turtle') self.assertEqual(api.client.last_called_method, 'POST') self.assertEqual(api.client.last_called_data['pairing_phrase'], 'awkward turtle') @@ -205,7 +205,7 @@ def test_pass_arbitrary_parameters_on_pair(self): '{"id":"1", "enabled":true, "user":{"id":"1","name":"some user"}}' ) }) - pairing = api.pair('awkward turtle', 'some user', test_param='42') + pairing = api.pair('some user', 'awkward turtle', test_param='42') self.assertEqual(api.client.last_called_method, 'POST') self.assertEqual(api.client.last_called_data['pairing_phrase'], 'awkward turtle') @@ -269,7 +269,7 @@ def test_pair_qr(self): 'enabled': True, 'user': {'id': 'id', 'name': 'name'}}))}) - api.pair_qr('user_name') + api.pair('user_name') self.assertEqual(api.client.last_called_method, 'POST') self.assertEqual(api.client.last_called_data['user_name'], 'user_name') @@ -281,18 +281,18 @@ def test_pair_sms(self): 'enabled': True, 'user': {'id': 'id', 'name': 'name'}}))}) - api.pair_sms('phone_number', 'user_name') + api.pair('user_name', '555-555-5555') last_called_data = api.client.last_called_data self.assertEqual(api.client.last_called_method, 'POST') - self.assertEqual(last_called_data['phone_number'], 'phone_number') + self.assertEqual(last_called_data['phone_number'], '555-555-5555') self.assertEqual(last_called_data['user_name'], 'user_name') - api.pair_sms('phone_number', 'user_name', 'phone_country') + api.pair('user_name', '555-555-5555', phone_country='1') last_called_data = api.client.last_called_data self.assertEqual(api.client.last_called_method, 'POST') - self.assertEqual(last_called_data['phone_number'], 'phone_number') + self.assertEqual(last_called_data['phone_number'], '555-555-5555') self.assertEqual(last_called_data['user_name'], 'user_name') - self.assertEqual(last_called_data['phone_country'], 'phone_country') + self.assertEqual(last_called_data['phone_country'], '1') def test_authenticate_with_otp(self): api = toopher.ToopherApi('key', 'secret') diff --git a/toopher/__init__.py b/toopher/__init__.py index 373f9f7..eff0821 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -129,33 +129,49 @@ def __init__(self, key, secret, api_url=None): base_url = api_url if api_url else DEFAULT_BASE_URL self.base_url = base_url.rstrip('/') - def pair(self, pairing_phrase, user_name, **kwargs): - uri = self.base_url + "/pairings/create" - params = {'pairing_phrase': pairing_phrase, - 'user_name': user_name} - + def pair(self, username, phrase_or_num=None, **kwargs): + params = {'user_name': username} params.update(kwargs) + if phrase_or_num: + if any(c.isdigit() for c in phrase_or_num): + url = self.base_url + "/pairings/create/sms" + params.update(phone_number=phrase_or_num) + else: + url = self.base_url + "/pairings/create" + params.update(pairing_phrase=phrase_or_num) + else: + url = self.base_url + "/pairings/create/qr" - result = self._request(uri, "POST", params) - return PairingStatus(result) - - def pair_qr(self, user_name, **kwargs): - uri = self.base_url + '/pairings/create/qr' - params = {'user_name': user_name} - params.update(kwargs) - result = self._request(uri, 'POST', params) + result = self._request(url, "POST", params) return PairingStatus(result) - def pair_sms(self, phone_number, user_name, phone_country=None): - uri = self.base_url + "/pairings/create/sms" - params = {'phone_number': phone_number, - 'user_name': user_name} - - if phone_country: - params['phone_country'] = phone_country - - result = self._request(uri, "POST", params) - return PairingStatus(result) + # def pair(self, pairing_phrase, user_name, **kwargs): + # uri = self.base_url + "/pairings/create" + # params = {'pairing_phrase': pairing_phrase, + # 'user_name': user_name} + # + # params.update(kwargs) + # + # result = self._request(uri, "POST", params) + # return PairingStatus(result) + # + # def pair_qr(self, user_name, **kwargs): + # uri = self.base_url + '/pairings/create/qr' + # params = {'user_name': user_name} + # params.update(kwargs) + # result = self._request(uri, 'POST', params) + # return PairingStatus(result) + # + # def pair_sms(self, phone_number, user_name, phone_country=None): + # uri = self.base_url + "/pairings/create/sms" + # params = {'phone_number': phone_number, + # 'user_name': user_name} + # + # if phone_country: + # params['phone_country'] = phone_country + # + # result = self._request(uri, "POST", params) + # return PairingStatus(result) def get_pairing_status(self, pairing_id): uri = self.base_url + "/pairings/" + pairing_id From de6b4ade3c5ebfcde906115482d607098277e24e Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 09:52:01 -0600 Subject: [PATCH 003/242] Refactor validate --- tests.py | 6 +++--- toopher/__init__.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests.py b/tests.py index d38631a..eb33d90 100644 --- a/tests.py +++ b/tests.py @@ -48,7 +48,7 @@ def test_validate_good_signature_is_successful(self): 'toopher_sig':'6d2c7GlQssGmeYYGpcf+V/kirOI=' } try: - self.iframe_api.validate(data, ToopherIframeTests.request_token) + self.iframe_api.validate_postback(data, ToopherIframeTests.request_token) except toopher.SignatureValidationError: self.fail() @@ -60,7 +60,7 @@ def test_arrays_get_flattened_for_validate(self): 'toopher_sig':[ '6d2c7GlQssGmeYYGpcf+V/kirOI=' ] } try: - self.iframe_api.validate(data, ToopherIframeTests.request_token) + self.iframe_api.validate_postback(data, ToopherIframeTests.request_token) except toopher.SignatureValidationError: self.fail() @@ -72,7 +72,7 @@ def test_immutable_dictionaries_get_copied_for_validate(self): ('toopher_sig', '6d2c7GlQssGmeYYGpcf+V/kirOI=') ]) try: - self.iframe_api.validate(data, ToopherIframeTests.request_token) + self.iframe_api.validate_postback(data, ToopherIframeTests.request_token) except toopher.SignatureValidationError: self.fail() diff --git a/toopher/__init__.py b/toopher/__init__.py index eff0821..709a7a5 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -73,7 +73,7 @@ def auth_uri(self, username, reset_email, action_name, automation_allowed, chall def login_uri(self, username, reset_email, request_token, **kwargs): return self.auth_uri(username, reset_email, 'Log In', True, False, request_token, 'None', DEFAULT_IFRAME_TTL, **kwargs) - def validate(self, data, request_token=None, ttl=DEFAULT_IFRAME_TTL): + def validate_postback(self, data, request_token=None): # make a mutable copy of the data data = dict(data) @@ -105,7 +105,7 @@ def validate(self, data, request_token=None, ttl=DEFAULT_IFRAME_TTL): if not signature_valid: raise SignatureValidationError("Computed signature does not match submitted signature: {0} vs {1}".format(computed_signature, maybe_sig)) - ttl_valid = int(time.time()) - int(data['timestamp']) < ttl + ttl_valid = int(time.time()) - int(data['timestamp']) < DEFAULT_IFRAME_TTL if not ttl_valid: raise SignatureValidationError("TTL expired") From 359cfc05428a2d8405425d27fc2055159755eea9 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 09:52:35 -0600 Subject: [PATCH 004/242] Refactor _signature --- toopher/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toopher/__init__.py b/toopher/__init__.py index 709a7a5..810b416 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -97,7 +97,7 @@ def validate_postback(self, data, request_token=None): del data['toopher_sig'] signature_valid = False try: - computed_signature =self.signature(data) + computed_signature =self._signature(data) signature_valid = maybe_sig == computed_signature except Exception, e: raise SignatureValidationError("Error while calculating signature", e) @@ -111,7 +111,7 @@ def validate_postback(self, data, request_token=None): return data - def signature(self, data): + def _signature(self, data): to_sign = urllib.urlencode(sorted(data.items())).encode('utf-8') secret = self.client.client_secret.encode('utf-8') return base64.b64encode(hmac.new(secret, to_sign, hashlib.sha1).digest()) From 5109c14a8bf881c6691a6587c5e418d0b1bbfe64 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 10:03:31 -0600 Subject: [PATCH 005/242] Refactor get_pair_url --- tests.py | 4 ++-- toopher/__init__.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests.py b/tests.py index eb33d90..d6531f2 100644 --- a/tests.py +++ b/tests.py @@ -76,9 +76,9 @@ def test_immutable_dictionaries_get_copied_for_validate(self): except toopher.SignatureValidationError: self.fail() - def test_get_pair_uri(self): + def test_get_pair_url(self): expected = 'https://api.toopher.test/v1/web/pair?username=jdoe&reset_email=jdoe%40example.com&expires=1100&v=2&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=UGlgBEUF6UZEhYPxevJeagqy6D4%3D' - self.assertEqual(expected, self.iframe_api.pair_uri('jdoe', 'jdoe@example.com')) + self.assertEqual(expected, self.iframe_api.get_pair_url('jdoe', 'jdoe@example.com')) def test_get_manage_user_uri(self): expected = 'https://api.toopher.test/v1/web/manage_user?username=jdoe&reset_email=jdoe%40example.com&expires=1100&v=2&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=sV8qoKnxJ3fxfP6AHNa0eNFxzJs%3D' diff --git a/toopher/__init__.py b/toopher/__init__.py index 810b416..6301f35 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -40,13 +40,15 @@ def __init__(self, key, secret, api_uri=None): api_uri = api_uri if api_uri else DEFAULT_BASE_URL self.base_uri = api_uri.rstrip('/') - def pair_uri(self, username, reset_email, ttl = DEFAULT_IFRAME_TTL): + def get_pair_url(self, username, reset_email, **kwargs): params = { 'v':IFRAME_VERSION, 'username':username, 'reset_email':reset_email } - return self.get_oauth_uri(self.base_uri + '/web/pair', params, ttl) + # can we just set ttl to DEFAULT_IFRAME_TTL + # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL + return self.get_oauth_uri(self.base_uri + '/web/pair', params, DEFAULT_IFRAME_TTL) def manage_user_uri(self, username, reset_email, ttl=DEFAULT_IFRAME_TTL): params = { From 186f7bb134cfba02169c93658931854d7c3076cd Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 10:48:37 -0600 Subject: [PATCH 006/242] Refactor get_user_management_url --- tests.py | 4 ++-- toopher/__init__.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests.py b/tests.py index d6531f2..62ee290 100644 --- a/tests.py +++ b/tests.py @@ -80,9 +80,9 @@ def test_get_pair_url(self): expected = 'https://api.toopher.test/v1/web/pair?username=jdoe&reset_email=jdoe%40example.com&expires=1100&v=2&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=UGlgBEUF6UZEhYPxevJeagqy6D4%3D' self.assertEqual(expected, self.iframe_api.get_pair_url('jdoe', 'jdoe@example.com')) - def test_get_manage_user_uri(self): + def test_get_user_management_url(self): expected = 'https://api.toopher.test/v1/web/manage_user?username=jdoe&reset_email=jdoe%40example.com&expires=1100&v=2&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=sV8qoKnxJ3fxfP6AHNa0eNFxzJs%3D' - self.assertEqual(expected, self.iframe_api.manage_user_uri('jdoe', 'jdoe@example.com')) + self.assertEqual(expected, self.iframe_api.get_user_management_url('jdoe', 'jdoe@example.com')) def test_get_login_uri(self): expected = 'https://api.toopher.test/v1/web/authenticate?username=jdoe&automation_allowed=True&reset_email=jdoe%40example.com&session_token=s9s7vsb&v=2&allow_inline_pairing=True&requester_metadata=None&challenge_required=False&expires=1100&action_name=Log+In&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=URVngBe35eP%2FiFOSQ5ZpuGEYcJs%3D' diff --git a/toopher/__init__.py b/toopher/__init__.py index 6301f35..40ea2f1 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -50,13 +50,15 @@ def get_pair_url(self, username, reset_email, **kwargs): # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL return self.get_oauth_uri(self.base_uri + '/web/pair', params, DEFAULT_IFRAME_TTL) - def manage_user_uri(self, username, reset_email, ttl=DEFAULT_IFRAME_TTL): + def get_user_management_url(self, username, reset_email, **kwargs): params = { 'v':IFRAME_VERSION, 'username':username, 'reset_email':reset_email } - return self.get_oauth_uri(self.base_uri + '/web/manage_user', params, ttl) + # can we just set ttl to DEFAULT_IFRAME_TTL + # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL + return self.get_oauth_uri(self.base_uri + '/web/manage_user', params, DEFAULT_IFRAME_TTL) def auth_uri(self, username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl=DEFAULT_IFRAME_TTL, allow_inline_pairing=True): params = { From 236ea0b1a78bfdfb975272b0c06446d6922ea8f1 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 10:49:44 -0600 Subject: [PATCH 007/242] Refactor get_auth_url --- README-Iframe.md | 2 +- tests.py | 9 ++++---- toopher/__init__.py | 50 ++++++++++++++++++++++++++++++++------------- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/README-Iframe.md b/README-Iframe.md index ed10347..8b89803 100644 --- a/README-Iframe.md +++ b/README-Iframe.md @@ -57,7 +57,7 @@ request.session['ToopherRequestToken'] = request_token The Toopher Authentication API provides the requester a rich set of controls over authentication parameters. ```python -auth_iframe_url = iframe_api.auth_uri(username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl); +auth_iframe_url = iframe_api.get_auth_url(username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl); ``` For the simple case of authenticating a user at login, a `login_uri` helper method is available: diff --git a/tests.py b/tests.py index 62ee290..5e1fd97 100644 --- a/tests.py +++ b/tests.py @@ -84,13 +84,14 @@ def test_get_user_management_url(self): expected = 'https://api.toopher.test/v1/web/manage_user?username=jdoe&reset_email=jdoe%40example.com&expires=1100&v=2&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=sV8qoKnxJ3fxfP6AHNa0eNFxzJs%3D' self.assertEqual(expected, self.iframe_api.get_user_management_url('jdoe', 'jdoe@example.com')) - def test_get_login_uri(self): + # Failing this test b/c allow_inline_pairing is defaulting to False when action_name is 'Log In' + def test_get_login_url(self): expected = 'https://api.toopher.test/v1/web/authenticate?username=jdoe&automation_allowed=True&reset_email=jdoe%40example.com&session_token=s9s7vsb&v=2&allow_inline_pairing=True&requester_metadata=None&challenge_required=False&expires=1100&action_name=Log+In&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=URVngBe35eP%2FiFOSQ5ZpuGEYcJs%3D' - self.assertEqual(expected, self.iframe_api.login_uri('jdoe', 'jdoe@example.com', ToopherIframeTests.request_token)) + self.assertEqual(expected, self.iframe_api.get_auth_url('jdoe', 'jdoe@example.com', ToopherIframeTests.request_token)) - def test_get_login_uri_without_inline_pairing(self): + def test_get_login_url_without_inline_pairing(self): expected = 'https://api.toopher.test/v1/web/authenticate?username=jdoe&automation_allowed=True&reset_email=jdoe%40example.com&session_token=s9s7vsb&v=2&allow_inline_pairing=False&requester_metadata=None&challenge_required=False&expires=1100&action_name=Log+In&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=lbz2kuYG3BM2Y0mZLElbTiWPv8A%3D' - self.assertEqual(expected, self.iframe_api.login_uri('jdoe', 'jdoe@example.com', ToopherIframeTests.request_token, allow_inline_pairing=False)) + self.assertEqual(expected, self.iframe_api.get_auth_url('jdoe', 'jdoe@example.com', ToopherIframeTests.request_token, allow_inline_pairing=False)) class ToopherTests(unittest.TestCase): diff --git a/toopher/__init__.py b/toopher/__init__.py index 40ea2f1..dea36a1 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -60,22 +60,44 @@ def get_user_management_url(self, username, reset_email, **kwargs): # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL return self.get_oauth_uri(self.base_uri + '/web/manage_user', params, DEFAULT_IFRAME_TTL) - def auth_uri(self, username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl=DEFAULT_IFRAME_TTL, allow_inline_pairing=True): + # Params still TBD + def get_auth_url(self, username, reset_email, request_token, action_name='Log In', requester_metadata='None', **kwargs): + if not 'allow_inline_pairing' in kwargs: + kwargs['allow_inline_pairing'] = True + params = { - 'v':IFRAME_VERSION, - 'username':username, - 'reset_email':reset_email, - 'action_name':action_name, - 'automation_allowed':automation_allowed, - 'challenge_required':challenge_required, - 'session_token':request_token, - 'requester_metadata':requester_metadata, - 'allow_inline_pairing':allow_inline_pairing - } - return self.get_oauth_uri(self.base_uri + '/web/authenticate', params, ttl) + 'v':IFRAME_VERSION, + 'username':username, + 'reset_email':reset_email, + 'action_name':action_name, + 'automation_allowed':True, + 'challenge_required':False, + 'session_token':request_token, + 'requester_metadata':requester_metadata, + 'allow_inline_pairing': kwargs['allow_inline_pairing'] + } - def login_uri(self, username, reset_email, request_token, **kwargs): - return self.auth_uri(username, reset_email, 'Log In', True, False, request_token, 'None', DEFAULT_IFRAME_TTL, **kwargs) + # can we just set ttl to DEFAULT_IFRAME_TTL + # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL + return self.get_oauth_uri(self.base_uri + '/web/authenticate', params, DEFAULT_IFRAME_TTL) + + + # def auth_uri(self, username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl=DEFAULT_IFRAME_TTL, allow_inline_pairing=True): + # params = { + # 'v':IFRAME_VERSION, + # 'username':username, + # 'reset_email':reset_email, + # 'action_name':action_name, + # 'automation_allowed':automation_allowed, + # 'challenge_required':challenge_required, + # 'session_token':request_token, + # 'requester_metadata':requester_metadata, + # 'allow_inline_pairing':allow_inline_pairing + # } + # return self.get_oauth_uri(self.base_uri + '/web/authenticate', params, ttl) + # + # def login_uri(self, username, reset_email, request_token, **kwargs): + # return self.auth_uri(username, reset_email, 'Log In', True, False, request_token, 'None', DEFAULT_IFRAME_TTL, **kwargs) def validate_postback(self, data, request_token=None): # make a mutable copy of the data From a2e09a640f213e2faf5bc68cabcb572ba4ad42a0 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 10:51:09 -0600 Subject: [PATCH 008/242] Refactor get_oauth_signed_url --- toopher/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/toopher/__init__.py b/toopher/__init__.py index dea36a1..f553d3f 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -48,7 +48,7 @@ def get_pair_url(self, username, reset_email, **kwargs): } # can we just set ttl to DEFAULT_IFRAME_TTL # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL - return self.get_oauth_uri(self.base_uri + '/web/pair', params, DEFAULT_IFRAME_TTL) + return self.get_oauth_signed_url(self.base_uri + '/web/pair', params, DEFAULT_IFRAME_TTL) def get_user_management_url(self, username, reset_email, **kwargs): params = { @@ -58,7 +58,7 @@ def get_user_management_url(self, username, reset_email, **kwargs): } # can we just set ttl to DEFAULT_IFRAME_TTL # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL - return self.get_oauth_uri(self.base_uri + '/web/manage_user', params, DEFAULT_IFRAME_TTL) + return self.get_oauth_signed_url(self.base_uri + '/web/manage_user', params, DEFAULT_IFRAME_TTL) # Params still TBD def get_auth_url(self, username, reset_email, request_token, action_name='Log In', requester_metadata='None', **kwargs): @@ -79,7 +79,7 @@ def get_auth_url(self, username, reset_email, request_token, action_name='Log In # can we just set ttl to DEFAULT_IFRAME_TTL # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL - return self.get_oauth_uri(self.base_uri + '/web/authenticate', params, DEFAULT_IFRAME_TTL) + return self.get_oauth_signed_url(self.base_uri + '/web/authenticate', params, DEFAULT_IFRAME_TTL) # def auth_uri(self, username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl=DEFAULT_IFRAME_TTL, allow_inline_pairing=True): @@ -94,7 +94,7 @@ def get_auth_url(self, username, reset_email, request_token, action_name='Log In # 'requester_metadata':requester_metadata, # 'allow_inline_pairing':allow_inline_pairing # } - # return self.get_oauth_uri(self.base_uri + '/web/authenticate', params, ttl) + # return self.get_oauth_signed_url(self.base_uri + '/web/authenticate', params, ttl) # # def login_uri(self, username, reset_email, request_token, **kwargs): # return self.auth_uri(username, reset_email, 'Log In', True, False, request_token, 'None', DEFAULT_IFRAME_TTL, **kwargs) @@ -142,7 +142,7 @@ def _signature(self, data): secret = self.client.client_secret.encode('utf-8') return base64.b64encode(hmac.new(secret, to_sign, hashlib.sha1).digest()) - def get_oauth_uri(self, uri, params, ttl): + def get_oauth_signed_url(self, uri, params, ttl): params['expires'] = str(int(time.time()) + ttl) return self.client.sign(uri + '?' + urllib.urlencode(params))[0] From 3c679a41b21f90fd32ab821e02a6b3a39d5b304d Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 11:34:00 -0600 Subject: [PATCH 009/242] Check kwargs and set default values for get_auth_url --- tests.py | 4 ++-- toopher/__init__.py | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/tests.py b/tests.py index 5e1fd97..21a0bc0 100644 --- a/tests.py +++ b/tests.py @@ -86,11 +86,11 @@ def test_get_user_management_url(self): # Failing this test b/c allow_inline_pairing is defaulting to False when action_name is 'Log In' def test_get_login_url(self): - expected = 'https://api.toopher.test/v1/web/authenticate?username=jdoe&automation_allowed=True&reset_email=jdoe%40example.com&session_token=s9s7vsb&v=2&allow_inline_pairing=True&requester_metadata=None&challenge_required=False&expires=1100&action_name=Log+In&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=URVngBe35eP%2FiFOSQ5ZpuGEYcJs%3D' + expected = 'https://api.toopher.test/v1/web/authenticate?username=jdoe&reset_email=jdoe%40example.com&session_token=s9s7vsb&allow_inline_pairing=True&expires=1100&action_name=Log+In&automation_allowed=True&requester_metadata=None&v=2&challenge_required=False&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=URVngBe35eP%2FiFOSQ5ZpuGEYcJs%3D' self.assertEqual(expected, self.iframe_api.get_auth_url('jdoe', 'jdoe@example.com', ToopherIframeTests.request_token)) def test_get_login_url_without_inline_pairing(self): - expected = 'https://api.toopher.test/v1/web/authenticate?username=jdoe&automation_allowed=True&reset_email=jdoe%40example.com&session_token=s9s7vsb&v=2&allow_inline_pairing=False&requester_metadata=None&challenge_required=False&expires=1100&action_name=Log+In&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=lbz2kuYG3BM2Y0mZLElbTiWPv8A%3D' + expected = 'https://api.toopher.test/v1/web/authenticate?username=jdoe&reset_email=jdoe%40example.com&session_token=s9s7vsb&allow_inline_pairing=False&expires=1100&action_name=Log+In&automation_allowed=True&requester_metadata=None&v=2&challenge_required=False&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=lbz2kuYG3BM2Y0mZLElbTiWPv8A%3D' self.assertEqual(expected, self.iframe_api.get_auth_url('jdoe', 'jdoe@example.com', ToopherIframeTests.request_token, allow_inline_pairing=False)) diff --git a/toopher/__init__.py b/toopher/__init__.py index f553d3f..67a5a03 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -64,22 +64,26 @@ def get_user_management_url(self, username, reset_email, **kwargs): def get_auth_url(self, username, reset_email, request_token, action_name='Log In', requester_metadata='None', **kwargs): if not 'allow_inline_pairing' in kwargs: kwargs['allow_inline_pairing'] = True + if not 'automation_allowed' in kwargs: + kwargs['automation_allowed'] = True + if not 'challenge_required' in kwargs: + kwargs['challenge_required'] = False + if not 'ttl' in kwargs: + ttl = DEFAULT_IFRAME_TTL + else: + ttl = kwargs.pop('ttl') params = { 'v':IFRAME_VERSION, 'username':username, 'reset_email':reset_email, 'action_name':action_name, - 'automation_allowed':True, - 'challenge_required':False, 'session_token':request_token, - 'requester_metadata':requester_metadata, - 'allow_inline_pairing': kwargs['allow_inline_pairing'] + 'requester_metadata':requester_metadata } + params.update(kwargs) - # can we just set ttl to DEFAULT_IFRAME_TTL - # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL - return self.get_oauth_signed_url(self.base_uri + '/web/authenticate', params, DEFAULT_IFRAME_TTL) + return self.get_oauth_signed_url(self.base_uri + '/web/authenticate', params, ttl) # def auth_uri(self, username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl=DEFAULT_IFRAME_TTL, allow_inline_pairing=True): From d6799d98b8387fbfeed0a4607802eb45bbf958a3 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 11:38:35 -0600 Subject: [PATCH 010/242] Check kwargs for ttl or set default value --- toopher/__init__.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/toopher/__init__.py b/toopher/__init__.py index 67a5a03..585582b 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -40,25 +40,18 @@ def __init__(self, key, secret, api_uri=None): api_uri = api_uri if api_uri else DEFAULT_BASE_URL self.base_uri = api_uri.rstrip('/') - def get_pair_url(self, username, reset_email, **kwargs): - params = { - 'v':IFRAME_VERSION, - 'username':username, - 'reset_email':reset_email - } - # can we just set ttl to DEFAULT_IFRAME_TTL - # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL - return self.get_oauth_signed_url(self.base_uri + '/web/pair', params, DEFAULT_IFRAME_TTL) - def get_user_management_url(self, username, reset_email, **kwargs): + if not 'ttl' in kwargs: + ttl = DEFAULT_IFRAME_TTL + else: + ttl = kwargs.pop('ttl') + params = { 'v':IFRAME_VERSION, 'username':username, 'reset_email':reset_email } - # can we just set ttl to DEFAULT_IFRAME_TTL - # ttl = kwargs['ttl'] or DEFAULT_IFRAME_TTL - return self.get_oauth_signed_url(self.base_uri + '/web/manage_user', params, DEFAULT_IFRAME_TTL) + return self.get_oauth_signed_url(self.base_uri + '/web/manage_user', params, ttl) # Params still TBD def get_auth_url(self, username, reset_email, request_token, action_name='Log In', requester_metadata='None', **kwargs): @@ -103,7 +96,12 @@ def get_auth_url(self, username, reset_email, request_token, action_name='Log In # def login_uri(self, username, reset_email, request_token, **kwargs): # return self.auth_uri(username, reset_email, 'Log In', True, False, request_token, 'None', DEFAULT_IFRAME_TTL, **kwargs) - def validate_postback(self, data, request_token=None): + def validate_postback(self, data, request_token=None, **kwargs): + if not 'ttl' in kwargs: + ttl = DEFAULT_IFRAME_TTL + else: + ttl = kwargs.pop('ttl') + # make a mutable copy of the data data = dict(data) @@ -135,7 +133,7 @@ def validate_postback(self, data, request_token=None): if not signature_valid: raise SignatureValidationError("Computed signature does not match submitted signature: {0} vs {1}".format(computed_signature, maybe_sig)) - ttl_valid = int(time.time()) - int(data['timestamp']) < DEFAULT_IFRAME_TTL + ttl_valid = int(time.time()) - int(data['timestamp']) < ttl if not ttl_valid: raise SignatureValidationError("TTL expired") From f76a6e8b5812ec804d881e786cdad86ad1d0c774 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 11:39:22 -0600 Subject: [PATCH 011/242] Rename _get_oauth_signed_url --- toopher/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/toopher/__init__.py b/toopher/__init__.py index 585582b..2af6bd2 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -51,7 +51,7 @@ def get_user_management_url(self, username, reset_email, **kwargs): 'username':username, 'reset_email':reset_email } - return self.get_oauth_signed_url(self.base_uri + '/web/manage_user', params, ttl) + return self._get_oauth_signed_url(self.base_uri + '/web/manage_user', params, ttl) # Params still TBD def get_auth_url(self, username, reset_email, request_token, action_name='Log In', requester_metadata='None', **kwargs): @@ -76,7 +76,7 @@ def get_auth_url(self, username, reset_email, request_token, action_name='Log In } params.update(kwargs) - return self.get_oauth_signed_url(self.base_uri + '/web/authenticate', params, ttl) + return self._get_oauth_signed_url(self.base_uri + '/web/authenticate', params, ttl) # def auth_uri(self, username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl=DEFAULT_IFRAME_TTL, allow_inline_pairing=True): @@ -91,7 +91,7 @@ def get_auth_url(self, username, reset_email, request_token, action_name='Log In # 'requester_metadata':requester_metadata, # 'allow_inline_pairing':allow_inline_pairing # } - # return self.get_oauth_signed_url(self.base_uri + '/web/authenticate', params, ttl) + # return self._get_oauth_signed_url(self.base_uri + '/web/authenticate', params, ttl) # # def login_uri(self, username, reset_email, request_token, **kwargs): # return self.auth_uri(username, reset_email, 'Log In', True, False, request_token, 'None', DEFAULT_IFRAME_TTL, **kwargs) @@ -144,7 +144,7 @@ def _signature(self, data): secret = self.client.client_secret.encode('utf-8') return base64.b64encode(hmac.new(secret, to_sign, hashlib.sha1).digest()) - def get_oauth_signed_url(self, uri, params, ttl): + def _get_oauth_signed_url(self, uri, params, ttl): params['expires'] = str(int(time.time()) + ttl) return self.client.sign(uri + '?' + urllib.urlencode(params))[0] From 123043880f3e604cc1cc971fd342b6b803fc67f9 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 11:49:34 -0600 Subject: [PATCH 012/242] Rename authenticate params --- toopher/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/toopher/__init__.py b/toopher/__init__.py index 2af6bd2..fc37e15 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -207,15 +207,15 @@ def get_pairing_status(self, pairing_id): result = self._request(uri, "GET") return PairingStatus(result) - def authenticate(self, id_or_username, terminal_or_otp, action_name=None, **kwargs): + def authenticate(self, id_or_username, terminal, action_name=None, **kwargs): url = self.base_url + "/authentication_requests/initiate" try: uuid.UUID(id_or_username) params = {'pairing_id': id_or_username, - 'terminal_name': terminal_or_otp} + 'terminal_name': terminal} except: params = {'user_name': id_or_username, - 'terminal_name_extra': terminal_or_otp} + 'terminal_name_extra': terminal} if action_name: params['action_name'] = action_name params.update(kwargs) From aa3cc13fb32777d6f7ee8d7f703d2fda640e69b1 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 11:50:28 -0600 Subject: [PATCH 013/242] Rename AuthenticationStatus to AuthenticationRequest --- README.md | 4 ++-- demo.py | 2 +- demo_bells_and_whistles.py | 2 +- tests.py | 16 ++++++++-------- toopher/__init__.py | 12 ++++++------ 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 6ad702b..b800168 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ pairing_status = api.pair("pairing phrase", "username@yourservice.com") auth = api.authenticate(pairing_status.id, "my computer") # Once they've responded you can then check the status -auth_status = api.get_authentication_status(auth.id) -if (auth_status.pending == False and auth_status.granted == True): +auth_request = api.get_authentication_request_by_id(auth.id) +if (auth_request.pending == False and auth_request.granted == True): # Success! ``` diff --git a/demo.py b/demo.py index d575cc3..cd3ed35 100755 --- a/demo.py +++ b/demo.py @@ -99,7 +99,7 @@ def print_sep(char='-'): print 'Checking status of authentication request...' try: - request_status = api.get_authentication_status(request_id) + request_status = api.get_authentication_request_by_id(request_id) except ToopherApiError, e: print 'Could not check authentication status (reason: %s)' % e continue diff --git a/demo_bells_and_whistles.py b/demo_bells_and_whistles.py index af924d5..ae96117 100755 --- a/demo_bells_and_whistles.py +++ b/demo_bells_and_whistles.py @@ -123,7 +123,7 @@ def print_sep(char='-'): if otp: request_status = api.authenticate_with_otp(request_id, otp) else: - request_status = api.get_authentication_status(request_id) + request_status = api.get_authentication_request_by_id(request_id) except ToopherApiError, e: print 'Could not check authentication status (reason: %s)' % e continue diff --git a/tests.py b/tests.py index 21a0bc0..29c3702 100644 --- a/tests.py +++ b/tests.py @@ -178,14 +178,14 @@ def fn(): self.assertEqual(last_called_data['terminal_name'], 'terminal_name') self.assertEqual(last_called_data['action_name'], 'action_name') - def test_authentication_status(self): + def test_authentication_request(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ 'authentication_requests/1': (200, '{"id":"1", "pending":false, "granted":true, "automated":false, "reason":"its a test", "terminal":{"id":"1", "name":"test terminal"}}' ) }) - auth_request = api.get_authentication_status('1') + auth_request = api.get_authentication_request_by_id('1') self.assertEqual(api.client.last_called_method, 'GET') self.assertEqual(auth_request.id, '1') self.assertFalse(auth_request.pending, False) @@ -243,14 +243,14 @@ def test_access_arbitrary_keys_in_pairing_status(self): self.assertEqual(pairing.random_key, "84") - def test_access_arbitrary_keys_in_authentication_status(self): + def test_access_arbitrary_keys_in_authentication_request(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ 'authentication_requests/1': (200, '{"id":"1", "pending":false, "granted":true, "automated":false, "reason":"its a test", "terminal":{"id":"1", "name":"test terminal"}, "random_key":"84"}' ) }) - auth_request = api.get_authentication_status('1') + auth_request = api.get_authentication_request_by_id('1') self.assertEqual(api.client.last_called_method, 'GET') self.assertEqual(auth_request.id, '1') self.assertFalse(auth_request.pending, False) @@ -440,21 +440,21 @@ def __getitem__(self, key): except KeyError as e: return ddict() -class AuthenticationStatusTests(unittest.TestCase): +class AuthenticationRequestTests(unittest.TestCase): def test_incomplete_response_raises_exception(self): response = {'key': 'value'} def fn(): - toopher.AuthenticationStatus(response) + toopher.AuthenticationRequest(response) self.assertRaises(toopher.ToopherApiError, fn) def test_nonzero_when_granted(self): response = ddict() response['granted'] = True - allowed = toopher.AuthenticationStatus(response) + allowed = toopher.AuthenticationRequest(response) self.assertTrue(allowed) response['granted'] = False - denied = toopher.AuthenticationStatus(response) + denied = toopher.AuthenticationRequest(response) self.assertFalse(denied) class PairingStatusTests(unittest.TestCase): diff --git a/toopher/__init__.py b/toopher/__init__.py index fc37e15..0193396 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -221,7 +221,7 @@ def authenticate(self, id_or_username, terminal, action_name=None, **kwargs): params.update(kwargs) result = self._request(url, "POST", params) - return AuthenticationStatus(result) + return AuthenticationRequest(result) # def authenticate(self, pairing_id, terminal_name, action_name=None, **kwargs): # uri = self.base_url + "/authentication_requests/initiate" @@ -235,17 +235,17 @@ def authenticate(self, id_or_username, terminal, action_name=None, **kwargs): # result = self._request(uri, "POST", params) # return AuthenticationStatus(result) - def get_authentication_status(self, authentication_request_id): + def get_authentication_request_by_id(self, authentication_request_id): uri = self.base_url + "/authentication_requests/" + authentication_request_id result = self._request(uri, "GET") - return AuthenticationStatus(result) + return AuthenticationRequest(result) def authenticate_with_otp(self, authentication_request_id, otp): uri = self.base_url + "/authentication_requests/" + authentication_request_id + '/otp_auth' params = {'otp' : otp} result = self._request(uri, "POST", params) - return AuthenticationStatus(result) + return AuthenticationRequest(result) # def authenticate_by_user_name(self, user_name, terminal_name_extra, action_name=None, **kwargs): # kwargs.update(user_name=user_name, terminal_name_extra=terminal_name_extra) @@ -323,7 +323,7 @@ def __getattr__(self, name): return self._raw_data[name] -class AuthenticationStatus(object): +class AuthenticationRequest(object): def __init__(self, json_response): try: self.id = json_response['id'] @@ -345,7 +345,7 @@ def __nonzero__(self): def __getattr__(self, name): if name.startswith('__') or name not in self._raw_data: # Exclude 'magic' methods to allow for (un)pickling - return super(AuthenticationStatus, self).__getattr__(name) + return super(AuthenticationRequest, self).__getattr__(name) else: return self._raw_data[name] From ad1da3eebeecb2ad74e023db1b12c7afb5bba688 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 11:51:27 -0600 Subject: [PATCH 014/242] Rename PairingStatus to Pairing --- README.md | 4 ++-- demo.py | 8 ++++---- demo_bells_and_whistles.py | 8 ++++---- tests.py | 16 ++++++++-------- toopher/__init__.py | 16 ++++++++-------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index b800168..b59a31a 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,10 @@ import toopher api = toopher.ToopherApi("", "") # Step 1 - Pair with their phone's Toopher app -pairing_status = api.pair("pairing phrase", "username@yourservice.com") +pairing = api.pair("pairing phrase", "username@yourservice.com") # Step 2 - Authenticate a log in -auth = api.authenticate(pairing_status.id, "my computer") +auth = api.authenticate(pairing.id, "my computer") # Once they've responded you can then check the status auth_request = api.get_authentication_request_by_id(auth.id) diff --git a/demo.py b/demo.py index cd3ed35..5b4b117 100755 --- a/demo.py +++ b/demo.py @@ -48,8 +48,8 @@ def print_sep(char='-'): print 'Sending pairing request...' try: - pairing_status = api.pair(pairing_phrase, user_name) - pairing_id = pairing_status.id + pairing = api.pair(pairing_phrase, user_name) + pairing_id = pairing.id break except ToopherApiError, e: print 'The pairing phrase was not accepted (reason: %s)' % e @@ -59,8 +59,8 @@ def print_sep(char='-'): print 'Checking status of pairing request...' try: - pairing_status = api.get_pairing_status(pairing_id) - if pairing_status.enabled: + pairing = api.get_pairing_by_id(pairing_id) + if pairing.enabled: print 'Pairing complete' print break diff --git a/demo_bells_and_whistles.py b/demo_bells_and_whistles.py index ae96117..41d381f 100755 --- a/demo_bells_and_whistles.py +++ b/demo_bells_and_whistles.py @@ -65,8 +65,8 @@ def print_sep(char='-'): print 'Sending pairing request...' try: - pairing_status = pair_func(pairing_key, user_name) - pairing_id = pairing_status.id + pairing = pair_func(pairing_key, user_name) + pairing_id = pairing.id break except ToopherApiError, e: print 'The pairing phrase was not accepted (reason: %s)' % e @@ -76,8 +76,8 @@ def print_sep(char='-'): print 'Checking status of pairing request...' try: - pairing_status = api.get_pairing_status(pairing_id) - if pairing_status.enabled: + pairing = api.get_pairing_by_id(pairing_id) + if pairing.enabled: print 'Pairing complete' print break diff --git a/tests.py b/tests.py index 29c3702..2dc0284 100644 --- a/tests.py +++ b/tests.py @@ -134,14 +134,14 @@ def fn(): self.assertEqual(api.client.last_called_data['test_param'], ['42']) self.assertRaises(KeyError, fn) - def test_pairing_status(self): + def test_pairing(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ 'pairings/1': (200, '{"id":"1", "enabled":true, "user":{"id":"1","name":"some user"}}' ) }) - pairing = api.get_pairing_status('1') + pairing = api.get_pairing_by_id('1') self.assertEqual(api.client.last_called_method, 'GET') self.assertEqual(pairing.id, '1') @@ -226,14 +226,14 @@ def test_pass_arbitrary_parameters_on_authenticate(self): self.assertEqual(api.client.last_called_data['terminal_name'], 'test terminal') self.assertEqual(api.client.last_called_data['test_param'], '42') - def test_access_arbitrary_keys_in_pairing_status(self): + def test_access_arbitrary_keys_in_pairing(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ 'pairings/1': (200, '{"id":"1", "enabled":true, "user":{"id":"1","name":"some user"}, "random_key":"84"}' ) }) - pairing = api.get_pairing_status('1') + pairing = api.get_pairing_by_id('1') self.assertEqual(api.client.last_called_method, 'GET') self.assertEqual(pairing.id, '1') @@ -457,21 +457,21 @@ def test_nonzero_when_granted(self): denied = toopher.AuthenticationRequest(response) self.assertFalse(denied) -class PairingStatusTests(unittest.TestCase): +class PairingTests(unittest.TestCase): def test_incomplete_response_raises_exception(self): response = {'key': 'value'} def fn(): - toopher.PairingStatus(response) + toopher.Pairing(response) self.assertRaises(toopher.ToopherApiError, fn) def test_nonzero_when_granted(self): response = ddict() response['enabled'] = True - allowed = toopher.PairingStatus(response) + allowed = toopher.Pairing(response) self.assertTrue(allowed) response['enabled'] = False - denied = toopher.PairingStatus(response) + denied = toopher.Pairing(response) self.assertFalse(denied) def main(): diff --git a/toopher/__init__.py b/toopher/__init__.py index 0193396..be84f98 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -171,7 +171,7 @@ def pair(self, username, phrase_or_num=None, **kwargs): url = self.base_url + "/pairings/create/qr" result = self._request(url, "POST", params) - return PairingStatus(result) + return Pairing(result) # def pair(self, pairing_phrase, user_name, **kwargs): # uri = self.base_url + "/pairings/create" @@ -181,14 +181,14 @@ def pair(self, username, phrase_or_num=None, **kwargs): # params.update(kwargs) # # result = self._request(uri, "POST", params) - # return PairingStatus(result) + # return Pairing(result) # # def pair_qr(self, user_name, **kwargs): # uri = self.base_url + '/pairings/create/qr' # params = {'user_name': user_name} # params.update(kwargs) # result = self._request(uri, 'POST', params) - # return PairingStatus(result) + # return Pairing(result) # # def pair_sms(self, phone_number, user_name, phone_country=None): # uri = self.base_url + "/pairings/create/sms" @@ -199,13 +199,13 @@ def pair(self, username, phrase_or_num=None, **kwargs): # params['phone_country'] = phone_country # # result = self._request(uri, "POST", params) - # return PairingStatus(result) + # return Pairing(result) - def get_pairing_status(self, pairing_id): + def get_pairing_by_id(self, pairing_id): uri = self.base_url + "/pairings/" + pairing_id result = self._request(uri, "GET") - return PairingStatus(result) + return Pairing(result) def authenticate(self, id_or_username, terminal, action_name=None, **kwargs): url = self.base_url + "/authentication_requests/initiate" @@ -299,7 +299,7 @@ def _parse_request_error(self, content): raise ToopherApiError(error_message) -class PairingStatus(object): +class Pairing(object): def __init__(self, json_response): try: self.id = json_response['id'] @@ -318,7 +318,7 @@ def __nonzero__(self): def __getattr__(self, name): if name.startswith('__') or name not in self._raw_data: # Exclude 'magic' methods to allow for (un)pickling - return super(PairingStatus, self).__getattr__(name) + return super(Pairing, self).__getattr__(name) else: return self._raw_data[name] From 4ba88b0bfc486881eccea2d4c74a406728192ebf Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 12:19:03 -0600 Subject: [PATCH 015/242] remove get_pair_url test --- tests.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests.py b/tests.py index 2dc0284..71d94ca 100644 --- a/tests.py +++ b/tests.py @@ -76,10 +76,6 @@ def test_immutable_dictionaries_get_copied_for_validate(self): except toopher.SignatureValidationError: self.fail() - def test_get_pair_url(self): - expected = 'https://api.toopher.test/v1/web/pair?username=jdoe&reset_email=jdoe%40example.com&expires=1100&v=2&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=UGlgBEUF6UZEhYPxevJeagqy6D4%3D' - self.assertEqual(expected, self.iframe_api.get_pair_url('jdoe', 'jdoe@example.com')) - def test_get_user_management_url(self): expected = 'https://api.toopher.test/v1/web/manage_user?username=jdoe&reset_email=jdoe%40example.com&expires=1100&v=2&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=sV8qoKnxJ3fxfP6AHNa0eNFxzJs%3D' self.assertEqual(expected, self.iframe_api.get_user_management_url('jdoe', 'jdoe@example.com')) From 0635cc3085b190238e956105edbf118003a226ce Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 12:19:24 -0600 Subject: [PATCH 016/242] Refactor authenticate_with_otp --- tests.py | 37 ++++++++++++++++++++++--------------- toopher/__init__.py | 16 +++++++++++----- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/tests.py b/tests.py index 71d94ca..f0e849e 100644 --- a/tests.py +++ b/tests.py @@ -291,21 +291,6 @@ def test_pair_sms(self): self.assertEqual(last_called_data['user_name'], 'user_name') self.assertEqual(last_called_data['phone_country'], '1') - def test_authenticate_with_otp(self): - api = toopher.ToopherApi('key', 'secret') - api.client = HttpClientMock({ - 'authentication_requests/id/otp_auth': (200, - json.dumps({'id': 'id', - 'pending': False, - 'granted': False, - 'automated': False, - 'reason': 'it is a test', - 'terminal': {'id': 'id', 'name': 'name'}}))}) - - api.authenticate_with_otp('id', 'otp') - self.assertEqual(api.client.last_called_method, 'POST') - self.assertEqual(api.client.last_called_data['otp'], 'otp') - def test_bad_response_raises_correct_error(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ @@ -453,6 +438,28 @@ def test_nonzero_when_granted(self): denied = toopher.AuthenticationRequest(response) self.assertFalse(denied) + def test_authenticate_with_otp(self): + response = {'id':'id', + 'pending':False, + 'granted':True, + 'automated': False, + 'reason': 'it is a test', + 'terminal': {'name':'name', 'id':'id'}} + auth_request = toopher.AuthenticationRequest(response) + + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'authentication_requests/{0}/otp_auth'.format(auth_request.id): (200, + json.dumps({'id': auth_request.id, + 'pending': False, + 'granted': False, + 'automated': False, + 'reason': 'it is a test', + 'terminal': {'id': 'id', 'name': 'name'}}))}) + auth_request.authenticate_with_otp('otp', api) + self.assertEqual(api.client.last_called_method, 'POST') + self.assertEqual(api.client.last_called_data['otp'], 'otp') + class PairingTests(unittest.TestCase): def test_incomplete_response_raises_exception(self): response = {'key': 'value'} diff --git a/toopher/__init__.py b/toopher/__init__.py index be84f98..67da2f4 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -241,11 +241,11 @@ def get_authentication_request_by_id(self, authentication_request_id): result = self._request(uri, "GET") return AuthenticationRequest(result) - def authenticate_with_otp(self, authentication_request_id, otp): - uri = self.base_url + "/authentication_requests/" + authentication_request_id + '/otp_auth' - params = {'otp' : otp} - result = self._request(uri, "POST", params) - return AuthenticationRequest(result) + # def authenticate_with_otp(self, authentication_request_id, otp): + # uri = self.base_url + "/authentication_requests/" + authentication_request_id + '/otp_auth' + # params = {'otp' : otp} + # result = self._request(uri, "POST", params) + # return AuthenticationRequest(result) # def authenticate_by_user_name(self, user_name, terminal_name_extra, action_name=None, **kwargs): # kwargs.update(user_name=user_name, terminal_name_extra=terminal_name_extra) @@ -349,5 +349,11 @@ def __getattr__(self, name): else: return self._raw_data[name] + def authenticate_with_otp(self, otp, api, **kwargs): + url = api.base_url + "/authentication_requests/" + self.id + '/otp_auth' + params = {'otp' : otp} + params.update(kwargs) + result = api._request(url, "POST", params) + return AuthenticationRequest(result) class ToopherApiError(Exception): pass From 5d7ae2750a9774b1e1e22c4d29a2de79069ebf16 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 12:54:09 -0600 Subject: [PATCH 017/242] Add Pairing.refresh_from_server --- tests.py | 22 ++++++++++++++++++++++ toopher/__init__.py | 3 +++ 2 files changed, 25 insertions(+) diff --git a/tests.py b/tests.py index f0e849e..1a89cc0 100644 --- a/tests.py +++ b/tests.py @@ -477,6 +477,28 @@ def test_nonzero_when_granted(self): denied = toopher.Pairing(response) self.assertFalse(denied) + def test_refresh_from_server(self): + response = {'id': 'id', + 'enabled': True, + 'user': {'id': 'id', 'name': 'name'}} + pairing = toopher.Pairing(response) + + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'pairings/{0}'.format(pairing.id): (200, + json.dumps({'id': pairing.id, + 'enabled': True, + 'user': {'id': 'id', 'name': 'name'}}))}) + pairing = pairing.refresh_from_server(api) + self.assertEqual(api.client.last_called_method, 'GET') + + self.assertEqual(pairing.id, 'id') + self.assertEqual(pairing.user_name, 'name') + self.assertEqual(pairing.user_id, 'id') + self.assertTrue(pairing.enabled) + + + def main(): unittest.main() diff --git a/toopher/__init__.py b/toopher/__init__.py index 67da2f4..4819169 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -322,6 +322,9 @@ def __getattr__(self, name): else: return self._raw_data[name] + def refresh_from_server(self, api): + return api.get_pairing_by_id(self.id) + class AuthenticationRequest(object): def __init__(self, json_response): From 276008b62b0653036bc962b097124bf535af1d15 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 12:59:32 -0600 Subject: [PATCH 018/242] Add AuthenticationRequest.refresh_from_server --- tests.py | 30 ++++++++++++++++++++++++++++++ toopher/__init__.py | 3 +++ 2 files changed, 33 insertions(+) diff --git a/tests.py b/tests.py index 1a89cc0..cfc583c 100644 --- a/tests.py +++ b/tests.py @@ -460,6 +460,36 @@ def test_authenticate_with_otp(self): self.assertEqual(api.client.last_called_method, 'POST') self.assertEqual(api.client.last_called_data['otp'], 'otp') + def test_refresh_from_server(self): + response = {'id':'id', + 'pending':False, + 'granted':True, + 'automated': False, + 'reason': 'it is a test', + 'terminal': {'name':'name', 'id':'id'}} + auth_request = toopher.AuthenticationRequest(response) + + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'authentication_requests/{0}'.format(auth_request.id): (200, + json.dumps({'id': auth_request.id, + 'pending': False, + 'granted': True, + 'automated': False, + 'reason': 'it is a test', + 'terminal': {'id': 'id', 'name': 'name'}}))}) + auth_request = auth_request.refresh_from_server(api) + self.assertEqual(api.client.last_called_method, 'GET') + + self.assertEqual(auth_request.id, 'id') + self.assertFalse(auth_request.pending, False) + self.assertTrue(auth_request.granted) + self.assertFalse(auth_request.automated) + self.assertEqual(auth_request.reason, 'it is a test') + self.assertEqual(auth_request.terminal_id, 'id') + self.assertEqual(auth_request.terminal_name, 'name') + + class PairingTests(unittest.TestCase): def test_incomplete_response_raises_exception(self): response = {'key': 'value'} diff --git a/toopher/__init__.py b/toopher/__init__.py index 4819169..1af186f 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -359,4 +359,7 @@ def authenticate_with_otp(self, otp, api, **kwargs): result = api._request(url, "POST", params) return AuthenticationRequest(result) + def refresh_from_server(self, api): + return api.get_authentication_request_by_id(self.id) + class ToopherApiError(Exception): pass From de24260c2830d46a6af92eab4bab1c2907d01964 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 13:09:56 -0600 Subject: [PATCH 019/242] Rename uri to url --- toopher/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/toopher/__init__.py b/toopher/__init__.py index 1af186f..f43c74a 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -202,9 +202,9 @@ def pair(self, username, phrase_or_num=None, **kwargs): # return Pairing(result) def get_pairing_by_id(self, pairing_id): - uri = self.base_url + "/pairings/" + pairing_id + url = self.base_url + "/pairings/" + pairing_id - result = self._request(uri, "GET") + result = self._request(url, "GET") return Pairing(result) def authenticate(self, id_or_username, terminal, action_name=None, **kwargs): @@ -236,9 +236,9 @@ def authenticate(self, id_or_username, terminal, action_name=None, **kwargs): # return AuthenticationStatus(result) def get_authentication_request_by_id(self, authentication_request_id): - uri = self.base_url + "/authentication_requests/" + authentication_request_id + url = self.base_url + "/authentication_requests/" + authentication_request_id - result = self._request(uri, "GET") + result = self._request(url, "GET") return AuthenticationRequest(result) # def authenticate_with_otp(self, authentication_request_id, otp): @@ -252,11 +252,12 @@ def get_authentication_request_by_id(self, authentication_request_id): # return self.authenticate('', '', action_name, **kwargs) def create_user_terminal(self, user_name, terminal_name, requester_terminal_id): - uri = self.base_url + '/user_terminals/create' + url = self.base_url + '/user_terminals/create' params = {'user_name': user_name, 'name': terminal_name, 'name_extra': requester_terminal_id} - self._request(uri, 'POST', params) + self._request(url, 'POST', params) + def set_toopher_enabled_for_user(self, user_name, enabled): uri = self.base_url + '/users' From 2d27608b240cab3f396c0d06ec030aa4736f8112 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 13:24:35 -0600 Subject: [PATCH 020/242] Add UserTerminal class and test --- tests.py | 7 ++++++- toopher/__init__.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tests.py b/tests.py index cfc583c..c3750b9 100644 --- a/tests.py +++ b/tests.py @@ -527,7 +527,12 @@ def test_refresh_from_server(self): self.assertEqual(pairing.user_id, 'id') self.assertTrue(pairing.enabled) - +class UserTerminalTests(unittest.TestCase): + def test_incomplete_response_raises_exception(self): + response = {'key': 'value'} + def fn(): + toopher.UserTerminal(response) + self.assertRaises(toopher.ToopherApiError, fn) def main(): unittest.main() diff --git a/toopher/__init__.py b/toopher/__init__.py index f43c74a..a268851 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -363,4 +363,26 @@ def authenticate_with_otp(self, otp, api, **kwargs): def refresh_from_server(self, api): return api.get_authentication_request_by_id(self.id) +class UserTerminal(object): + def __init__(self, json_response): + try: + self.id = json_response['id'] + self.name = json_response['name'] + self.name_extra = json_response['name_extra'] + user = json_response['user'] + self.user_id = user['id'] + self.user_name = user['name'] + except Exception: + raise ToopherApiError("Could not parse user terminal from response") + + self._raw_data = json_response + + def __getattr__(self, name): + if name.startswith('__') or name not in self._raw_data: # Exclude 'magic' methods to allow for (un)pickling + return super(UserTerminal, self).__getattr__(name) + else: + return self._raw_data[name] + + + class ToopherApiError(Exception): pass From 453c0eaed080b8e40006efa084efb550098ecb10 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 14:42:46 -0600 Subject: [PATCH 021/242] Add get_user_terminal_by_id --- tests.py | 17 +++++++++++++++++ toopher/__init__.py | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/tests.py b/tests.py index c3750b9..8bf2bb9 100644 --- a/tests.py +++ b/tests.py @@ -325,6 +325,23 @@ def test_create_user_terminal(self): self.assertEqual(last_called_data['name'], 'terminal_name') self.assertEqual(last_called_data['name_extra'], 'requester_terminal_id') + def test_get_user_terminal_by_id(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'user_terminals/1': (200, + '{"id":"1", "name":"name", "name_extra":"name_extra", "user":{"id":"1","name":"some user"}}' + ) + }) + + user_terminal = api.get_user_terminal_by_id("1") + + self.assertEqual(api.client.last_called_method, "GET") + self.assertEqual(user_terminal.id, "1") + self.assertEqual(user_terminal.name, "name") + self.assertEqual(user_terminal.name_extra, "name_extra") + self.assertEqual(user_terminal.user_name, "some user") + self.assertEqual(user_terminal.user_id, "1") + def test_enable_toopher_for_user(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ diff --git a/toopher/__init__.py b/toopher/__init__.py index a268851..1f65ad0 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -258,6 +258,12 @@ def create_user_terminal(self, user_name, terminal_name, requester_terminal_id): 'name_extra': requester_terminal_id} self._request(url, 'POST', params) + def get_user_terminal_by_id(self, terminal_id): + url = self.base_url + '/user_terminals/' + terminal_id + + result = self._request(url, "GET") + return UserTerminal(result) + def set_toopher_enabled_for_user(self, user_name, enabled): uri = self.base_url + '/users' From 9da5024ebb7c1d93a5b60d556b700b54e89c4b91 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 15:00:05 -0600 Subject: [PATCH 022/242] Add UserTerminal.refresh_from_server --- tests.py | 24 ++++++++++++++++++++++++ toopher/__init__.py | 7 +++++++ 2 files changed, 31 insertions(+) diff --git a/tests.py b/tests.py index 8bf2bb9..e6f5571 100644 --- a/tests.py +++ b/tests.py @@ -551,6 +551,30 @@ def fn(): toopher.UserTerminal(response) self.assertRaises(toopher.ToopherApiError, fn) + def test_refresh_from_server(self): + response = {'id': 'id', + 'name': 'name', + 'name_extra': 'name_extra', + 'user': {'id': 'id', 'name': 'name'}} + user_terminal = toopher.UserTerminal(response) + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'user_terminals/{0}'.format(user_terminal.id): (200, + json.dumps({'id': 'id', + 'name': 'name changed', + 'name_extra': 'name_extra changed', + 'user': {'id': 'id', 'name': 'name changed'}}))}) + user_terminal.refresh_from_server(api) + self.assertEqual(api.client.last_called_method, 'GET') + + + self.assertEqual(user_terminal.id, "id") + self.assertEqual(user_terminal.name, "name changed") + self.assertEqual(user_terminal.name_extra, "name_extra changed") + self.assertEqual(user_terminal.user_name, "name changed") + self.assertEqual(user_terminal.user_id, "id") + + def main(): unittest.main() diff --git a/toopher/__init__.py b/toopher/__init__.py index 1f65ad0..754ddd1 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -389,6 +389,13 @@ def __getattr__(self, name): else: return self._raw_data[name] + def refresh_from_server(self, api): + url = api.base_url + '/user_terminals/' + self.id + result = api._request(url, "GET") + self.name = result["name"] + self.name_extra = result["name_extra"] + user = result["user"] + self.user_name = user["name"] class ToopherApiError(Exception): pass From f32ec2126712e9b81144fbf134bd84b39e46948f Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 15:05:48 -0600 Subject: [PATCH 023/242] Remove comment since test is passing --- tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests.py b/tests.py index e6f5571..a9891b4 100644 --- a/tests.py +++ b/tests.py @@ -80,7 +80,6 @@ def test_get_user_management_url(self): expected = 'https://api.toopher.test/v1/web/manage_user?username=jdoe&reset_email=jdoe%40example.com&expires=1100&v=2&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=sV8qoKnxJ3fxfP6AHNa0eNFxzJs%3D' self.assertEqual(expected, self.iframe_api.get_user_management_url('jdoe', 'jdoe@example.com')) - # Failing this test b/c allow_inline_pairing is defaulting to False when action_name is 'Log In' def test_get_login_url(self): expected = 'https://api.toopher.test/v1/web/authenticate?username=jdoe&reset_email=jdoe%40example.com&session_token=s9s7vsb&allow_inline_pairing=True&expires=1100&action_name=Log+In&automation_allowed=True&requester_metadata=None&v=2&challenge_required=False&oauth_nonce=12345678&oauth_timestamp=1000&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&oauth_consumer_key=abcdefg&oauth_signature=URVngBe35eP%2FiFOSQ5ZpuGEYcJs%3D' self.assertEqual(expected, self.iframe_api.get_auth_url('jdoe', 'jdoe@example.com', ToopherIframeTests.request_token)) From 205a1cbbb3d0a16224fde99c6983dec0e0bc6d9e Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 15:13:08 -0600 Subject: [PATCH 024/242] "Refactor" AuthenticationRequest.refresh_from_server --- tests.py | 14 +++++++------- toopher/__init__.py | 10 +++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/tests.py b/tests.py index a9891b4..c476ace 100644 --- a/tests.py +++ b/tests.py @@ -491,19 +491,19 @@ def test_refresh_from_server(self): json.dumps({'id': auth_request.id, 'pending': False, 'granted': True, - 'automated': False, - 'reason': 'it is a test', - 'terminal': {'id': 'id', 'name': 'name'}}))}) - auth_request = auth_request.refresh_from_server(api) + 'automated': True, + 'reason': 'it is a test CHANGED', + 'terminal': {'id': 'id', 'name': 'name changed'}}))}) + auth_request.refresh_from_server(api) self.assertEqual(api.client.last_called_method, 'GET') self.assertEqual(auth_request.id, 'id') self.assertFalse(auth_request.pending, False) self.assertTrue(auth_request.granted) - self.assertFalse(auth_request.automated) - self.assertEqual(auth_request.reason, 'it is a test') + self.assertTrue(auth_request.automated) + self.assertEqual(auth_request.reason, 'it is a test CHANGED') self.assertEqual(auth_request.terminal_id, 'id') - self.assertEqual(auth_request.terminal_name, 'name') + self.assertEqual(auth_request.terminal_name, 'name changed') class PairingTests(unittest.TestCase): diff --git a/toopher/__init__.py b/toopher/__init__.py index 754ddd1..cc959ea 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -367,7 +367,15 @@ def authenticate_with_otp(self, otp, api, **kwargs): return AuthenticationRequest(result) def refresh_from_server(self, api): - return api.get_authentication_request_by_id(self.id) + url = api.base_url + '/authentication_requests/' + self.id + result = api._request(url, 'GET') + self.pending = result['pending'] + self.granted = result['granted'] + self.automated = result['automated'] + self.reason = result['reason'] + terminal = result['terminal'] + self.terminal_name = terminal['name'] + class UserTerminal(object): def __init__(self, json_response): From eb80a8bf44a125ba417edea321975bdfabf27d86 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 15:16:21 -0600 Subject: [PATCH 025/242] "Refactor" Pairing.refresh_from_server --- tests.py | 10 +++++----- toopher/__init__.py | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests.py b/tests.py index c476ace..a7e1d9e 100644 --- a/tests.py +++ b/tests.py @@ -533,15 +533,15 @@ def test_refresh_from_server(self): api.client = HttpClientMock({ 'pairings/{0}'.format(pairing.id): (200, json.dumps({'id': pairing.id, - 'enabled': True, - 'user': {'id': 'id', 'name': 'name'}}))}) - pairing = pairing.refresh_from_server(api) + 'enabled': False, + 'user': {'id': 'id', 'name': 'name changed'}}))}) + pairing.refresh_from_server(api) self.assertEqual(api.client.last_called_method, 'GET') self.assertEqual(pairing.id, 'id') - self.assertEqual(pairing.user_name, 'name') + self.assertEqual(pairing.user_name, 'name changed') self.assertEqual(pairing.user_id, 'id') - self.assertTrue(pairing.enabled) + self.assertFalse(pairing.enabled) class UserTerminalTests(unittest.TestCase): def test_incomplete_response_raises_exception(self): diff --git a/toopher/__init__.py b/toopher/__init__.py index cc959ea..7d5b962 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -330,7 +330,11 @@ def __getattr__(self, name): return self._raw_data[name] def refresh_from_server(self, api): - return api.get_pairing_by_id(self.id) + url = api.base_url + '/pairings/' + self.id + result = api._request(url, 'GET') + self.enabled = result['enabled'] + user = result['user'] + self.user_name = user['name'] class AuthenticationRequest(object): From 0616c0bcf97b082727965f485254ed8e8b9e33ae Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 15:21:59 -0600 Subject: [PATCH 026/242] Return UserTerminal instance from create_user_terminal --- tests.py | 20 ++++++++++++++------ toopher/__init__.py | 3 ++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tests.py b/tests.py index a7e1d9e..c3db876 100644 --- a/tests.py +++ b/tests.py @@ -314,15 +314,23 @@ def fn(): class ZeroStorageTests(unittest.TestCase): def test_create_user_terminal(self): api = toopher.ToopherApi('key', 'secret') - api.client = HttpClientMock({'user_terminals/create': (200, '{}')}) + api.client = HttpClientMock({ + 'user_terminals/create': (200, + json.dumps({ + 'id':'id', + 'name':'terminal_name', + 'name_extra':'requester_terminal_id', + 'user': {'name':'user_name', 'id':'id'} + }))}) - api.create_user_terminal('user_name', 'terminal_name', 'requester_terminal_id') + user_terminal = api.create_user_terminal('user_name', 'terminal_name', 'requester_terminal_id') - last_called_data = api.client.last_called_data self.assertEqual(api.client.last_called_method, 'POST') - self.assertEqual(last_called_data['user_name'], 'user_name') - self.assertEqual(last_called_data['name'], 'terminal_name') - self.assertEqual(last_called_data['name_extra'], 'requester_terminal_id') + self.assertEqual(user_terminal.id, 'id') + self.assertEqual(user_terminal.user_name, 'user_name') + self.assertEqual(user_terminal.user_id, 'id') + self.assertEqual(user_terminal.name, 'terminal_name') + self.assertEqual(user_terminal.name_extra, 'requester_terminal_id') def test_get_user_terminal_by_id(self): api = toopher.ToopherApi('key', 'secret') diff --git a/toopher/__init__.py b/toopher/__init__.py index 7d5b962..6e6971a 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -256,7 +256,8 @@ def create_user_terminal(self, user_name, terminal_name, requester_terminal_id): params = {'user_name': user_name, 'name': terminal_name, 'name_extra': requester_terminal_id} - self._request(url, 'POST', params) + result = self._request(url, 'POST', params) + return UserTerminal(result) def get_user_terminal_by_id(self, terminal_id): url = self.base_url + '/user_terminals/' + terminal_id From aad114e91241f6e4e065fe38ae8c9a58d7da7af6 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 15:37:26 -0600 Subject: [PATCH 027/242] Refactor enable_user and disable_user --- tests.py | 16 +++++++++++----- toopher/__init__.py | 19 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/tests.py b/tests.py index c3db876..0185b6e 100644 --- a/tests.py +++ b/tests.py @@ -349,17 +349,23 @@ def test_get_user_terminal_by_id(self): self.assertEqual(user_terminal.user_name, "some user") self.assertEqual(user_terminal.user_id, "1") - def test_enable_toopher_for_user(self): + def test_enable_toopher_user(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ 'users': (200, json.dumps([{'id': 'user_id', 'name': 'user_name'}])), 'users/user_id': (200, json.dumps({'name': 'user_name'}))}) - api.set_toopher_enabled_for_user('user_name', True) + api.enable_user('user_name') self.assertEqual(api.client.last_called_method, 'POST') self.assertFalse(api.client.last_called_data['disable_toopher_auth']) - api.set_toopher_enabled_for_user('user_name', False) + def test_disable_toopher_user(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'users': (200, json.dumps([{'id': 'user_id', 'name': 'user_name'}])), + 'users/user_id': (200, json.dumps({'name': 'user_name'}))}) + + api.disable_user('user_name') self.assertEqual(api.client.last_called_method, 'POST') self.assertTrue(api.client.last_called_data['disable_toopher_auth']) @@ -369,7 +375,7 @@ def test_enable_toopher_multiple_users(self): json.dumps([{'name': 'first user'}, {'name': 'second user'}]))}) def fn(): - api.set_toopher_enabled_for_user('multiple users', True) + api.enable_user('multiple users') self.assertRaises(toopher.ToopherApiError, fn) def test_enable_toopher_no_users(self): @@ -377,7 +383,7 @@ def test_enable_toopher_no_users(self): api.client = HttpClientMock({'users': (200, '[]')}) def fn(): - api.set_toopher_enabled_for_user('no users', True) + api.enable_user('no users') self.assertRaises(toopher.ToopherApiError, fn) diff --git a/toopher/__init__.py b/toopher/__init__.py index 6e6971a..7a2899b 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -265,20 +265,25 @@ def get_user_terminal_by_id(self, terminal_id): result = self._request(url, "GET") return UserTerminal(result) - - def set_toopher_enabled_for_user(self, user_name, enabled): - uri = self.base_url + '/users' + def _set_toopher_disabled_for_user(self, user_name, disable): + url = self.base_url + '/users' params = {'name': user_name} - users = self._request(uri, 'GET', params) + users = self._request(url, 'GET', params) if len(users) > 1: raise ToopherApiError('Multiple users with name = %s' % user_name) elif not len(users): raise ToopherApiError('No users with name = %s' % user_name) - uri = self.base_url + '/users/' + users[0]['id'] - params = {'disable_toopher_auth': not enabled} - self._request(uri, 'POST', params) + url = self.base_url + '/users/' + users[0]['id'] + params = {'disable_toopher_auth': disable} + self._request(url, 'POST', params) + + def enable_user(self, username): + self._set_toopher_disabled_for_user(username, False) + + def disable_user(self, username): + self._set_toopher_disabled_for_user(username, True) def _request(self, uri, method, params=None): data = {'params' if method == 'GET' else 'data': params} From a047ddaa5400cb6dc9387d97bf05a5f468092a6e Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 15:59:14 -0600 Subject: [PATCH 028/242] Add get_pairing_reset_link --- tests.py | 8 ++++++++ toopher/__init__.py | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/tests.py b/tests.py index 0185b6e..fad96c1 100644 --- a/tests.py +++ b/tests.py @@ -386,6 +386,14 @@ def fn(): api.enable_user('no users') self.assertRaises(toopher.ToopherApiError, fn) + def test_get_pairing_reset_link(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'pairings/1/generate_reset_link': (200, + json.dumps({'url': 'http://api.toopher.test/v1/pairings/1/reset?reset_authorization=abcde'}))}) + + reset_link = api.get_pairing_reset_link('1') + self.assertEqual('http://api.toopher.test/v1/pairings/1/reset?reset_authorization=abcde', reset_link) def test_disabled_user_raises_correct_error(self): diff --git a/toopher/__init__.py b/toopher/__init__.py index 7a2899b..76a1415 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -285,6 +285,16 @@ def enable_user(self, username): def disable_user(self, username): self._set_toopher_disabled_for_user(username, True) + def get_pairing_reset_link(self, pairing_id, **kwargs): + if not 'security_question' in kwargs: + kwargs['security_question'] = None + if not 'security_answer' in kwargs: + kwargs['security_answer'] = None + + url = self.base_url + '/pairings/' + pairing_id + '/generate_reset_link' + result = self._request(url, 'POST', kwargs) + return result['url'] + def _request(self, uri, method, params=None): data = {'params' if method == 'GET' else 'data': params} header_data = {'User-Agent':'Toopher-Python/%s (Python %s)' % (VERSION, sys.version.split()[0])} From ea710e8008f6fbb7ed0cdc346437f4334eada73a Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 16:54:09 -0600 Subject: [PATCH 029/242] Add email_pairing_reset_link_to_user --- tests.py | 8 ++++++++ toopher/__init__.py | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/tests.py b/tests.py index fad96c1..5a6bcc5 100644 --- a/tests.py +++ b/tests.py @@ -395,6 +395,14 @@ def test_get_pairing_reset_link(self): reset_link = api.get_pairing_reset_link('1') self.assertEqual('http://api.toopher.test/v1/pairings/1/reset?reset_authorization=abcde', reset_link) + def test_email_pairing_reset_link_to_user(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'pairings/1/send_reset_link': (201, '[]') + }) + + result = api.email_pairing_reset_link_to_user('1', 'email') + self.assertTrue(result) def test_disabled_user_raises_correct_error(self): api = toopher.ToopherApi('key', 'secret') diff --git a/toopher/__init__.py b/toopher/__init__.py index 76a1415..0d0b537 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -295,6 +295,13 @@ def get_pairing_reset_link(self, pairing_id, **kwargs): result = self._request(url, 'POST', kwargs) return result['url'] + def email_pairing_reset_link_to_user(self, pairing_id, email, **kwargs): + params = {'reset_email': email} + params.update(kwargs) + url = self.base_url + '/pairings/' + pairing_id + '/send_reset_link' + self._request(url, 'POST', params) + return True #would raise error in _request if failed + def _request(self, uri, method, params=None): data = {'params' if method == 'GET' else 'data': params} header_data = {'User-Agent':'Toopher-Python/%s (Python %s)' % (VERSION, sys.version.split()[0])} From 15aa2516e75621e047b3adf3e6c14bde1c2cee71 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 16:54:21 -0600 Subject: [PATCH 030/242] Add post and get --- tests.py | 36 ++++++++++++++++++++++++++++++++++++ toopher/__init__.py | 8 ++++++++ 2 files changed, 44 insertions(+) diff --git a/tests.py b/tests.py index 5a6bcc5..e23c7b0 100644 --- a/tests.py +++ b/tests.py @@ -404,6 +404,42 @@ def test_email_pairing_reset_link_to_user(self): result = api.email_pairing_reset_link_to_user('1', 'email') self.assertTrue(result) + def test_get(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'pairings/1': (200, + '{"id":"1", "enabled":true, "user":{"id":"1","name":"some user"}}')}) + result = api.get('/pairings/1') + self.assertEqual(api.client.last_called_method, 'GET') + + self.assertEqual(result['id'], '1') + self.assertEqual(result['user']['name'], 'some user') + self.assertEqual(result['user']['id'], '1') + self.assertTrue(result['enabled']) + + def test_post(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'user_terminals/create': (200, + json.dumps({ + 'id':'id', + 'name':'terminal_name', + 'name_extra':'requester_terminal_id', + 'user': {'name':'user_name', 'id':'id'} + }))}) + + result = api.post('/user_terminals/create', + name='terminal_name', + name_extra='requester_terminal_id', + user_name='user_name') + + self.assertEqual(api.client.last_called_method, 'POST') + self.assertEqual(result['id'], 'id') + self.assertEqual(result['user']['name'], 'user_name') + self.assertEqual(result['user']['id'], 'id') + self.assertEqual(result['name'], 'terminal_name') + self.assertEqual(result['name_extra'], 'requester_terminal_id') + def test_disabled_user_raises_correct_error(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ diff --git a/toopher/__init__.py b/toopher/__init__.py index 0d0b537..8218920 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -302,6 +302,14 @@ def email_pairing_reset_link_to_user(self, pairing_id, email, **kwargs): self._request(url, 'POST', params) return True #would raise error in _request if failed + def get(self, endpoint, **kwargs): + url = self.base_url + endpoint + return self._request(url, 'GET', kwargs) + + def post(self, endpoint, **kwargs): + url = self.base_url + endpoint + return self._request(url, 'POST', kwargs) + def _request(self, uri, method, params=None): data = {'params' if method == 'GET' else 'data': params} header_data = {'User-Agent':'Toopher-Python/%s (Python %s)' % (VERSION, sys.version.split()[0])} From be1639a6dd5d57cde1583ad25a6f52d198df37f3 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 17:12:23 -0600 Subject: [PATCH 031/242] Add User and create_user --- tests.py | 19 +++++++++++++++++++ toopher/__init__.py | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/tests.py b/tests.py index e23c7b0..aa35dc9 100644 --- a/tests.py +++ b/tests.py @@ -113,6 +113,19 @@ def test_version_number_in_setup(self): version_number = line.strip().replace("version='", "").replace("',", "") self.assertEqual(version_number, toopher.VERSION) + def test_create_user(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'users/create': (200, + '{"id":"1", "name":"name", "disable_toopher_auth": false }' + ) + }) + user = api.create_user('name') + self.assertEqual(api.client.last_called_method, 'POST') + self.assertEqual(user.id, '1') + self.assertEqual(user.name, 'name') + self.assertFalse(user.disable_toopher_auth) + def test_create_pairing(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ @@ -639,6 +652,12 @@ def test_refresh_from_server(self): self.assertEqual(user_terminal.user_name, "name changed") self.assertEqual(user_terminal.user_id, "id") +class UserTests(unittest.TestCase): + def test_incomplete_response_raises_exception(self): + response = {'key': 'value'} + def fn(): + toopher.User(response) + self.assertRaises(toopher.ToopherApiError, fn) def main(): unittest.main() diff --git a/toopher/__init__.py b/toopher/__init__.py index 8218920..f1e5aa8 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -251,6 +251,13 @@ def get_authentication_request_by_id(self, authentication_request_id): # kwargs.update(user_name=user_name, terminal_name_extra=terminal_name_extra) # return self.authenticate('', '', action_name, **kwargs) + def create_user(self, username, **kwargs): + url = self.base_url + '/users/create' + params = {'name': username} + params.update(kwargs) + result = self._request(url, 'POST', params) + return User(result) + def create_user_terminal(self, user_name, terminal_name, requester_terminal_id): url = self.base_url + '/user_terminals/create' params = {'user_name': user_name, @@ -440,5 +447,22 @@ def refresh_from_server(self, api): user = result["user"] self.user_name = user["name"] +class User(object): + def __init__(self, json_response): + try: + self.id = json_response['id'] + self.name = json_response['name'] + self.disable_toopher_auth = json_response['disable_toopher_auth'] + except Exception: + raise ToopherApiError("Could not parse user from response") + + self._raw_data = json_response + + def __getattr__(self, name): + if name.startswith('__') or name not in self._raw_data: # Exclude 'magic' methods to allow for (un)pickling + return super(User, self).__getattr__(name) + else: + return self._raw_data[name] + class ToopherApiError(Exception): pass From 3ac3e53d91b107259ee6928c9cad66eff25e66d3 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 17:14:57 -0600 Subject: [PATCH 032/242] Add get_user_by_id --- tests.py | 13 +++++++++++++ toopher/__init__.py | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/tests.py b/tests.py index aa35dc9..ad91bff 100644 --- a/tests.py +++ b/tests.py @@ -126,6 +126,19 @@ def test_create_user(self): self.assertEqual(user.name, 'name') self.assertFalse(user.disable_toopher_auth) + def test_get_user_by_id(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'users/1': (200, + '{"id":"1", "name":"name", "disable_toopher_auth": false }' + ) + }) + user = api.get_user_by_id('1') + self.assertEqual(api.client.last_called_method, 'GET') + self.assertEqual(user.id, '1') + self.assertEqual(user.name, 'name') + self.assertFalse(user.disable_toopher_auth) + def test_create_pairing(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ diff --git a/toopher/__init__.py b/toopher/__init__.py index f1e5aa8..839cea1 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -258,6 +258,11 @@ def create_user(self, username, **kwargs): result = self._request(url, 'POST', params) return User(result) + def get_user_by_id(self, user_id): + url = self.base_url + '/users/' + user_id + result = self._request(url, 'GET') + return User(result) + def create_user_terminal(self, user_name, terminal_name, requester_terminal_id): url = self.base_url + '/user_terminals/create' params = {'user_name': user_name, From e0e8167c33cfdf04100a2398ee14744444da601a Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 17:19:46 -0600 Subject: [PATCH 033/242] Add User.refresh_from_server --- tests.py | 18 ++++++++++++++++++ toopher/__init__.py | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/tests.py b/tests.py index ad91bff..0065b19 100644 --- a/tests.py +++ b/tests.py @@ -672,6 +672,24 @@ def fn(): toopher.User(response) self.assertRaises(toopher.ToopherApiError, fn) + def test_refresh_from_server(self): + response = {'id': 'id', + 'name': 'name', + 'disable_toopher_auth': False} + user = toopher.User(response) + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'users/{0}'.format(user.id): (200, + json.dumps({'id': 'id', + 'name':'name CHANGED', + 'disable_toopher_auth':'true'}))}) + user.refresh_from_server(api) + + self.assertEqual(api.client.last_called_method, 'GET') + self.assertEqual(user.id, 'id') + self.assertEqual(user.name, 'name CHANGED') + self.assertTrue(user.disable_toopher_auth) + def main(): unittest.main() diff --git a/toopher/__init__.py b/toopher/__init__.py index 839cea1..d9824d3 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -469,5 +469,11 @@ def __getattr__(self, name): else: return self._raw_data[name] + def refresh_from_server(self, api): + url = api.base_url + '/users/' + self.id + result = api._request(url, 'GET') + self.name = result['name'] + self.disable_toopher_auth = result['disable_toopher_auth'] + class ToopherApiError(Exception): pass From 62fc11ad578830e2773d138ff40a14450954aedc Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 17:26:57 -0600 Subject: [PATCH 034/242] Add User.enable and User.disable --- tests.py | 30 ++++++++++++++++++++++++++++++ toopher/__init__.py | 8 ++++++++ 2 files changed, 38 insertions(+) diff --git a/tests.py b/tests.py index 0065b19..5a0898b 100644 --- a/tests.py +++ b/tests.py @@ -690,6 +690,36 @@ def test_refresh_from_server(self): self.assertEqual(user.name, 'name CHANGED') self.assertTrue(user.disable_toopher_auth) + def test_enable(self): + response = {'id': 'id', + 'name': 'name', + 'disable_toopher_auth': True} + user = toopher.User(response) + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'users': (200, json.dumps([{'id': 'user_id', 'name': 'user_name'}])), + 'users/user_id': (200, json.dumps({'name': 'user_name'}))}) + user.enable(api) + + self.assertEqual(api.client.last_called_method, 'POST') + self.assertFalse(api.client.last_called_data['disable_toopher_auth']) + self.assertFalse(user.disable_toopher_auth) + + def test_disable(self): + response = {'id': 'id', + 'name': 'name', + 'disable_toopher_auth': False} + user = toopher.User(response) + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'users': (200, json.dumps([{'id': 'user_id', 'name': 'user_name'}])), + 'users/user_id': (200, json.dumps({'name': 'user_name'}))}) + user.disable(api) + + self.assertEqual(api.client.last_called_method, 'POST') + self.assertTrue(api.client.last_called_data['disable_toopher_auth']) + self.assertTrue(user.disable_toopher_auth) + def main(): unittest.main() diff --git a/toopher/__init__.py b/toopher/__init__.py index d9824d3..cfa3806 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -475,5 +475,13 @@ def refresh_from_server(self, api): self.name = result['name'] self.disable_toopher_auth = result['disable_toopher_auth'] + def enable(self, api): + api._set_toopher_disabled_for_user(self.name, False) + self.disable_toopher_auth = False + + def disable(self, api): + api._set_toopher_disabled_for_user(self.name, True) + self.disable_toopher_auth = True + class ToopherApiError(Exception): pass From a5bafd2c62998e590b7395a79d641a8f14fdc3a8 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 17:30:14 -0600 Subject: [PATCH 035/242] Rename user_name to username --- toopher/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/toopher/__init__.py b/toopher/__init__.py index cfa3806..850bb9c 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -263,11 +263,12 @@ def get_user_by_id(self, user_id): result = self._request(url, 'GET') return User(result) - def create_user_terminal(self, user_name, terminal_name, requester_terminal_id): + def create_user_terminal(self, username, terminal_name, requester_terminal_id, **kwargs): url = self.base_url + '/user_terminals/create' - params = {'user_name': user_name, + params = {'user_name': username, 'name': terminal_name, 'name_extra': requester_terminal_id} + params.update(kwargs) result = self._request(url, 'POST', params) return UserTerminal(result) From 24118795e624bbb0642be4224de5e65e8fe8897b Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Thu, 15 Jan 2015 17:38:38 -0600 Subject: [PATCH 036/242] Add reset_user and User.reset --- tests.py | 20 ++++++++++++++++++++ toopher/__init__.py | 9 +++++++++ 2 files changed, 29 insertions(+) diff --git a/tests.py b/tests.py index 5a0898b..ceb6d47 100644 --- a/tests.py +++ b/tests.py @@ -126,6 +126,14 @@ def test_create_user(self): self.assertEqual(user.name, 'name') self.assertFalse(user.disable_toopher_auth) + def test_reset_user(self): + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'users/reset': (200, '[]') + }) + result = api.reset_user('name') + self.assertTrue(result) + def test_get_user_by_id(self): api = toopher.ToopherApi('key', 'secret') api.client = HttpClientMock({ @@ -720,6 +728,18 @@ def test_disable(self): self.assertTrue(api.client.last_called_data['disable_toopher_auth']) self.assertTrue(user.disable_toopher_auth) + def test_reset(self): + response = {'id': 'id', + 'name': 'name', + 'disable_toopher_auth': False} + user = toopher.User(response) + api = toopher.ToopherApi('key', 'secret') + api.client = HttpClientMock({ + 'users/reset': (200, '[]') + }) + result = user.reset(api) + self.assertTrue(result) + def main(): unittest.main() diff --git a/toopher/__init__.py b/toopher/__init__.py index 850bb9c..c327985 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -258,6 +258,12 @@ def create_user(self, username, **kwargs): result = self._request(url, 'POST', params) return User(result) + def reset_user(self, username): + url = self.base_url + '/users/reset' + params = {'name': username} + self._request(url, 'POST', params) + return True # would raise error in _request if failed + def get_user_by_id(self, user_id): url = self.base_url + '/users/' + user_id result = self._request(url, 'GET') @@ -484,5 +490,8 @@ def disable(self, api): api._set_toopher_disabled_for_user(self.name, True) self.disable_toopher_auth = True + def reset(self, api): + return api.reset_user(self.name) + class ToopherApiError(Exception): pass From 05ff6d77821fa39bc9affc181209db65c0650469 Mon Sep 17 00:00:00 2001 From: Amanda Steinwedel Date: Fri, 16 Jan 2015 12:59:37 -0600 Subject: [PATCH 037/242] Update README.md --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b59a31a..bd6b853 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,18 @@ import toopher api = toopher.ToopherApi("", "") # Step 1 - Pair with their phone's Toopher app -pairing = api.pair("pairing phrase", "username@yourservice.com") +# With pairing phrase +pairing = api.pair("username@yourservice.com", "pairing phrase") +# With SMS (country_code optional) +pairing = api.pair("username@yourservice.com", "555-555-5555", country_code="1") +# With QR code +pairing = api.pair("username@yourservice.com") # Step 2 - Authenticate a log in +# With pairing_id auth = api.authenticate(pairing.id, "my computer") +# With username +auth = api.authenticate("username", "requester_terminal_id") # Once they've responded you can then check the status auth_request = api.get_authentication_request_by_id(auth.id) @@ -54,9 +62,9 @@ try: # optimistically try to authenticate against Toopher API with username and a Terminal Identifier # Terminal Identifer is typically a randomly generated secure browser cookie. It does not # need to be human-readable - auth = api.authenticate_by_user_name(user_name, requester_terminal_id) + auth = api.authenticate("username", "requester_terminal_id") - # if you got here, everything is good! poll the auth request status as described above + # if you got here, everything is good! poll the auth request as described above # there are four distinct errors ToopherAPI can return if it needs more data except UserDisabledError: # you have marked this user as disabled in the Toopher API. @@ -67,7 +75,7 @@ except TerminalUnknownError: # This user has not assigned a "Friendly Name" to this terminal identifier. # Prompt them to enter a terminal name, then submit that "friendly name" to # the Toopher API: - # api.create_user_terminal(user_name, terminal_name, requester_terminal_id) + # api.create_user_terminal("username", "terminal_name", "requester_terminal_id") # Afterwards, re-try authentication except PairingDeactivatedError: # this user does not have an active pairing, From e4daab17ef27c2e4f1053b1b84e13845181fb7a9 Mon Sep 17 00:00:00 2001 From: Amanda Steinwedel Date: Fri, 16 Jan 2015 13:10:24 -0600 Subject: [PATCH 038/242] Remove commented out code --- toopher/__init__.py | 68 --------------------------------------------- 1 file changed, 68 deletions(-) diff --git a/toopher/__init__.py b/toopher/__init__.py index c327985..5b66bef 100644 --- a/toopher/__init__.py +++ b/toopher/__init__.py @@ -78,24 +78,6 @@ def get_auth_url(self, username, reset_email, request_token, action_name='Log In return self._get_oauth_signed_url(self.base_uri + '/web/authenticate', params, ttl) - - # def auth_uri(self, username, reset_email, action_name, automation_allowed, challenge_required, request_token, requester_metadata, ttl=DEFAULT_IFRAME_TTL, allow_inline_pairing=True): - # params = { - # 'v':IFRAME_VERSION, - # 'username':username, - # 'reset_email':reset_email, - # 'action_name':action_name, - # 'automation_allowed':automation_allowed, - # 'challenge_required':challenge_required, - # 'session_token':request_token, - # 'requester_metadata':requester_metadata, - # 'allow_inline_pairing':allow_inline_pairing - # } - # return self._get_oauth_signed_url(self.base_uri + '/web/authenticate', params, ttl) - # - # def login_uri(self, username, reset_email, request_token, **kwargs): - # return self.auth_uri(username, reset_email, 'Log In', True, False, request_token, 'None', DEFAULT_IFRAME_TTL, **kwargs) - def validate_postback(self, data, request_token=None, **kwargs): if not 'ttl' in kwargs: ttl = DEFAULT_IFRAME_TTL @@ -173,34 +155,6 @@ def pair(self, username, phrase_or_num=None, **kwargs): result = self._request(url, "POST", params) return Pairing(result) - # def pair(self, pairing_phrase, user_name, **kwargs): - # uri = self.base_url + "/pairings/create" - # params = {'pairing_phrase': pairing_phrase, - # 'user_name': user_name} - # - # params.update(kwargs) - # - # result = self._request(uri, "POST", params) - # return Pairing(result) - # - # def pair_qr(self, user_name, **kwargs): - # uri = self.base_url + '/pairings/create/qr' - # params = {'user_name': user_name} - # params.update(kwargs) - # result = self._request(uri, 'POST', params) - # return Pairing(result) - # - # def pair_sms(self, phone_number, user_name, phone_country=None): - # uri = self.base_url + "/pairings/create/sms" - # params = {'phone_number': phone_number, - # 'user_name': user_name} - # - # if phone_country: - # params['phone_country'] = phone_country - # - # result = self._request(uri, "POST", params) - # return Pairing(result) - def get_pairing_by_id(self, pairing_id): url = self.base_url + "/pairings/" + pairing_id @@ -223,34 +177,12 @@ def authenticate(self, id_or_username, terminal, action_name=None, **kwargs): result = self._request(url, "POST", params) return AuthenticationRequest(result) - # def authenticate(self, pairing_id, terminal_name, action_name=None, **kwargs): - # uri = self.base_url + "/authentication_requests/initiate" - # params = {'pairing_id': pairing_id, - # 'terminal_name': terminal_name} - # if action_name: - # params['action_name'] = action_name - # - # params.update(kwargs) - # - # result = self._request(uri, "POST", params) - # return AuthenticationStatus(result) - def get_authentication_request_by_id(self, authentication_request_id): url = self.base_url + "/authentication_requests/" + authentication_request_id result = self._request(url, "GET") return AuthenticationRequest(result) - # def authenticate_with_otp(self, authentication_request_id, otp): - # uri = self.base_url + "/authentication_requests/" + authentication_request_id + '/otp_auth' - # params = {'otp' : otp} - # result = self._request(uri, "POST", params) - # return AuthenticationRequest(result) - - # def authenticate_by_user_name(self, user_name, terminal_name_extra, action_name=None, **kwargs): - # kwargs.update(user_name=user_name, terminal_name_extra=terminal_name_extra) - # return self.authenticate('', '', action_name, **kwargs) - def create_user(self, username, **kwargs): url = self.base_url + '/users/create' params = {'name': username} From 9cf10a41a805bc67963d1908a3d9e0b648ad2311 Mon Sep 17 00:00:00 2001 From: Amanda Steinwedel Date: Fri, 16 Jan 2015 13:17:46 -0600 Subject: [PATCH 039/242] Update README-Iframe.md --- README-Iframe.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README-Iframe.md b/README-Iframe.md index 8b89803..0261b74 100644 --- a/README-Iframe.md +++ b/README-Iframe.md @@ -18,12 +18,12 @@ We recommend using some form of primary authentication before initiating a Tooph ### Step 1: Embed a request in an ` ``` -There is no difference in the markup required for a Pairing vs. an Authentication iframe request (the generated URI embeds all relevant information). +There is no difference in the markup required for a Pairing vs. an Authentication iframe request (the generated URL embeds all relevant information). # Examples -#### Generating an Authentication iframe URI +#### Generating an Authentication iframe URL Every Toopher Authentication session should include a unique `request_token` - a randomized string that is included in the signed request to the Toopher API and returned in the signed response from the Toopher ` -``` - -There is no difference in the markup required for a Pairing vs. an Authentication iframe request (the generated URL embeds all relevant information). - -# Examples - -#### Generating an Authentication iframe URL -Every Toopher Authentication session should include a unique `request_token` - a randomized string that is included in the signed request to the Toopher API and returned in the signed response from the Toopher ` +``` + +There is no difference in the markup required for a Pairing vs. an Authentication iframe request (the generated URL embeds all relevant information). + +## Examples + +#### Generating an Authentication iframe URL +Every Toopher Authentication session should include a unique `request_token` - a randomized string that is included in the signed request to the Toopher API and returned in the signed response from the Toopher ` ``` -There is no difference in the markup required for a Pairing vs. an Authentication iframe request (the generated URL embeds all relevant information). +There is no difference in the markup required for a Pairing vs. an Authentication IFRAME request (the generated URL embeds all relevant information). -## Examples +### Examples -#### Generating an Authentication iframe URL -Every Toopher Authentication session should include a unique `request_token` - a randomized string that is included in the signed request to the Toopher API and returned in the signed response from the Toopher ` ``` -There is no difference in the markup required for a Pairing vs. an Authentication IFRAME request (the generated URL embeds all relevant information). +There is no difference in the markup required for an authentication vs. a user management IFRAME request (the generated URL embeds all relevant information). ### Examples @@ -185,9 +185,10 @@ In this example, `data` is a `dict` of the form data POSTed to your server from There are two ways to validate postback data: +#####Process Postback ```python try: - # Try to process the postback from the Toopher Authentication IFRAME + # Try to process the postback from the Toopher IFRAME # and receive an AuthenticationRequest object. authentication_request = iframe_api.process_postback(form_data) @@ -205,6 +206,7 @@ except toopher.ToopherApiError as e: # The postback resource type was not valid. ``` +#####Check AuthenticationRequest Status ```python # returns boolean indicating if user should be granted access authentication_request_granted = iframe_api.is_authentication_granted(form_data) @@ -213,8 +215,8 @@ if authentication_request_granted: # Success! ``` -#### Generating a Pairing IFRAME URL -The Toopher Authentication API provides the requester a rich set of controls over pairing parameters. +#### Generating a User Management IFRAME URL +The Toopher Authentication API provides the requester a rich set of controls over user management parameters. ```python # optional parameter: reset_email @@ -230,12 +232,17 @@ pair_iframe_url = iframe_api.get_user_management_url(username) #### Validating Postback Data from Pairing IFRAME & Parsing Errors In this example, `data` is a `dict` of the form data POSTed to your server from the Toopher Pairing IFRAME. You should replace the commented blocks in the `except` with code appropriate for the condition described in the comment. +#####Process Postback ```python try: - # Try to process the postback from the Toopher Authentication IFRAME + # Try to process the postback from the Toopher IFRAME # and receive a Pairing or User object. postback_object = iframe_api.process_postback(form_data) + # If you got here and you have a pairing object, you just need to check the status of the pairing. + if postback_object.enabled: + #Success! + # There are three distinct errors ToopherAPI can return if the postback is not valid. except toopher.UserDisableError as e: # You have marked this user as Toopher disabled in the Toopher API From c410e78147ad21cfd98369e1a7ecc5b42bf1ed25 Mon Sep 17 00:00:00 2001 From: Grace Yim Date: Mon, 9 Mar 2015 10:04:05 -0500 Subject: [PATCH 201/242] Reformat text and subheaders --- README.md | 115 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 32f1a6d..9dfee91 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ First off, to access the Toopher API you'll need to sign up for an account at th ### The Toopher Two-Step Interacting with the Toopher web service involves two steps: pairing, and authenticating. +#### Step 0: Create an API Object +Using your consumer key and consumer secret, create a ToopherApi object which will be used to generate an authentication or user management url and process the postback data. + #### Step 1: Pair Before you can enhance your website's actions with Toopher, your customers will need to pair their phone's Toopher app with your website. To do this, they generate a unique, nonsensical "pairing phrase" from within the app on their phone. You will need to prompt them for a pairing phrase as part of the Toopher enrollment process. Once you have a pairing phrase, just send it to the Toopher web service and we'll return a pairing ID that you can use whenever you want to authenticate an action for that user. @@ -26,7 +29,7 @@ This library makes it super simple to do the Toopher two-step. Check it out: ```python from toopher import * -# Create an API Object Using Your Credentials +# Step 0 - Create an API Object Using Your Credentials api = toopher.ToopherApi("", "") # Step 1 - Pair with their phone's Toopher app @@ -106,60 +109,58 @@ $ nosetests test ``` #Using the Toopher IFRAME -Toopher's IFRAME-based authentication flow is the simplest way for web developers to integrate Toopher Two-Factor Authentication into an application. +Toopher's IFRAME-based flow is the simplest way for web developers to integrate Toopher Two-Factor Authentication into an application. + +### Toopher IFRAME Overview + +The IFRAME-based flow works by inserting an ` +``` ### Authentication IFRAME Workflow #### Primary Authentication We recommend using some form of primary authentication before initiating a Toopher authentication request. Typical primary authentication methods involve verifying that the user has a valid username and password to access the resource being protected. #### Step 1: Embed a request in an IFRAME -After verifying the user's primary authentication, but before Assuming the user's primary authentication checks out, the next step is to kickoff Toopher authentication. +After verifying the user's primary authentication, but before assuming the user's primary authentication checks out, the next step is to kickoff Toopher authentication. + +1. Generate an authentication URL by specifying the request parameters to the library as detailed below +2. Display a webpage to your user that embeds this URL within an ` -``` - -There is no difference in the markup required for an authentication vs. a user management IFRAME request (the generated URL embeds all relevant information). - -### Examples - -#### Generating an Authentication IFRAME URL -Every Toopher Authentication session should include a unique `request_token` - a randomized string that is included in the signed request to the Toopher API and returned in the signed response from the Toopher IFRAME. To guard against potential replay attacks, your code should validate that the returned `request_token` is the same one used to create the request. - -Creating a random request token and storing it in the server-side session using Django: +Here's an example of creating a random request token and storing it in the server-side session using Django: ```python import random, string @@ -170,7 +171,7 @@ request.session['ToopherRequestToken'] = request_token The Toopher Authentication API provides the requester a rich set of controls over authentication parameters. ```python -# optional parameters: reset_email, request_token, action_name, requester_metadata, automation_allowed, challenge_required, and ttl +# Optional parameters: reset_email, request_token, action_name, requester_metadata, automation_allowed, challenge_required, and ttl auth_iframe_url = iframe_api.get_authentication_url(username, reset_email='email', request_token='token', action_name='action', requester_metadata, automation_allowed=automation_allowed, challenge_required=challenge_required, ttl=ttl); ``` @@ -180,16 +181,15 @@ For the simple case of authenticating a user at login, use `get_authentication_u login_iframe_url = iframe_api.get_authentication_url(username) ``` -#### Validating Postback Data from Authentication IFRAME & Parsing Errors +##### Validating Postback Data from Authentication IFRAME & Parsing Errors In this example, `data` is a `dict` of the form data POSTed to your server from the Toopher Authentication IFRAME. You should replace the commented blocks in the `except` with code appropriate for the condition described in the comment. -There are two ways to validate postback data: +There are two ways to validate postback data from an authentication IFRAME: -#####Process Postback +######Process Postback ```python try: - # Try to process the postback from the Toopher IFRAME - # and receive an AuthenticationRequest object. + # Try to process the postback from the Toopher IFRAME and receive an AuthenticationRequest object. authentication_request = iframe_api.process_postback(form_data) # If you got here, you just need to check the status of the authentication request. @@ -206,37 +206,56 @@ except toopher.ToopherApiError as e: # The postback resource type was not valid. ``` -#####Check AuthenticationRequest Status +######Check Authentication Request Status ```python -# returns boolean indicating if user should be granted access +# Returns boolean indicating if user should be granted access authentication_request_granted = iframe_api.is_authentication_granted(form_data) if authentication_request_granted: # Success! ``` -#### Generating a User Management IFRAME URL +###User Management IFRAME Workflow +#### Primary Authentication +We recommend using some form of primary authentication before initiating a Toopher pairing. Typical primary authentication methods involve verifying that the user has a valid username and password to access the resource being protected. + +#### Step 1: Embed a request in an IFRAME +After verifying the user's primary authentication, the next step is to kickoff Toopher pairing. + +1. Generate a user management URL by specifying the request parameters to the library as detailed below +2. Display a webpage to your user that embeds this URL within an ` +if authentication_request_granted: + # Success! ``` -### Authentication IFRAME Workflow -#### Primary Authentication -We recommend using some form of primary authentication before initiating a Toopher authentication request. Typical primary authentication methods involve verifying that the user has a valid username and password to access the resource being protected. - -#### Step 1: Embed a request in an IFRAME -After verifying the user's primary authentication, but before assuming the user's primary authentication checks out, the next step is to kickoff Toopher authentication. - -1. Generate an authentication URL by specifying the request parameters to the library as detailed below -2. Display a webpage to your user that embeds this URL within an `