Files
pre-repos/okta_search_logs/show_values_from_ad.py
2026-01-26 16:49:09 -05:00

131 lines
4.5 KiB
Python

#!/usr/bin/env python3
import os
import sys
import json
import requests
from urllib.parse import quote
# ---------------- .env loading (KEY=VALUE; quotes supported) ----------------
def _strip_quotes(val: str) -> str:
val = val.strip()
if len(val) >= 2 and (val[0] == val[-1]) and val[0] in ("'", '"'):
return val[1:-1]
return val
def _load_env_file(path: str) -> None:
if not os.path.exists(path):
return
with open(path, "r", encoding="utf-8") as f:
for raw in f:
s = raw.strip()
if not s or s.startswith("#"):
continue
if "=" not in s:
continue
k, v = s.split("=", 1)
k = k.strip()
v = _strip_quotes(v)
if k and k not in os.environ:
os.environ[k] = v
def load_env():
"""Load .env from script dir then CWD; no 'export' needed."""
script_dir = os.path.dirname(os.path.abspath(__file__))
for p in (os.path.join(script_dir, ".env"), os.path.join(os.getcwd(), ".env")):
_load_env_file(p)
load_env()
# ---------------- Config ----------------
OKTA_DOMAIN = os.getenv("OKTA_DOMAIN") # e.g., "gallaudet.okta.com"
API_TOKEN = os.getenv("OKTA_API_TOKEN") # required
APP_ID = os.getenv("OKTA_APP_ID") # required: the target Okta appId
DEFAULT_EMAIL_DOMAIN = os.getenv("DEFAULT_EMAIL_DOMAIN", "gallaudet.edu")
if not (OKTA_DOMAIN and API_TOKEN and APP_ID):
sys.stderr.write(
"ERROR: Missing required settings. Ensure your .env contains:\n"
" OKTA_DOMAIN=\"gallaudet.okta.com\"\n"
" OKTA_API_TOKEN=\"xxxxx\"\n"
" OKTA_APP_ID=\"0oa...\"\n"
"Optional:\n"
" DEFAULT_EMAIL_DOMAIN=\"gallaudet.edu\"\n"
)
sys.exit(1)
BASE_URL = f"https://{OKTA_DOMAIN}"
USERS_URL = f"{BASE_URL}/api/v1/users"
APPS_URL = f"{BASE_URL}/api/v1/apps"
# ---------------- HTTP helper ----------------
def req(method, url, **kw):
headers = kw.pop("headers", {})
headers["Authorization"] = f"SSWS {API_TOKEN}"
headers["Accept"] = "application/json"
return requests.request(method, url, headers=headers, timeout=15, **kw)
# ---------------- Args ----------------
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <USER_ID_or_LOGIN>")
print("Examples:")
print(f" {sys.argv[0]} 00u1abcdE2FGHIJKL3p4")
print(f" {sys.argv[0]} jared.evans@gallaudet.edu")
print(f" {sys.argv[0]} jared.evans # will append @{DEFAULT_EMAIL_DOMAIN}")
sys.exit(1)
user_arg = sys.argv[1]
def normalize_login(s: str) -> str:
return s if "@" in s else f"{s}@{DEFAULT_EMAIL_DOMAIN}"
# ---------------- Okta helpers ----------------
def find_user_id_by_login(login: str):
r = req("GET", USERS_URL, params={"filter": f'profile.login eq "{login}"', "limit": "1"})
if r.status_code != 200:
raise RuntimeError(f"User lookup error {r.status_code}: {r.text}")
data = r.json()
if isinstance(data, list) and data:
return data[0].get("id"), data[0]
return None, None
def get_app_user_assignment(app_id: str, user_id: str) -> requests.Response:
url = f"{APPS_URL}/{quote(app_id)}/users/{quote(user_id)}"
return req("GET", url)
# ---------------- Main flow ----------------
# Resolve user id if needed
if user_arg.startswith("00u"): # looks like an Okta user id
user_id = user_arg
resolved_login = None
else:
login = normalize_login(user_arg)
user_id, user_obj = find_user_id_by_login(login)
if not user_id:
print(f"User not found for login '{login}'.")
sys.exit(1)
resolved_login = login
print(f"Resolved login '{login}' to Okta user id: {user_id}")
# Fetch assignment for the fixed app
resp = get_app_user_assignment(APP_ID, user_id)
if resp.status_code == 200:
print(json.dumps(resp.json(), indent=2))
sys.exit(0)
# Helpful diagnostics
if resp.status_code == 404:
# 404 can mean: user not assigned to app, bad app id, or masked permission issue
app_check = req("GET", f"{APPS_URL}/{quote(APP_ID)}")
if app_check.status_code == 404:
print(f"App not found: appId '{APP_ID}'.")
elif app_check.status_code == 200:
who = resolved_login or user_id
print(f"User '{who}' (id {user_id}) is likely NOT assigned to app '{APP_ID}', or you lack permission.")
else:
print(f"Assignment 404; app check returned {app_check.status_code}: {app_check.text}")
sys.exit(1)
print(f"Error {resp.status_code}: {resp.text}")
sys.exit(1)