From 566eafdfc75d6087064f472e2a506ad6ce6257f3 Mon Sep 17 00:00:00 2001 From: jenkins Date: Sat, 20 Jun 2026 14:50:16 -0300 Subject: [PATCH] keycloak: allow groups scope for Veles Gitea --- .../veles-gitea-oidc-secret-ensure-job.yaml | 4 +- .../scripts/veles_gitea_oidc_secret_ensure.sh | 91 +++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml b/services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml index 4afb5551..33be307a 100644 --- a/services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml +++ b/services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml @@ -1,12 +1,12 @@ # services/keycloak/oneoffs/veles-gitea-oidc-secret-ensure-job.yaml -# One-off job for sso/veles-gitea-oidc-secret-ensure-3. +# One-off job for sso/veles-gitea-oidc-secret-ensure-4. # Purpose: create/update the Veles realm Gitea OIDC client and write the # matching Gitea auth-source secret to Vault. # Keep suspended until the Vault policy change has reconciled, then unsuspend once. apiVersion: batch/v1 kind: Job metadata: - name: veles-gitea-oidc-secret-ensure-3 + name: veles-gitea-oidc-secret-ensure-4 namespace: sso spec: suspend: true diff --git a/services/keycloak/scripts/veles_gitea_oidc_secret_ensure.sh b/services/keycloak/scripts/veles_gitea_oidc_secret_ensure.sh index 32764b6b..da0aa6e6 100755 --- a/services/keycloak/scripts/veles_gitea_oidc_secret_ensure.sh +++ b/services/keycloak/scripts/veles_gitea_oidc_secret_ensure.sh @@ -145,6 +145,94 @@ ensure_mapper() { fi } +ensure_client_scope() { + scope_name="$1" + scopes="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/client-scopes?search=$(printf '%s' "${scope_name}" | jq -sRr @uri)" || true)" + scope_id="$(echo "$scopes" | jq -r --arg name "$scope_name" '.[]? | select(.name == $name) | .id' | head -n1 || true)" + if [ -n "$scope_id" ] && [ "$scope_id" != "null" ]; then + printf '%s' "$scope_id" + return + fi + + scope_payload="$(jq -nc --arg name "$scope_name" '{name:$name,protocol:"openid-connect",attributes:{"include.in.token.scope":"true","display.on.consent.screen":"false"}}')" + status="$(curl -sS -o /tmp/keycloak-client-scope-create.json -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "${scope_payload}" \ + "${KC_URL}/admin/realms/${REALM}/client-scopes")" + if [ "$status" != "201" ] && [ "$status" != "204" ] && [ "$status" != "409" ]; then + echo "Keycloak client scope create failed for ${scope_name} (status ${status})" >&2 + cat /tmp/keycloak-client-scope-create.json >&2 || true + exit 1 + fi + + scopes="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/client-scopes?search=$(printf '%s' "${scope_name}" | jq -sRr @uri)" || true)" + scope_id="$(echo "$scopes" | jq -r --arg name "$scope_name" '.[]? | select(.name == $name) | .id' | head -n1 || true)" + if [ -z "$scope_id" ] || [ "$scope_id" = "null" ]; then + echo "Keycloak client scope ${scope_name} not found after create" >&2 + exit 1 + fi + printf '%s' "$scope_id" +} + +ensure_scope_mapper() { + scope_id="$1" + mapper_name="$2" + mapper_payload="$3" + mappers="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/client-scopes/${scope_id}/protocol-mappers/models" || true)" + mapper_id="$(echo "$mappers" | jq -r --arg name "$mapper_name" '.[]? | select(.name == $name) | .id' | head -n1 || true)" + if [ -n "$mapper_id" ] && [ "$mapper_id" != "null" ]; then + mapper_payload="$(echo "$mapper_payload" | jq --arg id "$mapper_id" '. + {id:$id}')" + status="$(curl -sS -o /tmp/keycloak-scope-mapper.json -w "%{http_code}" -X PUT \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "${mapper_payload}" \ + "${KC_URL}/admin/realms/${REALM}/client-scopes/${scope_id}/protocol-mappers/models/${mapper_id}")" + else + status="$(curl -sS -o /tmp/keycloak-scope-mapper.json -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -H 'Content-Type: application/json' \ + -d "${mapper_payload}" \ + "${KC_URL}/admin/realms/${REALM}/client-scopes/${scope_id}/protocol-mappers/models")" + fi + if [ "$status" != "200" ] && [ "$status" != "201" ] && [ "$status" != "204" ]; then + echo "Keycloak client-scope mapper ensure failed for ${mapper_name} (status ${status})" >&2 + cat /tmp/keycloak-scope-mapper.json >&2 || true + exit 1 + fi +} + +ensure_client_optional_scope() { + client_uuid="$1" + scope_id="$2" + scope_name="$3" + default_scopes="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${client_uuid}/default-client-scopes" || true)" + optional_scopes="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${client_uuid}/optional-client-scopes" || true)" + + if echo "$default_scopes" | jq -e --arg name "$scope_name" '.[]? | select(.name == $name)' >/dev/null 2>&1 \ + || echo "$optional_scopes" | jq -e --arg name "$scope_name" '.[]? | select(.name == $name)' >/dev/null 2>&1; then + return + fi + + status="$(curl -sS -o /dev/null -w "%{http_code}" -X PUT \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${client_uuid}/optional-client-scopes/${scope_id}")" + if [ "$status" != "200" ] && [ "$status" != "201" ] && [ "$status" != "204" ]; then + status="$(curl -sS -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "${KC_URL}/admin/realms/${REALM}/clients/${client_uuid}/optional-client-scopes/${scope_id}")" + if [ "$status" != "200" ] && [ "$status" != "201" ] && [ "$status" != "204" ]; then + echo "Failed to attach ${scope_name} client scope to ${CLIENT_ID} (status ${status})" >&2 + exit 1 + fi + fi +} + TESTER_GROUP_ID="$(ensure_group "${TESTER_GROUP}")" TESTER_ROLE="$(ensure_role "${TESTER_GROUP}")" ensure_group_role "${TESTER_GROUP_ID}" "${TESTER_ROLE}" @@ -197,6 +285,9 @@ roles_mapper_payload="$(jq -nc \ '{name:"roles",protocol:"openid-connect",protocolMapper:"oidc-usermodel-realm-role-mapper",consentRequired:false,config:{"multivalued":"true","id.token.claim":"true","access.token.claim":"true","userinfo.token.claim":"true","claim.name":"roles","jsonType.label":"String"}}')" ensure_mapper "${CLIENT_UUID}" groups "${groups_mapper_payload}" ensure_mapper "${CLIENT_UUID}" roles "${roles_mapper_payload}" +GROUPS_SCOPE_ID="$(ensure_client_scope groups)" +ensure_scope_mapper "${GROUPS_SCOPE_ID}" groups "${groups_mapper_payload}" +ensure_client_optional_scope "${CLIENT_UUID}" "${GROUPS_SCOPE_ID}" groups CLIENT_SECRET="$(curl -sS -H "Authorization: Bearer ${ACCESS_TOKEN}" \ "${KC_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" | jq -r '.value' 2>/dev/null || true)"