Skip to content

Commit

Permalink
Clean up AuthZ cookie generation (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcmxgti authored Dec 19, 2023
1 parent 6e92fe8 commit 47cf2be
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 43 deletions.
9 changes: 8 additions & 1 deletion tests/unit/test_okta.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,9 @@ def test_create_authz_cookies():
"org": "acme",
"authorization_endpoint": "pytesturl",
"token_endpoint": "tokeneurl",
"nonce": "pytest",
"issuer": "pytest",
"ln": "pytest",
}
assert okta.create_authz_cookies(pytest_oauth2_config, pytest_oauth2_session_data) is None
from tokendito import okta
Expand Down Expand Up @@ -734,7 +737,9 @@ def test_get_oauth2_configuration(mocker):
"grant_types_supported": "authorization_code",
"request_parameter_supported": "pytest",
}
pytest_config = Config(okta={"client_id": "test_client_id", "org": "acme"})
pytest_config = Config(
okta={"client_id": "test_client_id", "org": "acme", "username": "pytest"}
)
mocker.patch.object(HTTP_client, "get", return_value=response)
assert okta.get_oauth2_configuration(pytest_config)["org"] == "acme"

Expand Down Expand Up @@ -765,6 +770,8 @@ def test_validate_oauth2_configuration():
"scopes_supported": "pytest",
"response_types_supported": "code",
"request_parameter_supported": "pytest",
"ln": "pytest",
"nonce": "pytest",
}
assert okta.validate_oauth2_configuration(pytest_oauth2_config) is None

Expand Down
90 changes: 48 additions & 42 deletions tokendito/okta.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ def send_saml_request(saml_request):
Submit SAML request to IdP, and get the response back.
:param saml_request: dict with IdP post_url, relay_state, and saml_request
:param cookies: session cookies with `sid`
:returns: dict with with SP post_url, relay_state, and saml_response
"""
# Define the payload and headers for the request
Expand All @@ -171,8 +170,6 @@ def send_saml_request(saml_request):
# Use the HTTP client to make a GET request
response = HTTP_client.get(url, params=payload, headers=headers)

logger.debug(f"{base64.b64decode(payload['SAMLRequest'])}")

# Extract relevant information from the response to form the saml_response dictionary
saml_response = {
"response": extract_saml_response(response.text, raw=True),
Expand All @@ -183,6 +180,7 @@ def send_saml_request(saml_request):
# Mask sensitive values for logging purposes
user.add_sensitive_value_to_be_masked(saml_response["response"])

logger.debug(f"SAML response is {saml_response}")
# Return the formed SAML response
return saml_response

Expand All @@ -193,21 +191,33 @@ def create_authz_cookies(oauth2_config, oauth2_session_data):
Needed for SAML2 flow for OIE.
"""
session_token = HTTP_client.session.cookies.get("sessionToken")
try:
oauth2_url = f"{oauth2_config['org']}/oauth2/v1"
oauth2_config_reformatted = {
"responseType": "code",
"state": oauth2_session_data["state"],
"clientId": oauth2_config["client_id"],
"authorizeUrl": oauth2_config["authorization_endpoint"],
"tokenUrl": oauth2_config["token_endpoint"],
"scope": "openid",
"sessionToken": session_token,
"userInfoUrl": f"{oauth2_url}/userinfo",
"revokeUrl": f"{oauth2_url}/revoke",
"logoutUrl": f"{oauth2_url}/logout",
"nonce": oauth2_session_data["nonce"],
"scopes": [
"openid",
"profile",
"email",
"okta.users.read.self",
"okta.users.manage.self",
"okta.internal.enduser.read",
"okta.internal.enduser.manage",
"okta.enduser.dashboard.read",
"okta.enduser.dashboard.manage",
],
"clientId": oauth2_config["client_id"],
"urls": {
"issuer": oauth2_config["issuer"],
"authorizeUrl": oauth2_config["authorization_endpoint"],
"userinfoUrl": f"{oauth2_url}/userinfo",
"tokenUrl": oauth2_config["token_endpoint"],
"revokeUrl": f"{oauth2_url}/revoke",
"logoutUrl": f"{oauth2_url}/logout",
},
"ignoreSignature": False,
}
except KeyError as e:
logger.error(f"Missing key in config:{e}")
Expand All @@ -217,11 +227,15 @@ def create_authz_cookies(oauth2_config, oauth2_session_data):
domain = urllib.parse.urlparse(oauth2_config["org"]).netloc
cookiejar.set(
"okta-oauth-redirect-params",
f"{{{urllib.parse.urlencode(oauth2_config_reformatted)}}}",
urllib.parse.quote(
json.dumps(oauth2_config_reformatted, separators=(",", ":")), safe="{}:[]/"
),
domain=domain,
path="/",
)
cookiejar.set("okta-oauth-state", oauth2_session_data["state"], domain=domain, path="/")
cookiejar.set("okta-oauth-nonce", oauth2_session_data["nonce"], domain=domain, path="/")
cookiejar.set("ln", oauth2_config["ln"], domain=domain, path="/")
HTTP_client.add_cookies(cookiejar) # add cookies


