import json, os, sys, hmac, hashlib, base64, email.utils import urllib.parse, urllib.request, urllib.error def _cfg(): return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) def _inputs(): return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) def _canon_params(params): # RFC-3986 encode each key/value, sort by key, join k=v with & items = [] for k in sorted(params.keys()): ek = urllib.parse.quote(str(k), "~") ev = urllib.parse.quote(str(params[k]), "~") items.append(ek + "=" + ev) return "&".join(items) def _sign(method, host, path, params, cfg, now): canon = "\n".join([now, method.upper(), host.lower(), path, _canon_params(params)]) skey = str(cfg.get("secret_key", "")).encode("utf-8") sig = hmac.new(skey, canon.encode("utf-8"), hashlib.sha1).hexdigest() ikey = str(cfg.get("integration_key", "")) auth = base64.b64encode((ikey + ":" + sig).encode("utf-8")).decode("utf-8") return "Basic " + auth def call(method, path, cfg, params=None): params = params or {} host = str(cfg.get("api_hostname", "")).strip() now = email.utils.formatdate() # RFC 2822, e.g. 'Wed, 01 Jan 2020 00:00:00 -0000' authz = _sign(method, host, path, params, cfg, now) headers = {"Authorization": authz, "Date": now, "Accept": "application/json"} method = method.upper() url = "https://" + host + path data = None if method in ("GET", "DELETE"): if params: url += "?" + _canon_params(params) else: # POST: params go in the body, form-encoded with the SAME canonicalization headers["Content-Type"] = "application/x-www-form-urlencoded" data = _canon_params(params).encode("utf-8") req = urllib.request.Request(url, data=data, headers=headers, method=method) with urllib.request.urlopen(req, timeout=60) as r: raw = r.read() return json.loads(raw) if raw else {} def _run(fn): try: print(json.dumps(fn(_cfg(), _inputs()))) except urllib.error.HTTPError as e: print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) sys.exit(1) except Exception as e: print(json.dumps({"error": str(e)})) sys.exit(1) def main(cfg, inputs): username = str(inputs.get("username", "") or "").strip() if username: params = {"username": username} else: limit = inputs.get("limit", 100) params = {"limit": int(limit)} return call("GET", "/admin/v1/users", cfg, params) _run(main)