#!/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]} ") 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)