131 lines
4.5 KiB
Python
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)
|