#!/usr/bin/env python3 """Drive the OpenZaak → NRC notification check from *inside* the compose network. Registers an abonnement on the `zaken` kanaal pointing at a webhook sink, then creates a zaak against the published BIG zaaktype. OpenZaak publishes a `zaken`/`create` notification; NRC delivers it to the sink. The host harness (infra/verify-notifications.sh) then asserts the sink received it. Reached by container IP, not service name: OpenZaak/NRC validate URLs with Django's URLValidator, which rejects a single-label host like `openzaak`. Stdlib only. Env: OZ_BASE, NRC_BASE, SINK_CALLBACK, SINK_AUTH, OZ_CLIENT_ID, OZ_SECRET. """ import base64 import hashlib import hmac import json import os import sys import time import urllib.error import urllib.request OZ = os.environ["OZ_BASE"].rstrip("/") NRC = os.environ["NRC_BASE"].rstrip("/") SINK_CALLBACK = os.environ["SINK_CALLBACK"] SINK_AUTH = os.environ.get("SINK_AUTH", "Bearer notification-sink-token") CID = os.environ.get("OZ_CLIENT_ID", "big-reference-seed") SECRET = os.environ.get("OZ_SECRET", "insecure-dev-secret-change-me") RSIN = "517439943" def token(): b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=") seg = ( b64(json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",", ":")).encode()) + b"." + b64(json.dumps( {"iss": CID, "iat": int(time.time()), "client_id": CID, "user_id": "verify", "user_representation": "verify"}, separators=(",", ":")).encode()) ) return (seg + b"." + b64(hmac.new(SECRET.encode(), seg, hashlib.sha256).digest())).decode() def call(method, url, body=None, crs=False): headers = {"Authorization": "Bearer " + token(), "Content-Type": "application/json", "Accept": "application/json"} if crs: headers["Accept-Crs"] = "EPSG:4326" headers["Content-Crs"] = "EPSG:4326" data = json.dumps(body).encode() if body is not None else None try: with urllib.request.urlopen( urllib.request.Request(url, data=data, method=method, headers=headers), timeout=30 ) as r: return r.status, json.loads(r.read() or "null") except urllib.error.HTTPError as e: return e.code, json.loads(e.read() or "null") def main(): status, ab = call("POST", f"{NRC}/api/v1/abonnement", { "callbackUrl": SINK_CALLBACK, "auth": SINK_AUTH, "kanalen": [{"naam": "zaken", "filters": {}}], }) if status != 201: sys.exit(f"create abonnement -> {status}: {json.dumps(ab)}") print(f"abonnement: {ab['url']}") status, body = call( "GET", f"{OZ}/catalogi/api/v1/zaaktypen?identificatie=BIG-REGISTRATIE&status=definitief") results = body.get("results", []) if status == 200 else [] if not results: sys.exit("no published BIG-REGISTRATIE zaaktype — seed with OZ_PUBLISH=1 first") zaaktype = results[0]["url"] status, zaak = call("POST", f"{OZ}/zaken/api/v1/zaken", { "bronorganisatie": RSIN, "verantwoordelijkeOrganisatie": RSIN, "zaaktype": zaaktype, "startdatum": time.strftime("%Y-%m-%d"), "vertrouwelijkheidaanduiding": "openbaar", }, crs=True) if status != 201: sys.exit(f"create zaak -> {status}: {json.dumps(zaak)}") # The harness greps the sink for this exact URL. print(f"ZAAK_CREATED {zaak['url']}") if __name__ == "__main__": main()