Skip to content

Commit

Permalink
Merge pull request #409 from Nike-Inc/master
Browse files Browse the repository at this point in the history
Merge changes from Master into dev
  • Loading branch information
epierce authored Jun 5, 2023
2 parents 5a7c20d + 1019a08 commit b67476f
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ A configuration wizard will prompt you to enter the necessary configuration para
- token:hardware - OTP using hardware like Yubikey
- call - OTP via Voice call
- sms - OTP via SMS message
- email - OTP via email
- web - DUO uses localhost webbrowser to support push|call|passcode
- passcode - DUO uses `OKTA_MFA_CODE` or `--mfa-code` if set, or prompts user for passcode(OTP).
Expand Down
2 changes: 1 addition & 1 deletion gimme_aws_creds/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,6 @@ def update_config_file(self):
'preferred_mfa_type': '',
'remember_device': 'n',
'aws_default_duration': '3600',
'device_token': '',
'output_format': 'export',
'force_classic': '',
'open_browser': ''
Expand Down Expand Up @@ -558,6 +557,7 @@ def _get_preferred_mfa_type(self, default_entry):
- token:hardware - OTP using hardware like Yubikey
- call - OTP via Voice call
- sms - OTP via SMS message
- email - OTP via email message
- web - DUO uses localhost webbrowser to support push|call|passcode
- passcode - DUO uses `OKTA_MFA_CODE` or `--mfa-code` if set, or prompts user for passcode(OTP).
"""
Expand Down
2 changes: 1 addition & 1 deletion gimme_aws_creds/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ def handle_action_store_json_creds(self, stream=None):

def handle_action_register_device(self):
# Capture the Device Token and write it to the config file
if self.okta_platform == "classic" and ( self.device_token is None or self.config.action_register_device is True ):
if self.okta_platform == "classic" and ( not self.device_token or self.config.action_register_device is True ):
if not self.config.action_register_device:
self.ui.notify('\n*** No device token found in configuration file, it will be created.')
self.ui.notify('*** You may be prompted for MFA more than once for this run.\n')
Expand Down
23 changes: 23 additions & 0 deletions gimme_aws_creds/okta_classic.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,25 @@ def _login_send_sms(self, state_token, factor):
if 'sessionToken' in response_data:
return {'stateToken': None, 'sessionToken': response_data['sessionToken'], 'apiResponse': response_data}

def _login_send_email(self, state_token, factor):
""" Send email message for second factor authentication"""
response = self._http_client.post(
factor['_links']['verify']['href'],
params={'rememberDevice': self._remember_device},
json={'stateToken': state_token},
headers=self._get_headers(),
verify=self._verify_ssl_certs
)
response.raise_for_status()

self.ui.info("A verification code has been sent to " + factor['profile']['email'])
response_data = response.json()

if 'stateToken' in response_data:
return {'stateToken': response_data['stateToken'], 'apiResponse': response_data}
if 'sessionToken' in response_data:
return {'stateToken': None, 'sessionToken': response_data['sessionToken'], 'apiResponse': response_data}

def _login_send_call(self, state_token, factor):
""" Send Voice call for second factor authentication"""
response = self._http_client.post(
Expand Down Expand Up @@ -561,6 +580,8 @@ def _login_multi_factor(self, state_token, login_data):
return self._login_send_sms(state_token, factor)
elif factor['factorType'] == 'call':
return self._login_send_call(state_token, factor)
elif factor['factorType'] == 'email':
return self._login_send_email(state_token, factor)
elif factor['factorType'] == 'token:software:totp':
return self._login_input_mfa_challenge(state_token, factor['_links']['verify']['href'])
elif factor['factorType'] == 'token':
Expand Down Expand Up @@ -841,6 +862,8 @@ def _build_factor_name(self, factor):
return "Okta Verify App: " + factor['profile']['deviceType'] + ": " + factor['profile']['name']
elif factor['factorType'] == 'sms':
return factor['factorType'] + ": " + factor['profile']['phoneNumber']
elif factor['factorType'] == 'email':
return factor['factorType'] + ": " + factor['profile']['email']
elif factor['factorType'] == 'call':
return factor['factorType'] + ": " + factor['profile']['phoneNumber']
elif factor['factorType'] == 'token:software:totp':
Expand Down
124 changes: 123 additions & 1 deletion tests/test_okta_classic_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ def setUp(self):
}
}

self.email_factor = {
"id": "ema9hmdk2qvhjOQQ30h7",
"factorType": "email",
"provider": "OKTA",
"vendorName": "OKTA",
"profile": {
"email": "[email protected]"
},
"_links": {
"verify": {
"href": "https://example.okta.com/api/v1/authn/factors/ema9hmdk2qvhjOQQ30h7/verify",
"hints": {
"allow": [
"POST"
]
}
}
}
}

self.push_factor = {
"id": "opf9ei43pbAgb2qgc0h7",
"factorType": "push",
Expand Down Expand Up @@ -194,7 +214,7 @@ def setUp(self):
</html>"""

