def test_class_properties(self): error_response = None try: raise duo.PasscodeRequired('factor', 'state_token') except duo.PasscodeRequired as err: error_response = err self.assertEqual(error_response.factor, 'factor') self.assertEqual(error_response.state_token, 'state_token')
def test_class_properties(self): error_response = None try: raise duo.PasscodeRequired("factor", "state_token") except duo.PasscodeRequired as err: error_response = err self.assertEqual(error_response.factor, "factor") self.assertEqual(error_response.state_token, "state_token")
def test_auth_okta_duo_mfa_passcode(self, _config_mock): keyman = Keyman(['foo', '-o', 'foo', '-u', 'bar', '-a', 'baz']) keyman.okta_client = mock.MagicMock() keyman.okta_client.auth.side_effect = duo.PasscodeRequired('a', 'b') keyman.okta_client.duo_auth.return_value = True keyman.user_input = mock.MagicMock() keyman.user_input.return_value = '000000' keyman.auth_okta() keyman.okta_client.duo_auth.assert_has_calls([ mock.call('a', 'b', '000000'), ])
def test_auth_okta_duo_mfa_passcode(self, _config_mock): keyman = Keyman(["foo", "-o", "foo", "-u", "bar", "-a", "baz"]) keyman.okta_client = mock.MagicMock() keyman.okta_client.auth.side_effect = duo.PasscodeRequired("a", "b") keyman.okta_client.duo_auth.return_value = True keyman.user_input = mock.MagicMock() keyman.user_input.return_value = "000000" keyman.auth_okta() keyman.okta_client.duo_auth.assert_has_calls( [ mock.call("a", "b", "000000"), ], )
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