Skip to content

Commit

Permalink
oie implementation draft
Browse files Browse the repository at this point in the history
  • Loading branch information
sevignyj committed Aug 23, 2023
1 parent 45703a5 commit 9de3d7c
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 89 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ venv/
ENV/
env.bak/
venv.bak/
.vscode/

# Spyder project settings
.spyderproject
Expand Down
2 changes: 1 addition & 1 deletion tokendito/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def main(args=None): # needed for console script

path = os.path.dirname(os.path.dirname(__file__))
sys.path[0:0] = [path]
from tokendito.tool import cli
from tokendito.user import cli

try:
return cli(args)
Expand Down
149 changes: 137 additions & 12 deletions tokendito/okta.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,40 @@ def api_error_code_parser(status=None):
return message


def get_auth_pipeline(url=None):
"""Get auth pipeline version."""
logger.debug(f"get_auth_pipeline({url})")
headers = {"accept": "application/json"}
# https://developer.okta.com/docs/api/openapi/okta-management/management/tag/OrgSetting/
url = f"{url}/.well-known/okta-organization"

response = user.request_wrapper("GET", url, headers=headers)
try:
ret = response.json()
except (KeyError, ValueError) as e:
logger.error(f"Failed to parse type in {url}:{str(e)}")
logger.debug(f"Response: {response.text}")
sys.exit(1)
logger.debug(f"we have {ret}")
auth_pipeline = ret.get("pipeline", None)
if auth_pipeline != "idx" and auth_pipeline != "v1":
logger.error(f"unsupported auth pipeline version {auth_pipeline}")
sys.exit(1)
logger.debug(f"Pipeline is of type {auth_pipeline}")
return auth_pipeline


def get_auth_properties(userid=None, url=None):
"""Make a call to the webfinger endpoint.
"""Make a call to the webfinger endpoint to get the auth properties metadata
:param userid: User for which we are requesting an auth endpoint.
:param url: Site where we are looking up the user.
:returns: dictionary with authentication properties.
"""
payload = {"resource": f"okta:acct:{userid}", "rel": "okta:idp"}
# payload = {"resource": f"okta:acct:{userid}"}
headers = {"accept": "application/jrd+json"}
url = f"{url}/.well-known/webfinger"

logger.debug(f"Looking up auth endpoint for {userid} in {url}")
response = user.request_wrapper("GET", url, headers=headers, params=payload)

Expand Down Expand Up @@ -214,35 +237,136 @@ def get_session_token(config, primary_auth, headers):

return session_token

def get_oauth_token(authz_server_metadata, authz_code):
"""Get OAuth token from Okta.
:param url: URL of the Okta OAuth token endpoint
todo:, authz_code in payload, put call etc.
:return: OAuth token
"""
token_endpoint = authz_server_metadata['token_endpoint']
# headers = {"accept": "application/json"}
headers = {"accept": "text/html,application/xhtml+xml,application/xml"}
payload = {"code": authz_code}
response = user.request_wrapper("POST", token_endpoint, headers=headers, data=payload)
breakpoint()
return response.json()

def extract_authz_code(response_text):
"""extract authorization code from response text
:param response_text: response text from /authorize call
:return: authorization code
"""
authz_code = re.search(r"(?<=code=)[^&]+", response_text).group(0)
return authz_code

def authz_code_request(authz_server_metadata, authn_sid):
"""implements authorization code request
calls /authorize endpoint with authenticated sid.
:param
:return: authorization code, needed for /token call """
authz_server_url = authz_server_metadata['authorization_endpoint']
headers = {"accept": "text/html,application/xhtml+xml,application/xml"}
payload = {"sid": authn_sid}
response = user.request_wrapper("GET", authz_server_url, headers=headers, data=payload)
authz_code = extract_authz_code(response.text)

def authorization_code(authz_server_metadata, authn_sid):
# Authorization Code flow (see
# https://developer.okta.com/docs/guides/implement-grant-type/authcode/main/#about-the-authorization-code-grant
# )
authz_code = authz_code_request(authz_server_metadata, authn_sid)
auth_token = get_oauth_token(authz_server_metadata, authz_code)
return auth_token

def authorization_code_enabled(authz_server_metadata):
"""determines if authorization code grant is enabled
todo
"""
return True

def authenticate(config):
"""Authenticate user.
def get_authorization_server_info(url=None):
"""Get OAuth token from Okta.
:param url: URL of the Okta OAuth token endpoint
:return: OAuth token
"""
url = f"{url}/.well-known/oauth-authorization-server"
headers = {"accept": "application/json"}
response = user.request_wrapper("GET", url, headers=headers)
logger.info(f"Authorization Server info: {response.json()}")
return response.json()

