@tidecloak/create-nextjs 0.14.2 → 0.14.7
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.
- package/init/realm.json +13 -10
- package/init/tcinit.sh +491 -177
- package/package.json +1 -1
- package/template-js-app/init/realm.json +164 -161
- package/template-js-app/init/tcinit.sh +459 -179
- package/template-js-app/package.json +1 -1
- package/template-ts-app/init/realm.json +164 -161
- package/template-ts-app/init/tcinit.sh +459 -179
- package/template-ts-app/package.json +1 -1
- package/template-ts-app/tsconfig.json +1 -1
package/init/tcinit.sh
CHANGED
|
@@ -2,43 +2,145 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
#
|
|
5
|
+
# @tidecloak/create-nextjs - tcinit.sh (CANONICAL copy)
|
|
6
|
+
#
|
|
7
|
+
# Provisions a firstAdmin / threshold-1 Tide realm for a Next.js app, driving
|
|
8
|
+
# the CURRENT iga-core native governance model (drain PENDING change-requests
|
|
9
|
+
# via /iga/change-requests/{id}/approve, which records AND auto-commits at
|
|
10
|
+
# threshold-1). The legacy tide-admin/change-set/{type}/sign|commit path has
|
|
11
|
+
# been removed.
|
|
12
|
+
#
|
|
13
|
+
# This canonical copy is invoked by create.ts as:
|
|
14
|
+
# bash "<packageRoot>/init/tcinit.sh" (cwd = the scaffolded app dir)
|
|
15
|
+
# It resolves paths relative to PROJECT_ROOT (= packageRoot) so that create.ts
|
|
16
|
+
# can pick up the generated <packageRoot>/tidecloak.json.
|
|
17
|
+
#
|
|
18
|
+
# The governance body below (from "Helper: grab a fresh master admin-cli
|
|
19
|
+
# token" onward) is IDENTICAL to the two shipped template copies under
|
|
20
|
+
# template-ts-app/init/ and template-js-app/init/. Keep them in sync via
|
|
21
|
+
# `npm run sync:init`. Only this path/bootstrap preamble differs.
|
|
6
22
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
# ─── Determine paths ─────────────────────────────────────────────────────────
|
|
7
25
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
26
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
9
27
|
|
|
10
|
-
# Load
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
28
|
+
# ─── Load defaults from .env.example next to the script (CRLF-safe) ──────────
|
|
29
|
+
# The defaults file is co-located with this script (init/.env.example), NOT in
|
|
30
|
+
# PROJECT_ROOT. -f guard keeps it a no-op when absent.
|
|
31
|
+
ENV_FILE="${SCRIPT_DIR}/.env.example"
|
|
32
|
+
if [[ -f "$ENV_FILE" ]]; then
|
|
33
|
+
# Apply each KEY=VALUE from the defaults file with FALLBACK semantics: a
|
|
34
|
+
# variable already present in the caller's environment is preserved and the
|
|
35
|
+
# default is ignored. We deliberately do NOT `source` the file, because raw
|
|
36
|
+
# assignments would hard-override caller-supplied env (e.g. NEW_REALM_NAME).
|
|
37
|
+
# CRLF-safe: a trailing CR is stripped from every line.
|
|
38
|
+
while IFS= read -r _env_line || [[ -n "$_env_line" ]]; do
|
|
39
|
+
_env_line="${_env_line%$'\r'}"
|
|
40
|
+
if [[ "$_env_line" =~ ^[[:space:]]*(#|$) ]]; then
|
|
41
|
+
continue
|
|
42
|
+
fi
|
|
43
|
+
if [[ "$_env_line" =~ ^[[:space:]]*(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=(.*)$ ]]; then
|
|
44
|
+
_env_key="${BASH_REMATCH[2]}"
|
|
45
|
+
_env_val="${BASH_REMATCH[3]}"
|
|
46
|
+
_env_val="${_env_val#"${_env_val%%[![:space:]]*}"}"
|
|
47
|
+
_env_val="${_env_val%"${_env_val##*[![:space:]]}"}"
|
|
48
|
+
if [[ ${#_env_val} -ge 2 && "$_env_val" == \"*\" ]]; then
|
|
49
|
+
_env_val="${_env_val:1:${#_env_val}-2}"
|
|
50
|
+
elif [[ ${#_env_val} -ge 2 && "$_env_val" == \'*\' ]]; then
|
|
51
|
+
_env_val="${_env_val:1:${#_env_val}-2}"
|
|
52
|
+
fi
|
|
53
|
+
if [[ -z "${!_env_key:-}" ]]; then
|
|
54
|
+
printf -v "$_env_key" '%s' "$_env_val"
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
done < "$ENV_FILE"
|
|
58
|
+
unset _env_line _env_key _env_val
|
|
14
59
|
fi
|
|
15
60
|
|
|
16
|
-
#
|
|
17
|
-
# Defaults (override via env)
|
|
18
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
# ─── Defaults (override via env) ─────────────────────────────────────────────
|
|
19
62
|
TIDECLOAK_LOCAL_URL="${TIDECLOAK_LOCAL_URL:-http://localhost:8080}"
|
|
20
63
|
CLIENT_APP_URL="${CLIENT_APP_URL:-http://localhost:3000}"
|
|
21
|
-
REALM_JSON_PATH="${REALM_JSON_PATH:-${SCRIPT_DIR}/realm.json}"
|
|
22
|
-
ADAPTER_OUTPUT_PATH="${ADAPTER_OUTPUT_PATH:-${PROJECT_ROOT}/tidecloak.json}"
|
|
23
64
|
NEW_REALM_NAME="${NEW_REALM_NAME:-nextjs-test}"
|
|
24
|
-
REALM_MGMT_CLIENT_ID="realm-management"
|
|
25
|
-
ADMIN_ROLE_NAME="tide-realm-admin"
|
|
65
|
+
REALM_MGMT_CLIENT_ID="${REALM_MGMT_CLIENT_ID:-realm-management}"
|
|
66
|
+
ADMIN_ROLE_NAME="${ADMIN_ROLE_NAME:-tide-realm-admin}"
|
|
26
67
|
KC_USER="${KC_USER:-admin}"
|
|
27
68
|
KC_PASSWORD="${KC_PASSWORD:-password}"
|
|
28
69
|
CLIENT_NAME="${CLIENT_NAME:-myclient}"
|
|
29
70
|
SUBSCRIPTION_EMAIL="${SUBSCRIPTION_EMAIL:-test@demo.org}"
|
|
71
|
+
ADAPTER_OUTPUT_PATH="${ADAPTER_OUTPUT_PATH:-${PROJECT_ROOT}/tidecloak.json}"
|
|
72
|
+
MARKER_DIR="${PROJECT_ROOT}"
|
|
73
|
+
|
|
74
|
+
# ─── Find realm.json robustly ────────────────────────────────────────────────
|
|
75
|
+
# Priority: env → same dir → parent → current working dir. A relative
|
|
76
|
+
# REALM_JSON_PATH sourced from .env.example (e.g. "./realm.json") is resolved
|
|
77
|
+
# against cwd first and then falls back to the copy shipped next to the script.
|
|
78
|
+
CANDIDATES=()
|
|
79
|
+
[[ "${REALM_JSON_PATH:-}" != "" ]] && CANDIDATES+=("${REALM_JSON_PATH}")
|
|
80
|
+
CANDIDATES+=("${SCRIPT_DIR}/realm.json" "${SCRIPT_DIR}/../realm.json" "$(pwd)/realm.json")
|
|
81
|
+
|
|
82
|
+
REALM_JSON_PATH=""
|
|
83
|
+
for p in "${CANDIDATES[@]}"; do
|
|
84
|
+
if [[ -f "$p" ]]; then REALM_JSON_PATH="$p"; break; fi
|
|
85
|
+
done
|
|
30
86
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
SED_INPLACE=(-i '')
|
|
87
|
+
if [[ -z "${REALM_JSON_PATH}" ]]; then
|
|
88
|
+
echo "ERROR: Could not find realm.json in:" >&2
|
|
89
|
+
for p in "${CANDIDATES[@]}"; do echo " - $p" >&2; done
|
|
90
|
+
echo " Put realm.json next to the script (${SCRIPT_DIR}/realm.json)" >&2
|
|
91
|
+
echo " OR run with: REALM_JSON_PATH=/abs/path/realm.json bash init/tcinit.sh" >&2
|
|
92
|
+
exit 1
|
|
38
93
|
fi
|
|
39
94
|
|
|
95
|
+
# ─── Dependency checks ───────────────────────────────────────────────────────
|
|
96
|
+
need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1" >&2; exit 1; }; }
|
|
97
|
+
need_cmd curl
|
|
98
|
+
need_cmd jq
|
|
99
|
+
need_cmd sed
|
|
100
|
+
need_cmd mktemp
|
|
101
|
+
|
|
102
|
+
# ─── sed -i portability ──────────────────────────────────────────────────────
|
|
103
|
+
if sed --version >/dev/null 2>&1; then SED_INPLACE=(-i); else SED_INPLACE=(-i ''); fi
|
|
104
|
+
|
|
105
|
+
# ─── Cleanup handler ─────────────────────────────────────────────────────────
|
|
106
|
+
# INCOMPLETE_FIRSTADMIN is set to 1 once IGA is enabled (step 4) and cleared
|
|
107
|
+
# back to 0 after the final grant+flip (step 14) completes. If the script exits
|
|
108
|
+
# non-zero while this flag is set, the realm is half-provisioned (IGA on, admin
|
|
109
|
+
# not yet granted/flipped) and we warn loudly. We do NOT auto-delete the realm.
|
|
110
|
+
TMP_REALM_JSON=""
|
|
111
|
+
INCOMPLETE_FIRSTADMIN=0
|
|
112
|
+
cleanup() {
|
|
113
|
+
local rc=$?
|
|
114
|
+
[[ -n "${TMP_REALM_JSON}" && -f "${TMP_REALM_JSON}" ]] && rm -f "${TMP_REALM_JSON}" || true
|
|
115
|
+
[[ -f "${MARKER_DIR}/.realm_name" ]] && rm -f "${MARKER_DIR}/.realm_name" || true
|
|
116
|
+
if [[ "${rc}" != "0" && "${INCOMPLETE_FIRSTADMIN}" == "1" ]]; then
|
|
117
|
+
echo "" >&2
|
|
118
|
+
echo "──────────────────────────────────────────────────────────────────────" >&2
|
|
119
|
+
echo "WARNING: realm '${REALM_NAME:-?}' may be left in an incomplete firstAdmin" >&2
|
|
120
|
+
echo " state (IGA enabled, admin not yet granted/flipped)." >&2
|
|
121
|
+
echo " Complete provisioning or delete the realm before use." >&2
|
|
122
|
+
echo "──────────────────────────────────────────────────────────────────────" >&2
|
|
123
|
+
fi
|
|
124
|
+
}
|
|
125
|
+
trap cleanup EXIT
|
|
126
|
+
|
|
127
|
+
# ─── Plaintext-to-remote credential warning (non-fatal) ──────────────────────
|
|
128
|
+
# TIDECLOAK_LOCAL_URL is resolved in the preamble above. If we are about to send
|
|
129
|
+
# admin credentials and bearer tokens over cleartext http:// to a NON-loopback
|
|
130
|
+
# host, warn the user. Loopback http:// stays silent; https:// stays silent.
|
|
131
|
+
case "${TIDECLOAK_LOCAL_URL}" in
|
|
132
|
+
http://localhost|http://localhost:*|http://localhost/*) : ;;
|
|
133
|
+
http://127.0.0.1|http://127.0.0.1:*|http://127.0.0.1/*) : ;;
|
|
134
|
+
http://\[::1\]|http://\[::1\]:*|http://\[::1\]/*) : ;;
|
|
135
|
+
http://*)
|
|
136
|
+
echo "WARNING: TIDECLOAK_LOCAL_URL='${TIDECLOAK_LOCAL_URL}' uses cleartext http:// to a non-loopback host." >&2
|
|
137
|
+
echo " Admin credentials and bearer tokens will be sent UNENCRYPTED over the network." >&2
|
|
138
|
+
echo " Use an https:// URL when targeting a remote TideCloak." >&2
|
|
139
|
+
;;
|
|
140
|
+
esac
|
|
141
|
+
|
|
40
142
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
-
#
|
|
143
|
+
# Helper: grab a fresh master admin-cli token (master realm password grant)
|
|
42
144
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
43
145
|
get_admin_token() {
|
|
44
146
|
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/realms/master/protocol/openid-connect/token" \
|
|
@@ -51,205 +153,417 @@ get_admin_token() {
|
|
|
51
153
|
}
|
|
52
154
|
|
|
53
155
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
-
#
|
|
156
|
+
# Helper: status-capturing admin API call.
|
|
157
|
+
# Usage: api METHOD URL [extra curl args...]
|
|
158
|
+
# then read the results from the globals it sets:
|
|
159
|
+
# RESP_CODE - the HTTP status ("000" on hard network failure)
|
|
160
|
+
# RESP_BODY - the full response body
|
|
161
|
+
# IMPORTANT: call api WITHOUT command substitution. It sets globals in the
|
|
162
|
+
# CURRENT shell; running it as `code=$(api ...)` would execute it in a subshell
|
|
163
|
+
# and the RESP_BODY/RESP_CODE it sets would be discarded before the parent
|
|
164
|
+
# could read them.
|
|
165
|
+
# - Authorization: Bearer ${TOKEN} is added automatically (refresh TOKEN first).
|
|
166
|
+
# - Never aborts the script on a non-2xx HTTP response (curl -s exits 0);
|
|
167
|
+
# only a hard network failure yields RESP_CODE "000".
|
|
55
168
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
169
|
+
RESP_CODE=""
|
|
170
|
+
RESP_BODY=""
|
|
171
|
+
api() {
|
|
172
|
+
local method="$1" url="$2"; shift 2
|
|
173
|
+
local tmp
|
|
174
|
+
tmp="$(mktemp)"
|
|
175
|
+
RESP_CODE=$(curl -s -o "${tmp}" -w "%{http_code}" -X "${method}" "${url}" \
|
|
176
|
+
-H "Authorization: Bearer ${TOKEN}" "$@") || RESP_CODE="000"
|
|
177
|
+
RESP_BODY="$(cat "${tmp}")"
|
|
178
|
+
rm -f "${tmp}"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
182
|
+
# Drain PENDING IGA change-requests (current iga-core native governance).
|
|
183
|
+
# LIST returns a bare JSON array; per-item id = .id; /approve body = {}
|
|
184
|
+
# (application/json). At threshold-1 /approve records AND auto-commits, so
|
|
185
|
+
# there is no separate /commit. Loop-until-empty is required because approving
|
|
186
|
+
# one CR unblocks its dependents.
|
|
187
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
188
|
+
drain_change_requests() {
|
|
189
|
+
local label="${1:-}" rounds=0
|
|
190
|
+
echo "Draining PENDING change-requests ${label}..."
|
|
191
|
+
while (( rounds < 12 )); do
|
|
192
|
+
TOKEN="$(get_admin_token)"
|
|
193
|
+
local list_tmp list_code
|
|
194
|
+
list_tmp="$(mktemp)"
|
|
195
|
+
list_code=$(curl -s -o "${list_tmp}" -w "%{http_code}" \
|
|
196
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/iga/change-requests?status=PENDING" \
|
|
197
|
+
-H "Authorization: Bearer ${TOKEN}" -H "Cache-Control: no-store") || list_code="000"
|
|
198
|
+
if [[ "${list_code}" == "401" || "${list_code}" == "403" ]]; then
|
|
199
|
+
rm -f "${list_tmp}"
|
|
200
|
+
echo "FATAL: change-request LIST returned HTTP ${list_code} ${label} - admin authentication/authorization failed." >&2
|
|
201
|
+
echo " Refusing to treat an auth failure as an empty inbox. Check KC_USER/KC_PASSWORD and admin privileges." >&2
|
|
202
|
+
exit 1
|
|
203
|
+
fi
|
|
204
|
+
ids=$(jq -r '.[].id // empty' < "${list_tmp}")
|
|
205
|
+
rm -f "${list_tmp}"
|
|
206
|
+
if [[ -z "${ids}" ]]; then echo " inbox empty ${label}"; return 0; fi
|
|
207
|
+
while IFS= read -r id; do
|
|
208
|
+
[[ -z "$id" ]] && continue
|
|
209
|
+
st=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
210
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/iga/change-requests/${id}/approve" \
|
|
211
|
+
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" -d '{}')
|
|
212
|
+
case "$st" in
|
|
213
|
+
2*) : ;;
|
|
214
|
+
401|403)
|
|
215
|
+
echo "FATAL: change-request approve ${id} returned HTTP ${st} ${label} - admin authentication/authorization failed." >&2
|
|
216
|
+
echo " Check KC_USER/KC_PASSWORD and admin privileges." >&2
|
|
217
|
+
exit 1 ;;
|
|
218
|
+
404|409|412) : ;;
|
|
219
|
+
*) echo " WARN approve ${id} -> ${st}" ;;
|
|
220
|
+
esac
|
|
221
|
+
done <<< "${ids}"
|
|
222
|
+
((rounds++)) || true
|
|
223
|
+
done
|
|
224
|
+
echo " WARN drain hit round cap ${label}"; return 0
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
228
|
+
# Step 1: prepare realm JSON (placeholder substitution) + create the realm
|
|
229
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
56
230
|
REALM_NAME="${NEW_REALM_NAME}"
|
|
57
|
-
echo "${REALM_NAME}" > "${
|
|
231
|
+
echo "${REALM_NAME}" > "${MARKER_DIR}/.realm_name"
|
|
58
232
|
|
|
59
233
|
TMP_REALM_JSON="$(mktemp)"
|
|
60
234
|
cp "${REALM_JSON_PATH}" "${TMP_REALM_JSON}"
|
|
61
235
|
|
|
62
|
-
# replace placeholders
|
|
63
236
|
sed "${SED_INPLACE[@]}" "s|http://localhost:3000|${CLIENT_APP_URL}|g" "${TMP_REALM_JSON}"
|
|
64
|
-
sed "${SED_INPLACE[@]}" "s|nextjs-test|${REALM_NAME}|g"
|
|
65
|
-
sed "${SED_INPLACE[@]}" "s|myclient|${CLIENT_NAME}|g"
|
|
237
|
+
sed "${SED_INPLACE[@]}" "s|nextjs-test|${REALM_NAME}|g" "${TMP_REALM_JSON}"
|
|
238
|
+
sed "${SED_INPLACE[@]}" "s|myclient|${CLIENT_NAME}|g" "${TMP_REALM_JSON}"
|
|
66
239
|
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
240
|
+
# TideCloak console origin for THIS realm (used by the signed IdP settings).
|
|
241
|
+
TIDE_CONSOLE_ORIGIN="${TIDECLOAK_LOCAL_URL}/realms/${REALM_NAME}/tide-console/"
|
|
242
|
+
|
|
243
|
+
echo "Creating realm '${REALM_NAME}'..."
|
|
70
244
|
TOKEN="$(get_admin_token)"
|
|
71
|
-
|
|
72
|
-
status=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
73
|
-
-X POST "${TIDECLOAK_LOCAL_URL}/admin/realms" \
|
|
74
|
-
-H "Authorization: Bearer ${TOKEN}" \
|
|
245
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms" \
|
|
75
246
|
-H "Content-Type: application/json" \
|
|
76
|
-
--data-binary @"${TMP_REALM_JSON}"
|
|
77
|
-
|
|
78
|
-
if [[ ${
|
|
79
|
-
echo "
|
|
247
|
+
--data-binary @"${TMP_REALM_JSON}"
|
|
248
|
+
code="${RESP_CODE}"
|
|
249
|
+
if [[ "${code}" == 2* ]]; then
|
|
250
|
+
echo " realm create -> ${code} (created)"
|
|
251
|
+
elif [[ "${code}" == "409" ]]; then
|
|
252
|
+
if [[ "${ALLOW_EXISTING_REALM:-}" == "1" ]]; then
|
|
253
|
+
echo " WARNING: realm '${REALM_NAME}' already exists (HTTP 409); ALLOW_EXISTING_REALM=1 set, proceeding." >&2
|
|
254
|
+
echo " NOTE: the drain step approves ALL pending change-requests in this realm," >&2
|
|
255
|
+
echo " including any unrelated to this provisioning run." >&2
|
|
256
|
+
else
|
|
257
|
+
echo "ERROR: Realm '${REALM_NAME}' already exists; this script provisions FRESH realms and would" >&2
|
|
258
|
+
echo " approve all pending change-requests. Set ALLOW_EXISTING_REALM=1 to override." >&2
|
|
259
|
+
exit 1
|
|
260
|
+
fi
|
|
80
261
|
else
|
|
81
|
-
echo "
|
|
262
|
+
echo "ERROR: realm creation failed (HTTP ${code})" >&2
|
|
263
|
+
echo " ${RESP_BODY}" >&2
|
|
82
264
|
exit 1
|
|
83
265
|
fi
|
|
84
266
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
267
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
268
|
+
# Step 2: setUpTideRealm (mints the realm VRK on the ORK network)
|
|
269
|
+
# REQUIRES healthy ORKs. Non-2xx = fail loudly.
|
|
270
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
271
|
+
echo "Setting up Tide realm (VRK keygen on the ORK network)..."
|
|
88
272
|
TOKEN="$(get_admin_token)"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
response=$(curl -i -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/setUpTideRealm" \
|
|
92
|
-
-H "Authorization: Bearer ${TOKEN}" \
|
|
273
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/setUpTideRealm" \
|
|
93
274
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
94
|
-
--data-urlencode "email=${SUBSCRIPTION_EMAIL}"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
275
|
+
--data-urlencode "email=${SUBSCRIPTION_EMAIL}" \
|
|
276
|
+
--data-urlencode "isRagnarokEnabled=true" \
|
|
277
|
+
--data-urlencode "skipLicense=false"
|
|
278
|
+
code="${RESP_CODE}"
|
|
279
|
+
if [[ "${code}" != 2* ]]; then
|
|
280
|
+
echo "ERROR: setUpTideRealm failed (HTTP ${code})." >&2
|
|
281
|
+
echo " This step needs a healthy ORK network (VRK keygen)." >&2
|
|
282
|
+
echo " Check that your ORKs are reachable and the license email is valid." >&2
|
|
283
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
284
|
+
exit 1
|
|
285
|
+
fi
|
|
286
|
+
echo " setUpTideRealm -> ${code}"
|
|
104
287
|
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
> /dev/null
|
|
132
|
-
done
|
|
133
|
-
echo "✅ ${TYPE^} change-sets done."
|
|
134
|
-
}
|
|
135
|
-
approve_and_commit clients
|
|
288
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
289
|
+
# Step 3: stamp iga.attestor=tide on the realm BEFORE enabling IGA
|
|
290
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
291
|
+
echo "Stamping iga.attestor=tide on the realm..."
|
|
292
|
+
TOKEN="$(get_admin_token)"
|
|
293
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}"
|
|
294
|
+
code="${RESP_CODE}"
|
|
295
|
+
if [[ "${code}" != 2* ]]; then
|
|
296
|
+
echo "ERROR: could not fetch realm representation (HTTP ${code})." >&2
|
|
297
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
298
|
+
exit 1
|
|
299
|
+
fi
|
|
300
|
+
REALM_REP_FILE="$(mktemp)"
|
|
301
|
+
jq '.attributes = ((.attributes // {}) + {"iga.attestor":"tide"})' <<< "${RESP_BODY}" > "${REALM_REP_FILE}"
|
|
302
|
+
TOKEN="$(get_admin_token)"
|
|
303
|
+
api PUT "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}" \
|
|
304
|
+
-H "Content-Type: application/json" \
|
|
305
|
+
--data-binary @"${REALM_REP_FILE}"
|
|
306
|
+
code="${RESP_CODE}"
|
|
307
|
+
rm -f "${REALM_REP_FILE}"
|
|
308
|
+
if [[ "${code}" != 2* ]]; then
|
|
309
|
+
echo "ERROR: failed to set iga.attestor=tide (HTTP ${code})." >&2
|
|
310
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
311
|
+
exit 1
|
|
312
|
+
fi
|
|
313
|
+
echo " iga.attestor=tide -> ${code}"
|
|
136
314
|
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
315
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
316
|
+
# Step 4: enable IGA (application/json body {"enabled":true})
|
|
317
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
318
|
+
echo "Enabling IGA governance..."
|
|
140
319
|
TOKEN="$(get_admin_token)"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients?clientId=${REALM_MGMT_CLIENT_ID}" \
|
|
155
|
-
-H "Authorization: Bearer ${TOKEN}" \
|
|
156
|
-
| jq -r '.[0].id')
|
|
157
|
-
|
|
158
|
-
ROLE_JSON=$(curl -s -X GET \
|
|
159
|
-
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients/${CLIENT_UUID}/roles/${ADMIN_ROLE_NAME}" \
|
|
160
|
-
-H "Authorization: Bearer ${TOKEN}")
|
|
320
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tide-admin/toggle-iga" \
|
|
321
|
+
-H "Content-Type: application/json" \
|
|
322
|
+
-d '{"enabled":true}'
|
|
323
|
+
code="${RESP_CODE}"
|
|
324
|
+
if [[ "${code}" == 2* ]]; then
|
|
325
|
+
echo " toggle-iga -> ${code} (IGA enabled)"
|
|
326
|
+
elif [[ "${code}" == "409" ]]; then
|
|
327
|
+
echo " toggle-iga -> 409 (already enabled)"
|
|
328
|
+
else
|
|
329
|
+
echo "ERROR: toggle-iga failed (HTTP ${code})." >&2
|
|
330
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
331
|
+
exit 1
|
|
332
|
+
fi
|
|
161
333
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
> /dev/null
|
|
334
|
+
# From here until the final grant+flip (step 14) the realm is half-provisioned:
|
|
335
|
+
# IGA is enabled but the first admin is not yet granted/flipped. A non-zero exit
|
|
336
|
+
# in this window triggers the incomplete-firstAdmin warning in cleanup().
|
|
337
|
+
INCOMPLETE_FIRSTADMIN=1
|
|
167
338
|
|
|
168
|
-
|
|
339
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
340
|
+
# Step 5: drain the ADOPT change-requests raised by enabling IGA
|
|
341
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
342
|
+
drain_change_requests "(after IGA enable)"
|
|
169
343
|
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
344
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
345
|
+
# Step 6: create the admin user (tideInvitable + emailVerified:false at create)
|
|
346
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
347
|
+
echo "Creating admin user..."
|
|
173
348
|
TOKEN="$(get_admin_token)"
|
|
174
|
-
|
|
175
|
-
INVITE_LINK=$(curl -s -X POST \
|
|
176
|
-
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tideAdminResources/get-required-action-link?userId=${USER_ID}&lifespan=43200" \
|
|
177
|
-
-H "Authorization: Bearer ${TOKEN}" \
|
|
349
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users" \
|
|
178
350
|
-H "Content-Type: application/json" \
|
|
179
|
-
-d '["
|
|
351
|
+
-d '{"username":"admin","email":"admin@tidecloak.com","firstName":"Admin","lastName":"User","enabled":true,"emailVerified":false,"attributes":{"tideInvitable":["true"]}}'
|
|
352
|
+
code="${RESP_CODE}"
|
|
353
|
+
if [[ "${code}" == 2* || "${code}" == "201" ]]; then
|
|
354
|
+
echo " create user -> ${code}"
|
|
355
|
+
elif [[ "${code}" == "409" ]]; then
|
|
356
|
+
echo " create user -> 409 (already exists)"
|
|
357
|
+
elif [[ "${code}" == "202" ]]; then
|
|
358
|
+
echo " create user -> 202 (IGA change-request parked)"
|
|
359
|
+
else
|
|
360
|
+
echo "ERROR: admin user creation failed (HTTP ${code})." >&2
|
|
361
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
362
|
+
exit 1
|
|
363
|
+
fi
|
|
180
364
|
|
|
181
|
-
|
|
182
|
-
|
|
365
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
366
|
+
# Step 7: drain (commits the CREATE_USER change-request) BEFORE resolving id
|
|
367
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
368
|
+
drain_change_requests "(after user create)"
|
|
183
369
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
370
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
371
|
+
# Step 8: resolve the admin userId (non-empty proves the create committed)
|
|
372
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
373
|
+
echo "Resolving admin userId..."
|
|
374
|
+
TOKEN="$(get_admin_token)"
|
|
375
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users?username=admin&exact=true"
|
|
376
|
+
code="${RESP_CODE}"
|
|
377
|
+
USER_ID="$(jq -r '.[0].id // empty' <<< "${RESP_BODY}")"
|
|
378
|
+
if [[ -z "${USER_ID}" ]]; then
|
|
379
|
+
echo "ERROR: could not resolve admin userId (HTTP ${code}); the CREATE_USER change-request may not have committed." >&2
|
|
380
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
381
|
+
exit 1
|
|
382
|
+
fi
|
|
383
|
+
echo " admin userId = ${USER_ID}"
|
|
191
384
|
|
|
385
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
386
|
+
# Step 9: mint the Tide enrollment link (link-tide-account-action)
|
|
387
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
388
|
+
echo "Minting Tide enrollment link..."
|
|
389
|
+
TOKEN="$(get_admin_token)"
|
|
390
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tideAdminResources/get-required-action-link?userId=${USER_ID}&lifespan=3600" \
|
|
391
|
+
-H "Content-Type: application/json" \
|
|
392
|
+
-H "Accept: text/plain" \
|
|
393
|
+
-d '["link-tide-account-action"]'
|
|
394
|
+
code="${RESP_CODE}"
|
|
395
|
+
if [[ "${code}" != 2* ]]; then
|
|
396
|
+
echo "ERROR: failed to mint enrollment link (HTTP ${code})." >&2
|
|
397
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
398
|
+
exit 1
|
|
399
|
+
fi
|
|
400
|
+
INVITE_LINK="${RESP_BODY}"
|
|
401
|
+
|
|
402
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
403
|
+
# Step 10: HUMAN GATE - complete Tide enrollment in the enclave, then poll
|
|
404
|
+
# until BOTH tideUserKey and vuid attributes appear on the user.
|
|
405
|
+
# Re-fetch the admin token every iteration (master token ~60s).
|
|
406
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
407
|
+
echo ""
|
|
408
|
+
echo "──────────────────────────────────────────────────────────────────────"
|
|
409
|
+
echo " ACTION REQUIRED: Open this URL and complete Tide enrollment:"
|
|
410
|
+
echo ""
|
|
411
|
+
echo " ${INVITE_LINK}"
|
|
412
|
+
echo ""
|
|
413
|
+
echo " Enrollment happens in the Tide enclave and can take a few minutes."
|
|
414
|
+
echo " This script will wait and continue automatically once you are enrolled."
|
|
415
|
+
echo "──────────────────────────────────────────────────────────────────────"
|
|
416
|
+
echo ""
|
|
417
|
+
|
|
418
|
+
poll_attempt=0
|
|
419
|
+
POLL_MAX=100000 # effectively unbounded (~4s * 100000)
|
|
420
|
+
while true; do
|
|
421
|
+
poll_attempt=$(( poll_attempt + 1 ))
|
|
422
|
+
TOKEN="$(get_admin_token)"
|
|
423
|
+
ATTRS=$(curl -s "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users?username=admin&exact=true&briefRepresentation=false" \
|
|
424
|
+
-H "Authorization: Bearer ${TOKEN}" -H "Cache-Control: no-store")
|
|
192
425
|
KEY=$(jq -r '.[0].attributes.tideUserKey[0] // empty' <<< "${ATTRS}")
|
|
193
426
|
VUID=$(jq -r '.[0].attributes.vuid[0] // empty' <<< "${ATTRS}")
|
|
194
|
-
|
|
195
427
|
if [[ -n "${KEY}" && -n "${VUID}" ]]; then
|
|
196
|
-
echo "
|
|
428
|
+
echo " Tide enrollment detected (tideUserKey + vuid present)."
|
|
197
429
|
break
|
|
198
430
|
fi
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
break
|
|
431
|
+
if (( poll_attempt >= POLL_MAX )); then
|
|
432
|
+
echo "ERROR: gave up waiting for Tide enrollment after ${poll_attempt} polls." >&2
|
|
433
|
+
exit 1
|
|
203
434
|
fi
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
echo
|
|
207
|
-
((attempt++))
|
|
435
|
+
printf " waiting for enrollment... (poll %s)\r" "${poll_attempt}"
|
|
436
|
+
sleep 4
|
|
208
437
|
done
|
|
209
438
|
|
|
210
|
-
|
|
439
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
440
|
+
# Step 11: drain (commits the tideUserKey / vuid change-requests)
|
|
441
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
442
|
+
drain_change_requests "(after enrollment)"
|
|
211
443
|
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
444
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
445
|
+
# Step 12: point the tide IdP at THIS realm's console origin, then sign it
|
|
446
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
447
|
+
echo "Signing tide IdP settings (CustomAdminUIDomain -> console origin)..."
|
|
215
448
|
TOKEN="$(get_admin_token)"
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"${
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
449
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/identity-provider/instances/tide"
|
|
450
|
+
code="${RESP_CODE}"
|
|
451
|
+
if [[ "${code}" != 2* ]]; then
|
|
452
|
+
echo "ERROR: could not fetch tide IdP instance (HTTP ${code})." >&2
|
|
453
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
454
|
+
exit 1
|
|
455
|
+
fi
|
|
456
|
+
IDP_REP_FILE="$(mktemp)"
|
|
457
|
+
jq --arg d "${TIDE_CONSOLE_ORIGIN}" '.config.CustomAdminUIDomain = $d' <<< "${RESP_BODY}" > "${IDP_REP_FILE}"
|
|
458
|
+
TOKEN="$(get_admin_token)"
|
|
459
|
+
api PUT "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/identity-provider/instances/tide" \
|
|
460
|
+
-H "Content-Type: application/json" \
|
|
461
|
+
--data-binary @"${IDP_REP_FILE}"
|
|
462
|
+
code="${RESP_CODE}"
|
|
463
|
+
rm -f "${IDP_REP_FILE}"
|
|
464
|
+
if [[ "${code}" != 2* ]]; then
|
|
465
|
+
echo "ERROR: failed to update tide IdP settings (HTTP ${code})." >&2
|
|
466
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
467
|
+
exit 1
|
|
468
|
+
fi
|
|
469
|
+
TOKEN="$(get_admin_token)"
|
|
470
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/sign-idp-settings" \
|
|
471
|
+
-H "Content-Type: text/plain" \
|
|
472
|
+
--data-binary ""
|
|
473
|
+
code="${RESP_CODE}"
|
|
474
|
+
if [[ "${code}" != 2* ]]; then
|
|
475
|
+
echo "ERROR: sign-idp-settings failed (HTTP ${code}). Needs healthy ORKs." >&2
|
|
476
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
477
|
+
exit 1
|
|
478
|
+
fi
|
|
479
|
+
echo " IdP settings signed -> ${code}"
|
|
223
480
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
481
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
482
|
+
# Step 13: grant tide-realm-admin to the enrolled admin (AFTER enrollment)
|
|
483
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
484
|
+
echo "Granting tide-realm-admin to the admin user..."
|
|
485
|
+
TOKEN="$(get_admin_token)"
|
|
486
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients?clientId=${REALM_MGMT_CLIENT_ID}"
|
|
487
|
+
code="${RESP_CODE}"
|
|
488
|
+
RM_UUID="$(jq -r '.[0].id // empty' <<< "${RESP_BODY}")"
|
|
489
|
+
if [[ -z "${RM_UUID}" ]]; then
|
|
490
|
+
echo "ERROR: could not resolve realm-management client uuid (HTTP ${code})." >&2
|
|
491
|
+
exit 1
|
|
492
|
+
fi
|
|
493
|
+
TOKEN="$(get_admin_token)"
|
|
494
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients/${RM_UUID}/roles/${ADMIN_ROLE_NAME}"
|
|
495
|
+
code="${RESP_CODE}"
|
|
496
|
+
if [[ "${code}" != 2* ]]; then
|
|
497
|
+
echo "ERROR: could not fetch ${ADMIN_ROLE_NAME} role (HTTP ${code})." >&2
|
|
498
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
499
|
+
exit 1
|
|
500
|
+
fi
|
|
501
|
+
ROLE_REP="${RESP_BODY}"
|
|
229
502
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
503
|
+
# idempotency: skip if the mapping already exists
|
|
504
|
+
TOKEN="$(get_admin_token)"
|
|
505
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${RM_UUID}"
|
|
506
|
+
code="${RESP_CODE}"
|
|
507
|
+
ALREADY=$(jq -r --arg r "${ADMIN_ROLE_NAME}" '[.[]?.name] | index($r) // empty' <<< "${RESP_BODY}")
|
|
508
|
+
if [[ -n "${ALREADY}" ]]; then
|
|
509
|
+
echo " ${ADMIN_ROLE_NAME} already mapped; skipping grant."
|
|
510
|
+
else
|
|
511
|
+
TOKEN="$(get_admin_token)"
|
|
512
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${RM_UUID}" \
|
|
513
|
+
-H "Content-Type: application/json" \
|
|
514
|
+
-d "[${ROLE_REP}]"
|
|
515
|
+
code="${RESP_CODE}"
|
|
516
|
+
case "${code}" in
|
|
517
|
+
2*) echo " grant -> ${code}" ;;
|
|
518
|
+
202) echo " grant -> 202 (change-request parked)" ;;
|
|
519
|
+
409)
|
|
520
|
+
echo " grant -> 409; draining users then retrying once..."
|
|
521
|
+
drain_change_requests "(grant 409 retry)"
|
|
522
|
+
TOKEN="$(get_admin_token)"
|
|
523
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${RM_UUID}" \
|
|
524
|
+
-H "Content-Type: application/json" \
|
|
525
|
+
-d "[${ROLE_REP}]"
|
|
526
|
+
code="${RESP_CODE}"
|
|
527
|
+
case "${code}" in
|
|
528
|
+
2*|202|409) echo " grant retry -> ${code}" ;;
|
|
529
|
+
*) echo "ERROR: grant retry failed (HTTP ${code})." >&2; echo " ${RESP_BODY}" >&2; exit 1 ;;
|
|
530
|
+
esac
|
|
531
|
+
;;
|
|
532
|
+
*) echo "ERROR: grant failed (HTTP ${code})." >&2; echo " ${RESP_BODY}" >&2; exit 1 ;;
|
|
533
|
+
esac
|
|
534
|
+
fi
|
|
233
535
|
|
|
234
|
-
|
|
536
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
537
|
+
# Step 14: FINAL drain - commits GRANT_ROLES and flips firstAdmin->multiAdmin.
|
|
538
|
+
# No drain or governed write may run after this point.
|
|
539
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
540
|
+
drain_change_requests "(final: grant + firstAdmin->multiAdmin flip)"
|
|
235
541
|
|
|
542
|
+
# Grant+flip committed: the realm is fully provisioned. Clear the incomplete
|
|
543
|
+
# state flag so a later non-zero exit does NOT emit the half-provisioned warning.
|
|
544
|
+
INCOMPLETE_FIRSTADMIN=0
|
|
236
545
|
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
546
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
547
|
+
# Step 15: fetch the client adapter config (tidecloak.json)
|
|
548
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
549
|
+
echo "Fetching adapter config..."
|
|
240
550
|
TOKEN="$(get_admin_token)"
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
551
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients?clientId=${CLIENT_NAME}"
|
|
552
|
+
code="${RESP_CODE}"
|
|
553
|
+
CLIENT_UUID="$(jq -r '.[0].id // empty' <<< "${RESP_BODY}")"
|
|
554
|
+
if [[ -z "${CLIENT_UUID}" ]]; then
|
|
555
|
+
echo "ERROR: could not resolve client '${CLIENT_NAME}' uuid (HTTP ${code})." >&2
|
|
556
|
+
exit 1
|
|
557
|
+
fi
|
|
558
|
+
TOKEN="$(get_admin_token)"
|
|
559
|
+
adapter_code=$(curl -s -o "${ADAPTER_OUTPUT_PATH}" -w "%{http_code}" \
|
|
248
560
|
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/get-installations-provider?clientId=${CLIENT_UUID}&providerId=keycloak-oidc-keycloak-json" \
|
|
249
|
-
-H "Authorization: Bearer ${TOKEN}"
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
561
|
+
-H "Authorization: Bearer ${TOKEN}")
|
|
562
|
+
if [[ "${adapter_code}" != 2* ]]; then
|
|
563
|
+
echo "ERROR: failed to fetch adapter config (HTTP ${adapter_code})." >&2
|
|
564
|
+
exit 1
|
|
565
|
+
fi
|
|
566
|
+
echo " Adapter config saved to ${ADAPTER_OUTPUT_PATH}"
|
|
254
567
|
|
|
255
|
-
echo "
|
|
568
|
+
echo ""
|
|
569
|
+
echo "All done. Realm '${REALM_NAME}' is provisioned and the first admin is enrolled."
|