self.factor_list = [self.sms_factor, self.push_factor, self.totp_factor, self.webauthn_factor]
self.factor_list = [self.sms_factor, self.push_factor, self.totp_factor, self.webauthn_factor, self.email_factor]

def setUp_client(self, okta_org_url, verify_ssl_certs):
client = OktaClassicClient(ui.default, okta_org_url, verify_ssl_certs)
Expand Down Expand Up @@ -423,6 +443,97 @@ def test_login_send_sms(self):
result = self.client._login_send_sms(self.state_token, self.sms_factor)
self.assertEqual(result, {'stateToken': self.state_token, 'apiResponse': verify_response})

@responses.activate
def test_login_send_email(self):
"""Test that email messages can be requested for MFA"""

verify_response = {
"stateToken": "00Wf8xZJ79mSoTYnJqXbvRegT8QB1EX1IBVk1TU7KI",
"type": "SESSION_STEP_UP",
"expiresAt": "2017-06-15T15:06:10.000Z",
"status": "MFA_CHALLENGE",
"_embedded": {
"user": {
"id": "00u8cakq7vQwtK7sR0h7",
"profile": {
"login": "[email protected]",
"firstName": "Jane",
"lastName": "Doe",
"locale": "en",
"timeZone": "America/Los_Angeles"
}
},
"factor": {
"id": "ema9hmdk2qvhjOQQ30h7",
"factorType": "email",
"provider": "OKTA",
"vendorName": "OKTA",
"profile": {
"email": "[email protected]"
}
},
"policy": {
"allowRememberDevice": False,
"rememberDeviceLifetimeInMinutes": 0,
"rememberDeviceByDefault": False
},
"target": {
"type": "APP",
"name": "gimmecredsserver",
"label": "Gimme-Creds-Server (Dev)",
"_links": {
"logo": {
"name": "medium",
"href": "https://op1static.oktacdn.com/bc/globalFileStoreRecord?id=gfsatgifysE8NG37F0h7",
"type": "image/png"
}
}
}
},
"_links": {
"next": {
"name": "verify",
"href": "https://example.okta.com/api/v1/authn/factors/ema9hmdk2qvhjOQQ30h7/verify",
"hints": {
"allow": [
"POST"
]
}
},
"cancel": {
"href": "https://example.okta.com/api/v1/authn/cancel",
"hints": {
"allow": [
"POST"
]
}
},
"prev": {
"href": "https://example.okta.com/api/v1/authn/previous",
"hints": {
"allow": [
"POST"
]
}
},
"resend": [
{
"name": "sms",
"href": "https://example.okta.com/api/v1/authn/factors/ema9hmdk2qvhjOQQ30h7/verify/resend",
"hints": {
"allow": [
"POST"
]
}
}
]
}
}

responses.add(responses.POST, 'https://example.okta.com/api/v1/authn/factors/ema9hmdk2qvhjOQQ30h7/verify', status=200, body=json.dumps(verify_response))
result = self.client._login_send_email(self.state_token, self.email_factor)
self.assertEqual(result, {'stateToken': self.state_token, 'apiResponse': verify_response})

@responses.activate
def test_login_send_push(self):
"""Test that Okta Verify can be used for MFA"""
Expand Down Expand Up @@ -996,6 +1107,12 @@ def test_choose_factor_totp(self, mock_input):
result = self.client._choose_factor(self.factor_list)
self.assertEqual(result, self.totp_factor)

@patch('builtins.input', return_value='4')
def test_choose_factor_totp(self, mock_input):
""" Test selecting email as a MFA"""
result = self.client._choose_factor(self.factor_list)
self.assertEqual(result, self.email_factor)

@patch('builtins.input', return_value='12')
def test_choose_bad_factor_totp(self, mock_input):
""" Test selecting an invalid MFA factor"""
Expand All @@ -1019,6 +1136,11 @@ def test_build_factor_name_sms(self):
result = self.client._build_factor_name(self.sms_factor)
self.assertEqual(result, "sms: +1 XXX-XXX-1234")

def test_build_factor_name_email(self):
""" Test building a display name for email"""
result = self.client._build_factor_name(self.email_factor)
self.assertEqual(result, "email: [email protected]")

def test_build_factor_name_push(self):
""" Test building a display name for push"""
result = self.client._build_factor_name(self.push_factor)
Expand Down

0 comments on commit b67476f

Please sign in to comment.