granny-devops 0.12.1__tar.gz → 0.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {granny_devops-0.12.1 → granny_devops-0.13.0}/PKG-INFO +6 -3
- {granny_devops-0.12.1 → granny_devops-0.13.0}/README.md +5 -2
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/__init__.py +1 -1
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/zitadel.py +51 -3
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/credentials/secrets.py +4 -3
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/zitadel/client.py +32 -3
- {granny_devops-0.12.1 → granny_devops-0.13.0}/pyproject.toml +1 -1
- {granny_devops-0.12.1 → granny_devops-0.13.0}/.gitignore +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/LICENSE +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/analyze/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/analyze/costs.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/analyze/credits.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/analyze/gpu_pricing.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/analyze/gpus.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/analyze/lambdas.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/analyze/vpcs.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/authentik/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/authentik/client.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/authentik/provision.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/aws/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/aws/quota.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/_client.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/account.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/deployment.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/openai.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/quota.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/role.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/sku.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/vm.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/azure/webapp.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cdn/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cdn/bunny.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/analyze.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/authentik.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/aws.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/azure.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/cdn.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/cloudflare.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/create.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/credentials.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/dns.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/docker.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/edge.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/elk.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/email.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/indexing.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/main.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/serverless.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cli/storage.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cloudflare/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cloudflare/d1.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cloudflare/r2.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/cloudflare/workers.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/auto_certificate.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/cloudfront-security-headers.js +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/manage-dns.sh +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/manage_mailjet_contacts.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/registrars.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_aws_cloudfront.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_bunny_edge_script.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_bunny_storage.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_cognito_identity_pool.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_hetzner_bunny.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_mailjet_dns.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_private_cdn.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_s3_website.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_scaleway_container.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_scaleway_faas.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/setup_workmail.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/create/www-redirect-function.js +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/credentials/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/base.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/bunny.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/cloudflare.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/cloudns.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/desec.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/factory.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/hetzner.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/inwx.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/manual.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/dns/records.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/docker/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/docker/build_base.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/edge/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/edge/bunny.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/elk/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/elk/client.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/email/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/email/mailjet.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/email/mailjet_contacts.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/email/ses_forwarding.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/email/workmail.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/indexing/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/indexing/google.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/indexing/indexnow.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/report.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/serverless/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/serverless/scaleway.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/storage/__init__.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/storage/aws.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/storage/bunny.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/storage/hetzner.py +0 -0
- {granny_devops-0.12.1 → granny_devops-0.13.0}/granny/zitadel/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: granny-devops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Cloud tools collection -- AWS infrastructure, CDN, and DevOps automation
|
|
5
5
|
Author-email: Martin Wieser <martin.wieser@pseekoo.com>
|
|
6
6
|
License: MIT License
|
|
@@ -301,10 +301,13 @@ granny authentik add-user-to-group user@example.com # defaults to dash_admin
|
|
|
301
301
|
granny authentik api GET /api/v3/core/users/me/ # generic escape hatch
|
|
302
302
|
|
|
303
303
|
# Zitadel admin (self-hosted OIDC server — same shape as authentik)
|
|
304
|
-
#
|
|
304
|
+
# ZITADEL_DOMAIN (e.g. https://id.example.com) — plain config, NOT a secret;
|
|
305
|
+
# set it via env or .env/.deploy.env (granny auto-loads those). Not vaulted.
|
|
306
|
+
# Plus one machine credential:
|
|
305
307
|
# ZITADEL_API_TOKEN (a PAT, used as a bearer), OR
|
|
306
308
|
# ZITADEL_SA_KEY / ZITADEL_SA_KEY_FILE (service-account key JSON; JWT-profile auth)
|
|
307
|
-
#
|
|
309
|
+
# Secret vault keys (with the [vault] extra): granny/infra/zitadel-api-token, -sa-key
|
|
310
|
+
# Mint a PAT reproducibly: granny zitadel create-pat iam-admin --expiration 2027-06-27T00:00:00Z
|
|
308
311
|
granny zitadel create-project platform
|
|
309
312
|
granny zitadel create-oidc-app platform my-app \
|
|
310
313
|
--redirect-uri https://app.example.com/oidc/callback
|
|
@@ -168,10 +168,13 @@ granny authentik add-user-to-group user@example.com # defaults to dash_admin
|
|
|
168
168
|
granny authentik api GET /api/v3/core/users/me/ # generic escape hatch
|
|
169
169
|
|
|
170
170
|
# Zitadel admin (self-hosted OIDC server — same shape as authentik)
|
|
171
|
-
#
|
|
171
|
+
# ZITADEL_DOMAIN (e.g. https://id.example.com) — plain config, NOT a secret;
|
|
172
|
+
# set it via env or .env/.deploy.env (granny auto-loads those). Not vaulted.
|
|
173
|
+
# Plus one machine credential:
|
|
172
174
|
# ZITADEL_API_TOKEN (a PAT, used as a bearer), OR
|
|
173
175
|
# ZITADEL_SA_KEY / ZITADEL_SA_KEY_FILE (service-account key JSON; JWT-profile auth)
|
|
174
|
-
#
|
|
176
|
+
# Secret vault keys (with the [vault] extra): granny/infra/zitadel-api-token, -sa-key
|
|
177
|
+
# Mint a PAT reproducibly: granny zitadel create-pat iam-admin --expiration 2027-06-27T00:00:00Z
|
|
175
178
|
granny zitadel create-project platform
|
|
176
179
|
granny zitadel create-oidc-app platform my-app \
|
|
177
180
|
--redirect-uri https://app.example.com/oidc/callback
|
|
@@ -275,19 +275,61 @@ def delete_user_cmd(user: str, dry_run: bool) -> None:
|
|
|
275
275
|
click.echo(json.dumps({"user": user, "deleted": True}, indent=2))
|
|
276
276
|
|
|
277
277
|
|
|
278
|
+
# ── personal access tokens ─────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@zitadel.command("create-pat")
|
|
282
|
+
@click.argument("user")
|
|
283
|
+
@click.option(
|
|
284
|
+
"--expiration",
|
|
285
|
+
default=None,
|
|
286
|
+
help="RFC3339 expiry, e.g. 2027-06-27T00:00:00Z (omit for non-expiring).",
|
|
287
|
+
)
|
|
288
|
+
@click.option("--dry-run", is_flag=True, help="Change nothing.")
|
|
289
|
+
def create_pat_cmd(user: str, expiration: str | None, dry_run: bool) -> None:
|
|
290
|
+
"""Mint a personal access token for machine USER (userName or id).
|
|
291
|
+
|
|
292
|
+
Prints {tokenId, token}. The token is shown ONCE — capture it and store it
|
|
293
|
+
at vault `granny/infra/zitadel-api-token` (or export ZITADEL_API_TOKEN).
|
|
294
|
+
"""
|
|
295
|
+
if dry_run:
|
|
296
|
+
click.echo(f"[DRY RUN] Would create a PAT for {user!r} (expiration={expiration})")
|
|
297
|
+
return
|
|
298
|
+
client = _client()
|
|
299
|
+
out = client.create_pat(_resolve_user(client, user), expiration_date=expiration)
|
|
300
|
+
click.echo(json.dumps({"tokenId": out.get("tokenId"), "token": out.get("token")}, indent=2))
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@zitadel.command("revoke-pat")
|
|
304
|
+
@click.argument("user")
|
|
305
|
+
@click.argument("token_id")
|
|
306
|
+
@click.option("--dry-run", is_flag=True, help="Change nothing.")
|
|
307
|
+
def revoke_pat_cmd(user: str, token_id: str, dry_run: bool) -> None:
|
|
308
|
+
"""Revoke PAT TOKEN_ID belonging to machine USER (userName or id)."""
|
|
309
|
+
if dry_run:
|
|
310
|
+
click.echo(f"[DRY RUN] Would revoke PAT {token_id} of {user!r}")
|
|
311
|
+
return
|
|
312
|
+
client = _client()
|
|
313
|
+
client.delete_pat(_resolve_user(client, user), token_id)
|
|
314
|
+
click.echo(json.dumps({"user": user, "tokenId": token_id, "revoked": True}, indent=2))
|
|
315
|
+
|
|
316
|
+
|
|
278
317
|
# ── list ─────────────────────────────────────────────────────────────────
|
|
279
318
|
|
|
280
319
|
|
|
281
320
|
@zitadel.command("list")
|
|
282
|
-
@click.argument("kind", type=click.Choice(["users", "projects", "apps", "roles"]))
|
|
321
|
+
@click.argument("kind", type=click.Choice(["users", "projects", "apps", "roles", "pats"]))
|
|
283
322
|
@click.option(
|
|
284
323
|
"--project", default=None, help="Project (id or name) — required for apps/roles."
|
|
285
324
|
)
|
|
325
|
+
@click.option(
|
|
326
|
+
"--user", default=None, help="User (userName or id) — required for pats."
|
|
327
|
+
)
|
|
286
328
|
@click.option(
|
|
287
329
|
"--json", "as_json", is_flag=True, help="Emit full JSON instead of a table."
|
|
288
330
|
)
|
|
289
|
-
def list_cmd(kind: str, project: str | None, as_json: bool) -> None:
|
|
290
|
-
"""List Zitadel objects of KIND (users | projects | apps | roles)."""
|
|
331
|
+
def list_cmd(kind: str, project: str | None, user: str | None, as_json: bool) -> None:
|
|
332
|
+
"""List Zitadel objects of KIND (users | projects | apps | roles | pats)."""
|
|
291
333
|
client = _client()
|
|
292
334
|
if kind == "users":
|
|
293
335
|
rows = client.list_users()
|
|
@@ -295,6 +337,12 @@ def list_cmd(kind: str, project: str | None, as_json: bool) -> None:
|
|
|
295
337
|
elif kind == "projects":
|
|
296
338
|
rows = client.list_projects()
|
|
297
339
|
cols = ("id", "name", "state")
|
|
340
|
+
elif kind == "pats":
|
|
341
|
+
if not user:
|
|
342
|
+
click.echo("--user is required for `list pats`", err=True)
|
|
343
|
+
sys.exit(1)
|
|
344
|
+
rows = client.list_pats(_resolve_user(client, user))
|
|
345
|
+
cols = ("id", "expirationDate", "creationDate")
|
|
298
346
|
else:
|
|
299
347
|
if not project:
|
|
300
348
|
click.echo(f"--project is required for `list {kind}`", err=True)
|
|
@@ -172,9 +172,10 @@ SECRET_MAP: dict[str, str] = {
|
|
|
172
172
|
# superuser-issued token (Authentik UI → Directory → Tokens). Falls back
|
|
173
173
|
# to AK_TOKEN env var in granny.authentik.AuthentikClient.from_environment.
|
|
174
174
|
"AUTHENTIK_API_TOKEN": "authentik-api-token",
|
|
175
|
-
# Zitadel OIDC server (`granny zitadel …`):
|
|
176
|
-
#
|
|
177
|
-
|
|
175
|
+
# Zitadel OIDC server (`granny zitadel …`): an optional PAT and the
|
|
176
|
+
# service-account key JSON used for JWT-profile machine auth. The IdP base
|
|
177
|
+
# URL (ZITADEL_DOMAIN) is plain config, NOT a secret — set it via env /
|
|
178
|
+
# .env / .deploy.env, so it is deliberately not mapped to the vault.
|
|
178
179
|
"ZITADEL_API_TOKEN": "zitadel-api-token",
|
|
179
180
|
"ZITADEL_SA_KEY": "zitadel-sa-key",
|
|
180
181
|
# Elasticsearch / Kibana security API credentials for `granny elk …`.
|
|
@@ -109,7 +109,9 @@ class ZitadelClient:
|
|
|
109
109
|
def from_environment(cls) -> "ZitadelClient":
|
|
110
110
|
"""Build a client from env vars + granny's secret pipeline.
|
|
111
111
|
|
|
112
|
-
Base URL: ``ZITADEL_DOMAIN`` (e.g. ``https://id.pseekoo.io``)
|
|
112
|
+
Base URL: ``ZITADEL_DOMAIN`` (e.g. ``https://id.pseekoo.io``) — this is
|
|
113
|
+
plain config, NOT a secret, so set it via env / .env / .deploy.env; it
|
|
114
|
+
is deliberately not vault-resolved.
|
|
113
115
|
|
|
114
116
|
Credential resolution (first match wins):
|
|
115
117
|
|
|
@@ -121,10 +123,15 @@ class ZitadelClient:
|
|
|
121
123
|
"""
|
|
122
124
|
from granny.credentials import get_secret
|
|
123
125
|
|
|
124
|
-
|
|
126
|
+
# The IdP base URL is config, not a secret — env / .env / .deploy.env
|
|
127
|
+
# only (deliberately not vault-resolved).
|
|
128
|
+
base_url = os.environ.get("ZITADEL_DOMAIN")
|
|
125
129
|
if not base_url:
|
|
126
130
|
raise ZitadelError(
|
|
127
|
-
0,
|
|
131
|
+
0,
|
|
132
|
+
"ZITADEL_DOMAIN is not set. It is config, not a secret — set it "
|
|
133
|
+
"via env or .env/.deploy.env, e.g. "
|
|
134
|
+
"ZITADEL_DOMAIN=https://id.pseekoo.io",
|
|
128
135
|
)
|
|
129
136
|
|
|
130
137
|
pat = os.environ.get("ZITADEL_API_TOKEN") or get_secret("ZITADEL_API_TOKEN")
|
|
@@ -431,6 +438,28 @@ class ZitadelClient:
|
|
|
431
438
|
"""Delete a user by id."""
|
|
432
439
|
self.request("DELETE", f"/management/v1/users/{user_id}")
|
|
433
440
|
|
|
441
|
+
# ── Personal access tokens (machine-user bearer tokens) ───────────────
|
|
442
|
+
# A PAT is a long-lived bearer for a machine user — what other projects
|
|
443
|
+
# store at vault ``granny/infra/zitadel-api-token`` so granny authenticates
|
|
444
|
+
# without the service-account key. The token value is returned ONCE.
|
|
445
|
+
|
|
446
|
+
def list_pats(self, user_id: str) -> list[dict[str, Any]]:
|
|
447
|
+
data = self.request(
|
|
448
|
+
"POST", f"/management/v1/users/{user_id}/pats/_search", body={"queries": []}
|
|
449
|
+
)
|
|
450
|
+
return (data or {}).get("result", []) or []
|
|
451
|
+
|
|
452
|
+
def create_pat(self, user_id: str, *, expiration_date: str | None = None) -> dict[str, Any]:
|
|
453
|
+
"""Create a PAT for ``user_id``. Returns {tokenId, token} (token shown once)."""
|
|
454
|
+
body: dict[str, Any] = {}
|
|
455
|
+
if expiration_date:
|
|
456
|
+
body["expirationDate"] = expiration_date
|
|
457
|
+
return self.request("POST", f"/management/v1/users/{user_id}/pats", body=body)
|
|
458
|
+
|
|
459
|
+
def delete_pat(self, user_id: str, token_id: str) -> None:
|
|
460
|
+
"""Revoke a PAT by its token id."""
|
|
461
|
+
self.request("DELETE", f"/management/v1/users/{user_id}/pats/{token_id}")
|
|
462
|
+
|
|
434
463
|
# ── Project roles + user grants (Zitadel's analog to Authentik groups) ─
|
|
435
464
|
# Zitadel has no flat "groups": authorization is modeled as project ROLES
|
|
436
465
|
# (defined on a project) plus user GRANTS (a user granted a set of role
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|