def test_handler(self, qh_mock): duo_test = duo.Duo(DETAILS, 'token') duo_test.handler_with_html('foo', 'bar', 'baz') assert qh_mock.called qh_mock.assert_has_calls([mock.call(None, 'foo', 'bar', 'baz')])
def test_do_auth_302_success(self): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() duo_test.session.params = mock.MagicMock() duo_test.session.post.return_value = mock.MagicMock() duo_test.session.post.return_value.status_code = 302 duo_test.session.post.return_value.headers = { "Location": "https://someurl/foo?sid=somesid", } ret = duo_test.do_auth("sid", "certs_url") self.assertEqual( duo_test.session.params, { "certs_url": "certs_url", "sid": "sid" }, ) self.assertEqual( duo_test.session.headers, { "Origin": "https://somehost", "Content-Type": "application/x-www-form-urlencoded", }, ) duo_test.session.assert_has_calls([ mock.call.post( ("https://somehost/frame/web/v1/auth?tx=somesig&parent=" "http://0.0.0.0:3000/duo&v=2.1"), allow_redirects=False, ), ], ) self.assertEqual(ret, "somesid")
def test_do_auth_302_success(self): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() duo_test.session.params = mock.MagicMock() duo_test.session.post.return_value = mock.MagicMock() duo_test.session.post.return_value.status_code = 302 duo_test.session.post.return_value.headers = { 'Location': 'https://someurl/foo?sid=somesid' } ret = duo_test.do_auth('sid', 'certs_url') self.assertEqual(duo_test.session.params, { 'certs_url': 'certs_url', 'sid': 'sid' }) self.assertEqual( duo_test.session.headers, { 'Origin': 'https://somehost', 'Content-Type': 'application/x-www-form-urlencoded' }) duo_test.session.assert_has_calls([ mock.call.post( ('https://somehost/frame/web/v1/auth?tx=somesig&parent=' 'http://0.0.0.0:3000/duo&v=2.1'), allow_redirects=False) ]) self.assertEqual(ret, 'somesid')
def setup_for_trigger_duo(self, factor): self.duo_test = duo.Duo(DETAILS, 'token', factor) self.duo_test.do_auth = mock.MagicMock() self.duo_test.do_auth.return_value = "sid" self.duo_test.get_txid = mock.MagicMock() self.duo_test.get_txid.return_value = 'txid' self.duo_test.get_status = mock.MagicMock() self.duo_test.get_status.return_value = 'auth'
def test_do_auth_500(self): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() duo_test.session.params = mock.MagicMock() duo_test.session.post.return_value = mock.MagicMock() duo_test.session.post.return_value.status_code = 500 with self.assertRaises(Exception): duo_test.do_auth('sid', 'certs_url')
def test_handler(self, qh_mock): duo_test = duo.Duo(DETAILS, "token") duo_test.handler_with_html("foo", "bar", "baz") assert qh_mock.called qh_mock.assert_has_calls([ mock.call(None, "foo", "bar", "baz"), ], )
def test_duo_webserver(self, server_mock): server_mock.return_value = mock.MagicMock() duo_test = duo.Duo(DETAILS, 'token') duo_test.duo_webserver() server_mock.assert_has_calls([ mock.call(('127.0.0.1', 65432), duo_test.handler_with_html), mock.call().serve_forever() ])
def test_do_redirect_missing_cookie(self): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() json_ok = {'response': {'crumbs': 'yum'}, 'stat': 'OK'} headers = {'Location': 'https://someurl/foo?sid=somesid'} duo_test.session.post.return_value = MockResponse( headers, 200, json_ok) ret = duo_test.do_redirect('url', 'sid') self.assertEqual(ret, None)
def test_do_auth_302_location_missing(self): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() duo_test.session.params = mock.MagicMock() duo_test.session.post.return_value = mock.MagicMock() duo_test.session.post.return_value.status_code = 302 duo_test.session.post.return_value.headers = {} with self.assertRaises(Exception): duo_test.do_auth('sid', 'certs_url')
def test_trigger_duo(self, process_mock, _sleep_mock): process_mock.start.return_value = None duo_test = duo.Duo(DETAILS, 'token') duo_test.trigger_duo() process_mock.assert_has_calls([ mock.call().start(), mock.call().terminate(), ])
def test_do_redirect_failure(self): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() json_ok = {'response': {'cookie': 'yum'}, 'stat': 'OK'} headers = {'Location': 'https://someurl/foo?sid=somesid'} duo_test.session.post.return_value = MockResponse( headers, 500, json_ok) with self.assertRaises(Exception): duo_test.do_redirect('url', 'sid')
def test_do_auth_200(self): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() duo_test.session.params = mock.MagicMock() json = {'response': {'sid': 'sid', 'certs_url': 'certs_url'}} headers = {'Location': 'https://someurl/foo?sid=somesid'} duo_test.session.post.side_effect = [ MockResponse(headers, 200, json), MockResponse(headers, 302, json)] ret = duo_test.do_auth(None, 'certs_url') self.assertEqual(ret, 'somesid')
def test_get_status_timeout(self, _sleep_mock): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() duo_test.do_redirect = mock.MagicMock() json_wait = {'stat': 'WAIT'} headers = {'Location': 'https://someurl/foo?sid=somesid'} duo_test.session.post.return_value = MockResponse( headers, 200, json_wait) with self.assertRaises(Exception): duo_test.get_status('txid', 'sid')
def test_do_redirect_success(self): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() json_ok = {'response': {'cookie': 'yum'}, 'stat': 'OK'} headers = {'Location': 'https://someurl/foo?sid=somesid'} duo_test.session.post.return_value = MockResponse( headers, 200, json_ok) ret = duo_test.do_redirect('url', 'sid') duo_test.session.assert_has_calls([ mock.call.post('https://somehosturl?sid=sid')]) self.assertEqual(ret, 'yum')
def test_get_txid_without_passcode(self): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() json = {'response': {'txid': 'txid'}} headers = {'Location': 'https://someurl/foo?sid=somesid'} duo_test.session.post.return_value = MockResponse(headers, 200, json) ret = duo_test.get_txid('sid', 'factor') duo_test.session.assert_has_calls([ mock.call.post(('https://somehost/frame/prompt?sid=sid&device=' 'phone1&factor=factor&out_of_date=False'))]) self.assertEqual(ret, 'txid')
def test_get_status_redirect(self, _sleep_mock): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() duo_test.do_redirect = mock.MagicMock() json_wait = {'stat': 'WAIT'} json_ok = {'response': {'result_url': 'url'}, 'stat': 'OK'} headers = {'Location': 'https://someurl/foo?sid=somesid'} duo_test.session.post.side_effect = [ MockResponse(headers, 200, json_wait), MockResponse(headers, 200, json_ok)] duo_test.get_status('txid', 'sid') duo_test.do_redirect.assert_has_calls([mock.call('url', 'sid')])
def test_do_auth_200(self): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() duo_test.session.params = mock.MagicMock() json = {"response": {"sid": "sid", "certs_url": "certs_url"}} headers = {"Location": "https://someurl/foo?sid=somesid"} duo_test.session.post.side_effect = [ MockResponse(headers, 200, json), MockResponse(headers, 302, json), ] ret = duo_test.do_auth(None, "certs_url") self.assertEqual(ret, "somesid")
def test_do_redirect_failure(self): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() json_ok = {"response": {"cookie": "yum"}, "stat": "OK"} headers = {"Location": "https://someurl/foo?sid=somesid"} duo_test.session.post.return_value = MockResponse( headers, 500, json_ok, ) with self.assertRaises(Exception): duo_test.do_redirect("url", "sid")
def test_do_redirect_missing_cookie(self): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() json_ok = {"response": {"crumbs": "yum"}, "stat": "OK"} headers = {"Location": "https://someurl/foo?sid=somesid"} duo_test.session.post.return_value = MockResponse( headers, 200, json_ok, ) ret = duo_test.do_redirect("url", "sid") self.assertEqual(ret, None)
def test_get_status_redirect(self, _sleep_mock): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() duo_test.do_redirect = mock.MagicMock() json_wait = {"stat": "WAIT"} json_ok = {"response": {"result_url": "url"}, "stat": "OK"} headers = {"Location": "https://someurl/foo?sid=somesid"} duo_test.session.post.side_effect = [ MockResponse(headers, 200, json_wait), MockResponse(headers, 200, json_ok), ] duo_test.get_status("txid", "sid") duo_test.do_redirect.assert_has_calls([mock.call("url", "sid")])
def test_get_status_timeout(self, _sleep_mock): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() duo_test.do_redirect = mock.MagicMock() json_wait = {"stat": "WAIT"} headers = {"Location": "https://someurl/foo?sid=somesid"} duo_test.session.post.return_value = MockResponse( headers, 200, json_wait, ) with self.assertRaises(Exception): duo_test.get_status("txid", "sid")
def test_get_txid_without_passcode(self): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() json = {"response": {"txid": "txid"}} headers = {"Location": "https://someurl/foo?sid=somesid"} duo_test.session.post.return_value = MockResponse(headers, 200, json) ret = duo_test.get_txid("sid", "factor") duo_test.session.assert_has_calls([ mock.call.post( "https://somehost/frame/prompt?sid=sid&device=" "phone1&factor=factor&out_of_date=False", ), ], ) self.assertEqual(ret, "txid")
def test_get_status_success(self, _sleep_mock): duo_test = duo.Duo(DETAILS, 'token') duo_test.session = mock.MagicMock() json_wait = {'stat': 'WAIT'} json_ok = {'response': {'cookie': 'yum'}, 'stat': 'OK'} headers = {'Location': 'https://someurl/foo?sid=somesid'} duo_test.session.post.side_effect = [ MockResponse(headers, 200, json_wait), MockResponse(headers, 200, json_ok)] ret = duo_test.get_status('txid', 'sid') duo_test.session.assert_has_calls([ mock.call.post('https://somehost/frame/status?sid=sid&txid=txid'), mock.call.post('https://somehost/frame/status?sid=sid&txid=txid')]) self.assertEqual(ret, 'yum')
def test_do_redirect_success(self): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() json_ok = {"response": {"cookie": "yum"}, "stat": "OK"} headers = {"Location": "https://someurl/foo?sid=somesid"} duo_test.session.post.return_value = MockResponse( headers, 200, json_ok, ) ret = duo_test.do_redirect("url", "sid") duo_test.session.assert_has_calls([ mock.call.post("https://somehosturl?sid=sid"), ], ) self.assertEqual(ret, "yum")
def test_get_status_success(self, _sleep_mock): duo_test = duo.Duo(DETAILS, "token") duo_test.session = mock.MagicMock() json_wait = {"stat": "WAIT"} json_ok = {"response": {"cookie": "yum"}, "stat": "OK"} headers = {"Location": "https://someurl/foo?sid=somesid"} duo_test.session.post.side_effect = [ MockResponse(headers, 200, json_wait), MockResponse(headers, 200, json_ok), ] ret = duo_test.get_status("txid", "sid") duo_test.session.assert_has_calls([ mock.call.post( "https://somehost/frame/status?sid=sid&txid=txid", ), mock.call.post( "https://somehost/frame/status?sid=sid&txid=txid", ), ], ) self.assertEqual(ret, "yum")
def duo_auth(self, fid, state_token, passcode=None): """Trigger a Duo Auth request. This method is meant to be called by self.auth() if a Login session requires MFA, and the users profile supports Duo. If web is requested we set up a local web server for web auth and then open a browser for the user. This is going to be left in place in case in the future Duo breaks the current method for getting around the web version. If web is not requested we will try to fake out Duo to move ahead with MFA without needing to use their iframe format. In either case we then immediately go into a wait loop. Each time we loop around, we pull the latest status for that push event. If it's declined we will throw an error. If its accepted, we write out our SessionToken. Args: fid: Okta Factor ID used to trigger the push state_token: State Token allowing us to trigger the push passcode: OTP passcode string Returns: Dict (JSON) of API response for the MFA status if successful otherwise None """ if self.duo_factor is None: # Prompt user for which Duo factor to use raise duo.FactorRequired(id, state_token) if self.duo_factor == "passcode" and not passcode: raise duo.PasscodeRequired(fid, state_token) path = '/authn/factors/{fid}/verify'.format(fid=fid) data = {'fid': fid, 'stateToken': state_token} ret = self._request(path, data) verification = ret['_embedded']['factor']['_embedded']['verification'] auth = None duo_client = duo.Duo(verification, state_token, self.duo_factor) if self.duo_factor == "web": # Duo Web via local browser LOG.warning('Duo required; opening browser...') proc = Process(target=duo_client.trigger_web_duo) proc.start() time.sleep(2) webbrowser.open_new('http://127.0.0.1:65432/duo.html') elif self.duo_factor == "passcode": # Duo auth with OTP code without a browser LOG.warning('Duo required; using OTP...') auth = duo_client.trigger_duo(passcode=passcode) else: # Duo Auth without the browser LOG.warning('Duo required; check your phone... 📱') auth = duo_client.trigger_duo() if auth is not None: self.mfa_callback(auth, verification, state_token) ret = self.mfa_wait_loop(ret, data) if ret: self.set_token(ret) return True return None
def test_init_with_args(self): duo_test = duo.Duo(DETAILS, 'token') self.assertEqual(duo_test.details, DETAILS) self.assertEqual(duo_test.token, 'token')
def test_init_missing_args(self): with self.assertRaises(TypeError): # noinspection PyArgumentList duo.Duo()
def test_init_missing_args(self): with self.assertRaises(TypeError): duo.Duo()