Expand All @@ -242,7 +256,6 @@ def send_saml_response(config, saml_response):
}
url = saml_response["post_url"]

logger.debug(f"{base64.b64decode(saml_response['response'])}")
# Log the SAML response details.
logger.debug(f"Sending SAML response to {url}")
# Use the HTTP client to make a POST request.
Expand All @@ -264,20 +277,19 @@ def send_saml_response(config, saml_response):
params = {"stateToken": state_token}
headers = {
"accept": "text/html,application/xhtml+xml,application/xml",
"content-type": "application/json",
}
response = HTTP_client.get(
# myurl, allow_redirects=False, params={"stateToken": state_token}
f"{config.okta['org']}/login/token/redirect",
params=params,
headers=headers,
)
logger.warning(
f"""
State token from {url}: {state_token}. TODO: need to go from this state token
to an idx cookies.
"""
)
if "idx" not in response.cookies:
logger.error(
f"Session cookie idx for {config.okta['org']} not found. Please file a bug."
)
logger.debug(f"Response: {response.headers}")
logger.debug(f"Response: {response.text}")
sys.exit(2)


def get_session_token(config, primary_auth, headers):
Expand Down Expand Up @@ -418,10 +430,7 @@ def get_response_type():


def get_authorize_scope():
"""We're only implementing openid scope.
So we're only returning "openid", which is ok for what we do.
"""
"""We're only implementing openid scope."""
return "openid"


Expand Down Expand Up @@ -609,10 +618,11 @@ def get_oauth2_configuration(config):
headers = {"accept": "application/json"}
response = HTTP_client.get(url, headers=headers)
logger.debug(f"Authorization Server info: {response.json()}")
# todo: handle errors.n
# TODO: handle errors
oauth2_config = response.json()
oauth2_config["org"] = config.okta["org"]
oauth2_config["client_id"] = get_client_id(config)
oauth2_config["ln"] = config.okta["username"]
validate_oauth2_configuration(oauth2_config)
return oauth2_config

Expand All @@ -632,6 +642,7 @@ def validate_oauth2_configuration(oauth2_config):
"scopes_supported",
"client_id",
"org",
"ln",
} # the authorization server must have these config elements
for item in mandadory_oauth2_config_items:
if item not in oauth2_config:
Expand Down Expand Up @@ -688,21 +699,15 @@ def idp_authenticate(config):
logger.error("Okta auth failed: unknown type.")
sys.exit(1)

auth_properties = get_auth_properties(userid=config.okta["username"], url=config.okta["org"])

if "type" not in auth_properties:
logger.error("Okta auth failed: unknown type.")
sys.exit(1)

if is_saml2_authentication(auth_properties):
# We may loop thru the saml2 servers until
# we find the authentication server.
saml2_authenticate(config, auth_properties)
elif local_authentication_enabled(auth_properties):
if local_authentication_enabled(auth_properties):
session_token = local_authenticate(config)
# authentication sends us a token
# which we then put in our session cookies
create_authn_cookies(config.okta["org"], session_token)
elif is_saml2_authentication(auth_properties):
# We may loop thru the saml2 servers until
# we find the authentication server.
saml2_authenticate(config, auth_properties)
else:
logger.error(
f"{auth_properties['type']} login via IdP Discovery is not currently supported"
Expand Down Expand Up @@ -734,8 +739,9 @@ def access_control(config):
oauth2_config = get_oauth2_configuration(config)
oauth2_session_data = get_oauth2_session_data(config.okta["org"])
create_authz_cookies(oauth2_config, oauth2_session_data)
# The flow says to initially call /authorize here, but that doesnt do anything...
# idp_authorize(oauth2_config, oauth2_session_data)
# The flow says to initially call /authorize here, but that doesnt do anything.
idp_authorize(oauth2_config, oauth2_session_data)
# We call it later, after we are authenticated.

idp_authenticate(config)

Expand Down Expand Up @@ -948,10 +954,10 @@ def extract_form_post_url(html):
"""
soup = BeautifulSoup(html, "html.parser")
post_url = None

elem = soup.find("form", attrs={"id": "appForm"})
if type(elem) is bs4.element.Tag:
post_url = str(elem.get("action"))
logger.debug(f"Found POST URL: {post_url}")
return post_url


Expand Down Expand Up @@ -1080,7 +1086,7 @@ def mfa_challenge(config, headers, primary_auth):
:param primary_auth: primary authentication
:return: Okta MFA Session token after the successful entry of the code
"""
logger.debug("Handle user MFA challenges")
logger.debug("Handle user MFA challenge")
try:
mfa_options = primary_auth["_embedded"]["factors"]
except KeyError as error:
Expand Down

0 comments on commit 47cf2be

Please sign in to comment.