diff --git a/tests/unit/test_okta.py b/tests/unit/test_okta.py index 8a32ebe..e9587dd 100644 --- a/tests/unit/test_okta.py +++ b/tests/unit/test_okta.py @@ -549,6 +549,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 @@ -708,7 +711,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" @@ -739,6 +744,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 diff --git a/tokendito/okta.py b/tokendito/okta.py index 541f135..99672dc 100644 --- a/tokendito/okta.py +++ b/tokendito/okta.py @@ -17,7 +17,7 @@ import re import sys import time -from urllib.parse import urlencode +from urllib.parse import quote from urllib.parse import urlparse import bs4 @@ -149,7 +149,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 @@ -194,20 +193,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"], + "nonce": oauth2_config["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"], - "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", + "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}") @@ -217,11 +229,13 @@ def create_authz_cookies(oauth2_config, oauth2_session_data): domain = urlparse(oauth2_config["org"]).netloc cookiejar.set( "okta-oauth-redirect-params", - f"{{{urlencode(oauth2_config_reformatted)}}}", + 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_config["nonce"], domain=domain, path="/") + cookiejar.set("ln", oauth2_config["ln"], domain=domain, path="/") HTTP_client.add_cookies(cookiejar) # add cookies @@ -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. @@ -264,20 +277,18 @@ 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.text}") + sys.exit(2) def get_session_token(config, primary_auth, headers): @@ -595,6 +606,8 @@ def get_oauth2_configuration(config): oauth2_config = response.json() oauth2_config["org"] = config.okta["org"] oauth2_config["client_id"] = get_client_id(config) + oauth2_config["ln"] = config.okta["username"] + oauth2_config["nonce"] = "TODO" validate_oauth2_configuration(oauth2_config) return oauth2_config @@ -614,6 +627,8 @@ def validate_oauth2_configuration(oauth2_config): "scopes_supported", "client_id", "org", + "ln", + "nonce", } # the authorization server must have these config elements for item in mandadory_oauth2_config_items: if item not in oauth2_config: @@ -676,15 +691,15 @@ def idp_authenticate(config): 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 curretly supported") sys.exit(1) @@ -714,8 +729,8 @@ def access_control(config): oauth2_config = get_oauth2_configuration(config) oauth2_session_data = generate_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. + # We call it later, after we are authenticated. idp_authenticate(config)