-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Okta Verify with Push Feature (#8)
* Add Okta Push feature * Requires more testing and dynamic switch for factor_type=sms | push * Add environment variable for Twilio and App * Update, simplify tree base on factor preference
- Loading branch information
1 parent
81a528a
commit b76b97e
Showing
9 changed files
with
312 additions
and
456 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,12 @@ | ||
OKTA_API_TOKEN=_GET_FROM_OKTA | ||
OKTA_ORG_URL=https://{_REPLACE_ME_}.okta.com | ||
OKTA_API_TOKEN=_REPLACE_ME_ | ||
|
||
TWILIO_ACCOUNT_SID=_REPLACE_ME_ | ||
TWILIO_API_KEY=_REPLACE_ME_ | ||
TWILIO_API_SECRET=_REPLACE_ME_ | ||
TWILIO_AUTH_TOKEN=_REPLACE_ME_ | ||
|
||
TWILIO_PHONE_SID=_REPLACE_ME_ | ||
TWILIO_PHONE_WEBHOOK_URL=https://{_REPLACE_ME_}.ngrok.io/ivr/welcome | ||
|
||
APP_CUSTOMER_NAME="VIRGIN MOBILE" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
# Author: [email protected] | ||
# Date: | ||
# Desc: Base makefile template for Python development | ||
# v1: Init | ||
# Date: April 30, 2020 | ||
# Desc: Python/Flask and Twilio development tooling | ||
|
||
.ONESHELL: | ||
.SHELL := /usr/bin/bash | ||
|
@@ -48,6 +47,14 @@ run: venv ## Run | |
@source venv/bin/activate; python manage.py runserver | ||
|
||
.PHONY: test | ||
test: venv ## Run test | ||
test: venv ## Execute test | ||
@echo "+ $@" | ||
@source venv/bin/activate; nosetests project/test | ||
|
||
.PHONY: twilio | ||
twilio: ## Update Twilio Voice WebHook | ||
@echo "+ $@" | ||
@twilio login $(TWILIO_ACCOUNT_SID) --auth-token=$(TWILIO_AUTH_TOKEN) --profile=okta --force | ||
@twilio phone-numbers:update $(TWILIO_PHONE_SID) \ | ||
--voice-method=POST \ | ||
--voice-url=$(TWILIO_PHONE_WEBHOOK_URL) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,23 @@ | ||
import requests | ||
import json | ||
import polling | ||
|
||
from ivr_phone_tree_python import app | ||
|
||
# //TODO: Refactor the Request alls with Env, Http Headers Template | ||
# //TODO: Create CONST for FACTOR TYPES | ||
|
||
|
||
class OktaIVR(object): | ||
def __init__(self, phone_number): | ||
self._user_phone_number = phone_number | ||
|
||
_user = self.__get_user_by_phone(self._user_phone_number) | ||
self._user_id = _user['id'] | ||
self._profile = _user['profile'] | ||
|
||
def _get_user_by_phone(phone_number): | ||
url = "https://bellca.okta.com/api/v1/users?search=profile.mobilePhone eq \"7734547477\"&limit=25" | ||
|
||
headers = { | ||
'Accept': 'application/json', | ||
'Content-Type': 'application/json', | ||
'Authorization': 'SSWS {api_token}'.format(api_token=app.config['OKTA_API_TOKEN']), | ||
} | ||
response = requests.request("GET", url, headers=headers) | ||
|
||
return response.json()[0] | ||
|
||
|
||
def get_user(phone_number): | ||
_user = get_user_by_phone(phone_number) | ||
_factor = get_user_factorid_by_factor_type(user_id=_user['id'], factor_type='sms') | ||
_user_factor_preference_type = _user['profile']['ivrFactorPreference'] | ||
_factor = get_user_factorid_by_factor_type(user_id=_user['id'], factor_type=_user_factor_preference_type) | ||
_auth = get_mfa_state_token(username=_user['profile']['login']) | ||
|
||
return _user, _factor | ||
return _user, _factor, _auth | ||
|
||
|
||
def get_user_by_phone(phone_number): | ||
query_search = requests.utils.quote('profile.mobilePhone eq "{}"'.format(phone_number)) | ||
url = 'https://bellca.okta.com/api/v1/users?search={}&limit=1'.format(query_search) | ||
query_search = requests.utils.quote('profile.ivrPhone eq "{phone_number}"'.format(phone_number=phone_number)) | ||
url = '{org_url}/api/v1/users?search={query}&limit=1'.format(org_url=app.config['OKTA_ORG_URL'], | ||
query=query_search) | ||
|
||
print('url:') | ||
print(url) | ||
|
@@ -55,8 +35,31 @@ def get_user_by_phone(phone_number): | |
return response.json()[0] | ||
|
||
|
||
def get_user_factorid_by_factor_type(user_id, factor_type='sms'): | ||
url = 'https://bellca.okta.com/api/v1/users/{user_id}/factors'.format(user_id=user_id) | ||
def get_user_factorid_by_factor_type(user_id=None, factor_type='sms'): | ||
if user_id is None: | ||
raise ValueError('user_id is None') | ||
|
||
factor_type_list = get_user_factors(user_id) | ||
|
||
result = [factor for factor in factor_type_list if factor['factorType'] == factor_type] | ||
if not result: | ||
raise Exception( | ||
'Not supported factor type. factor_type="{}"'.format(factor_type) | ||
) | ||
if len(result) > 1: | ||
raise Exception( | ||
'Multiple factor were returned.' | ||
) | ||
|
||
return result[0] | ||
|
||
|
||
def get_user_factors(user_id=None): | ||
if user_id is None: | ||
raise ValueError('user_id is None') | ||
|
||
url = '{org_url}/api/v1/users/{user_id}/factors'.format(org_url=app.config['OKTA_ORG_URL'], | ||
user_id=user_id) | ||
|
||
headers = { | ||
'Authorization': 'SSWS {api_token}'.format(api_token=app.config['OKTA_API_TOKEN']), | ||
|
@@ -67,13 +70,16 @@ def get_user_factorid_by_factor_type(user_id, factor_type='sms'): | |
response = requests.request("GET", url, headers=headers) | ||
|
||
factor_type_list = response.json() | ||
if not factor_type_list: | ||
raise Exception( | ||
'No factors found. user={}'.format(user_id) | ||
) | ||
|
||
result = [factor for factor in factor_type_list if factor['factorType'] == 'sms'] | ||
return result[0] | ||
return factor_type_list | ||
|
||
|
||
def get_mfa_state_token(username): | ||
url = "https://bellca.okta.com/api/v1/authn" | ||
url = '{org_url}/api/v1/authn'.format(org_url=app.config['OKTA_ORG_URL']) | ||
|
||
payload = { | ||
'username': username, | ||
|
@@ -88,7 +94,8 @@ def get_mfa_state_token(username): | |
|
||
|
||
def send_mfa_challenge(factor_id, state_token): | ||
url = 'https://bellca.okta.com/api/v1/authn/factors/{factor_id}/verify'.format(factor_id=factor_id) | ||
url = '{org_url}/api/v1/authn/factors/{factor_id}/verify'.format(org_url=app.config['OKTA_ORG_URL'], | ||
factor_id=factor_id) | ||
|
||
payload = { | ||
'stateToken': state_token | ||
|
@@ -104,7 +111,8 @@ def send_mfa_challenge(factor_id, state_token): | |
|
||
|
||
def sms_mfa_verify(factor_id, state_token, pass_code): | ||
url = 'https://bellca.okta.com/api/v1/authn/factors/{factor_id}/verify'.format(factor_id=factor_id) | ||
url = '{org_url}/api/v1/authn/factors/{factor_id}/verify'.format(org_url=app.config['OKTA_ORG_URL'], | ||
factor_id=factor_id) | ||
|
||
payload = { | ||
'stateToken': state_token, | ||
|
@@ -125,17 +133,26 @@ def sms_mfa_verify(factor_id, state_token, pass_code): | |
return _sms_verified | ||
|
||
|
||
def push_mfa_polling(): | ||
_push_verified = False | ||
def push_mfa_verify(response): | ||
print('push_mfa_verify') | ||
_valid = False | ||
result = response.json() | ||
|
||
print('result') | ||
print(result) | ||
|
||
if 'status' in result: | ||
if result['status'] == 'SUCCESS': | ||
_valid = True | ||
|
||
return _push_verified | ||
return _valid | ||
|
||
|
||
def push_mfa_verify(factor_id, state_token): | ||
# url = "https://bellca.okta.com/api/v1/authn/factors/opfai42xiNpKst1Qu4x6/verify" | ||
url = 'https://bellca.okta.com/api/v1/authn/factors/{factor_id}}/verify'.format(factor_id=factor_id) | ||
def push_mfa_polling(factor_id, state_token): | ||
_valid = False | ||
url = '{org_url}/api/v1/authn/factors/{factor_id}/verify'.format(org_url=app.config['OKTA_ORG_URL'], | ||
factor_id=factor_id) | ||
|
||
# payload = "{\n \"stateToken\": \"0063G-bqEfV6k5JNw35Jc_TnfVAACbs5y-iETHdV4s\",\n \"factorType\": \"push\",\n \"provider\": \"OKTA\"\n}" | ||
payload = { | ||
'stateToken': state_token, | ||
} | ||
|
@@ -144,138 +161,16 @@ def push_mfa_verify(factor_id, state_token): | |
'Content-Type': 'application/json', | ||
} | ||
|
||
response = requests.request("POST", url, headers=headers, data=json.dumps(payload)) | ||
|
||
print(response.text.encode('utf8')) | ||
|
||
_push_verified = False | ||
result = response.json() | ||
|
||
result = { | ||
"stateToken": "00qwBwEKCHQ-mRbB5N0zxmaq8LrLRMIhwTctfMjSJI", | ||
"expiresAt": "2020-04-26T18:15:10.000Z", | ||
"status": "MFA_CHALLENGE", | ||
"factorResult": "WAITING", | ||
"challengeType": "FACTOR", | ||
"_embedded": { | ||
"user": { | ||
"id": "00uaezbs2mhikusii4x6", | ||
"passwordChanged": "2020-04-24T21:05:48.000Z", | ||
"profile": { | ||
"login": "[email protected]", | ||
"firstName": "Noi", | ||
"lastName": "Narisak", | ||
"locale": "en", | ||
"timeZone": "America/Los_Angeles" | ||
} | ||
}, | ||
"factor": { | ||
"id": "opfai42xiNpKst1Qu4x6", | ||
"factorType": "push", | ||
"provider": "OKTA", | ||
"vendorName": "OKTA", | ||
"profile": { | ||
"credentialId": "[email protected]", | ||
"deviceType": "SmartPhone_Android", | ||
"keys": [ | ||
{ | ||
"kty": "RSA", | ||
"use": "sig", | ||
"kid": "default", | ||
"e": "AQAB", | ||
"n": "r75TcqG2gEIrBL6COX8tM9PyJZ4Qeo8w8Y3GTpg1p0OgpX24aBmqjM_QrUVzFklNwaahBgY5hKrO6amuTeMxJKo-PySX4RY0CpMrleBG7RfauVBF_OWZ-5Goz3Kb5h34lD4IjZkJMvHHlM8Q0ib2Vu2H2kBPmBUo4bmavldOUTIS62pPQRduEzojnUdgbhUoJ30vhhDw5aPmpscguJdf5CoXQRim5OcdoO6iu5Wbr29_edEX3b1VEDrmkj42HW4O-rdLuApIVn6oKrp4rL4XWr8YG9KnIX_lxVo_DTj69ayIKpvUF8wF75_DxRuBRr-V7VhYBoh-RQUn1OLIWlF1qQ" # noqa: E501 | ||
} | ||
], | ||
"name": "Pixel 3", | ||
"platform": "ANDROID", | ||
"version": "29" | ||
} | ||
}, | ||
"policy": { | ||
"allowRememberDevice": False, | ||
"rememberDeviceLifetimeInMinutes": 0, | ||
"rememberDeviceByDefault": False, | ||
"factorsPolicyInfo": { | ||
"opfai42xiNpKst1Qu4x6": { | ||
"autoPushEnabled": False | ||
} | ||
} | ||
} | ||
}, | ||
"_links": { | ||
"next": { | ||
"name": "poll", | ||
"href": "https://bellca.okta.com/api/v1/authn/factors/opfai42xiNpKst1Qu4x6/verify", | ||
"hints": { | ||
"allow": [ | ||
"POST" | ||
] | ||
} | ||
}, | ||
"resend": [ | ||
{ | ||
"name": "push", | ||
"href": "https://bellca.okta.com/api/v1/authn/factors/opfai42xiNpKst1Qu4x6/verify/resend", | ||
"hints": { | ||
"allow": [ | ||
"POST" | ||
] | ||
} | ||
} | ||
], | ||
"prev": { | ||
"href": "https://bellca.okta.com/api/v1/authn/previous", | ||
"hints": { | ||
"allow": [ | ||
"POST" | ||
] | ||
} | ||
}, | ||
"cancel": { | ||
"href": "https://bellca.okta.com/api/v1/authn/cancel", | ||
"hints": { | ||
"allow": [ | ||
"POST" | ||
] | ||
} | ||
} | ||
} | ||
} | ||
|
||
# //TODO: Logic | ||
# Loop interval every 5 seconds for 30 seconds. //NOTE: Look into https://github.com/justiniso/polling | ||
# if 'factorResult' in result: // factorResult attibute means we still waiting | ||
# POST "https://bellca.okta.com/api/v1/authn/factors/opfai42xiNpKst1Qu4x6/verify" | ||
# | ||
# | ||
|
||
result = { | ||
"expiresAt": "2020-04-26T18:12:20.000Z", | ||
"status": "SUCCESS", | ||
"sessionToken": "20111e1RJIsxLZv1-5KCmzKi1G1I2XOlv1HHmHEDNBMHD_d1vaqwDp5", | ||
"_embedded": { | ||
"user": { | ||
"id": "00uaezbs2mhikusii4x6", | ||
"passwordChanged": "2020-04-24T21:05:48.000Z", | ||
"profile": { | ||
"login": "[email protected]", | ||
"firstName": "Noi", | ||
"lastName": "Narisak", | ||
"locale": "en", | ||
"timeZone": "America/Los_Angeles" | ||
} | ||
} | ||
}, | ||
"_links": { | ||
"cancel": { | ||
"href": "https://bellca.okta.com/api/v1/authn/cancel", | ||
"hints": { | ||
"allow": [ | ||
"POST" | ||
] | ||
} | ||
} | ||
} | ||
} | ||
|
||
return result | ||
try: | ||
polling.poll( | ||
lambda: requests.request("POST", url, headers=headers, data=json.dumps(payload)), | ||
check_success=push_mfa_verify, | ||
step=2, # Attempt 3 times. | ||
timeout=10 # Timeout after 10 seconds. | ||
) | ||
_valid = True | ||
except polling.TimeoutException as te: | ||
while not te.values.empty(): | ||
print(te.values.get()) | ||
|
||
return _valid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.