def oie_auth(config, authn_sid):
authz_server_metadata = get_authorization_server_info(config.okta["org"])
# get url where to authorize
if authorization_code_enabled(authz_server_metadata):
authz_code = authorization_code(authz_server_metadata, authn_sid)
oauth_token = get_oauth_token(authz_server_metadata, authz_code)
sid = oauth_token # we may need to massage sid
return sid


def idp_auth(config):
"""authenticate and authorize with the idp
:param config: Config object
:return: session ID cookie.
"""

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)

sid = None

if is_local_auth(auth_properties):
session_token = local_auth(config)
if is_local_authn(auth_properties):
session_token = local_authn(config)
sid = user.request_cookies(config.okta["org"], session_token)
elif is_saml2_auth(auth_properties):
sid = saml2_auth(config, auth_properties)
else:
logger.error(f"{auth_properties['type']} login via IdP Discovery is not curretly supported")
sys.exit(1)

if oie_enabled(config.okta["org"]):
sid = oie_auth(config, sid)

return sid

def oie_enabled(url):
"""
Determines if OIE is enabled.
:pamam url: okta org url
:return: True if OIE is enabled, False otherwise
"""
if get_auth_pipeline(url) == "idx": # oie
return True
else:
return False


def local_authn(config):
"""Authenticate and authorize user on local okta instance.
:param config: Config object
:return: auth session ID cookie.
"""

url = config.okta["org"]

authn_sid = idp_authn(config)
user.add_sensitive_value_to_be_masked(authn_sid)
return authn_sid


def is_local_auth(auth_properties):
"""Check whether authentication happens locally.
def is_local_authn(auth_properties):
"""Check whether authentication happens on the current instance.
:param auth_properties: auth_properties dict
:return: True for local auth, False otherwise.
:return: True if this is the place to authenticate, False otherwise.
"""
try:
if auth_properties["type"] == "OKTA":
Expand All @@ -266,12 +390,13 @@ def is_saml2_auth(auth_properties):
return False


def local_auth(config):
"""Authenticate local user with okta credential.
def idp_authn(config):
"""Authenticate with okta.
:param config: Config object
:return: MFA session with options
"""
logger.debug(f"idp_authn({config}")
session_token = None
headers = {"content-type": "application/json", "accept": "application/json"}
payload = {"username": config.okta["username"], "password": config.okta["password"]}
Expand Down Expand Up @@ -303,7 +428,7 @@ def saml2_auth(config, auth_properties):
logger.info(f"Authentication is being redirected to {saml2_config.okta['org']}.")
# Try to authenticate using the new configuration. This could cause
# recursive calls, which allows for IdP chaining.
session_cookies = authenticate(saml2_config)
session_cookies = idp_authn(saml2_config)

# Once we are authenticated, send the SAML request to the IdP.
# This call requires session cookies.
Expand Down
6 changes: 3 additions & 3 deletions tokendito/tokendito.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python
# vim: set filetype=python ts=4 sw=4
# -*- coding: utf-8 -*-
"""tokendito cli entry point."""
"""tokendito entry point."""
import logging
import sys


Expand All @@ -12,8 +13,7 @@ def main(args=None): # needed for console script

path = os.path.dirname(os.path.dirname(__file__))
sys.path[0:0] = [path]
from tokendito.tool import cli

from tokendito.user import cli
return cli(args)


Expand Down
70 changes: 0 additions & 70 deletions tokendito/tool.py

This file was deleted.

Loading

0 comments on commit 9de3d7c

Please sign in to comment.