Fix --account N credential priority across all SDKs
--account N was silently ignored when UNSANDBOX_PUBLIC_KEY/SECRET_KEY env vars were set. The credential resolution checked env vars at tier 2 before ever reaching the CSV lookup, so the explicit flag had no effect. Correct priority order (all 8 SDKs): 1. CLI -p/-k flags (explicit key args) 2. --account N → direct CSV row lookup (bypasses env vars) 3. UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY env vars 4. ~/.unsandbox/accounts.csv default (row 0 or UNSANDBOX_ACCOUNT) SDKs updated: C, Python, Go, JavaScript, Ruby, PHP, Java, Rust Also adds: - --account N flag to CLI parsers in all 8 SDKs (Go, PHP, Java, Rust previously had no flag at all; Python/JS/Ruby had the parameter but never wired it to the CLI) - test_account_flag.sh integration test for each SDK verifying the priority behavior with real and garbage credentials - test-integration Makefile target for the C SDK
This commit is contained in:
parent
8093386568
commit
ed6c52b001
20 changed files with 1243 additions and 174 deletions
|
|
@ -25,11 +25,12 @@
|
|||
* // Snapshot operations
|
||||
* String snapshotId = Un.sessionSnapshot(sessionId, publicKey, secretKey, "my-snapshot", false);
|
||||
*
|
||||
* Authentication Priority (4-tier):
|
||||
* Authentication Priority (5-tier):
|
||||
* 1. Method arguments (publicKey, secretKey)
|
||||
* 2. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
|
||||
* 3. Config file (~/.unsandbox/accounts.csv, line 0 by default)
|
||||
* 4. Local directory (./accounts.csv, line 0 by default)
|
||||
* 2. --account N flag / accountIndex >= 0 (load row N from accounts.csv)
|
||||
* 3. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
|
||||
* 4. Config file (~/.unsandbox/accounts.csv, line 0 by default)
|
||||
* 5. Local directory (./accounts.csv, line 0 by default)
|
||||
*
|
||||
* Request Authentication (HMAC-SHA256):
|
||||
* Authorization: Bearer <public_key>
|
||||
|
|
@ -175,38 +176,58 @@ public class Un {
|
|||
}
|
||||
|
||||
private static String[] resolveCredentials(String publicKey, String secretKey) {
|
||||
return resolveCredentials(publicKey, secretKey, -1);
|
||||
}
|
||||
|
||||
private static String[] resolveCredentials(String publicKey, String secretKey, int accountIndex) {
|
||||
// Tier 1: Method arguments
|
||||
if (publicKey != null && !publicKey.isEmpty() && secretKey != null && !secretKey.isEmpty()) {
|
||||
return new String[]{publicKey, secretKey};
|
||||
}
|
||||
|
||||
// Tier 2: Environment variables
|
||||
// Tier 2: Explicit account index (e.g. --account N from CLI)
|
||||
if (accountIndex >= 0) {
|
||||
Path unsandboxDir = getUnsandboxDir();
|
||||
String[] creds = loadCredentialsFromCsv(unsandboxDir.resolve("accounts.csv"), accountIndex);
|
||||
if (creds != null) {
|
||||
return creds;
|
||||
}
|
||||
creds = loadCredentialsFromCsv(Paths.get("accounts.csv"), accountIndex);
|
||||
if (creds != null) {
|
||||
return creds;
|
||||
}
|
||||
throw new CredentialsException(
|
||||
"No credentials found at account index " + accountIndex + " in accounts.csv"
|
||||
);
|
||||
}
|
||||
|
||||
// Tier 3: Environment variables
|
||||
String envPk = System.getenv("UNSANDBOX_PUBLIC_KEY");
|
||||
String envSk = System.getenv("UNSANDBOX_SECRET_KEY");
|
||||
if (envPk != null && !envPk.isEmpty() && envSk != null && !envSk.isEmpty()) {
|
||||
return new String[]{envPk, envSk};
|
||||
}
|
||||
|
||||
// Determine account index
|
||||
int accountIndex = 0;
|
||||
// Determine account index from env (default 0)
|
||||
int csvIndex = 0;
|
||||
String accountEnv = System.getenv("UNSANDBOX_ACCOUNT");
|
||||
if (accountEnv != null && !accountEnv.isEmpty()) {
|
||||
try {
|
||||
accountIndex = Integer.parseInt(accountEnv);
|
||||
csvIndex = Integer.parseInt(accountEnv);
|
||||
} catch (NumberFormatException e) {
|
||||
// Use default
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 3: ~/.unsandbox/accounts.csv
|
||||
// Tier 4: ~/.unsandbox/accounts.csv
|
||||
Path unsandboxDir = getUnsandboxDir();
|
||||
String[] creds = loadCredentialsFromCsv(unsandboxDir.resolve("accounts.csv"), accountIndex);
|
||||
String[] creds = loadCredentialsFromCsv(unsandboxDir.resolve("accounts.csv"), csvIndex);
|
||||
if (creds != null) {
|
||||
return creds;
|
||||
}
|
||||
|
||||
// Tier 4: ./accounts.csv
|
||||
creds = loadCredentialsFromCsv(Paths.get("accounts.csv"), accountIndex);
|
||||
// Tier 5: ./accounts.csv
|
||||
creds = loadCredentialsFromCsv(Paths.get("accounts.csv"), csvIndex);
|
||||
if (creds != null) {
|
||||
return creds;
|
||||
}
|
||||
|
|
@ -214,9 +235,10 @@ public class Un {
|
|||
throw new CredentialsException(
|
||||
"No credentials found. Please provide via:\n" +
|
||||
" 1. Method arguments (publicKey, secretKey)\n" +
|
||||
" 2. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)\n" +
|
||||
" 3. ~/.unsandbox/accounts.csv\n" +
|
||||
" 4. ./accounts.csv"
|
||||
" 2. --account N flag (load row N from accounts.csv)\n" +
|
||||
" 3. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)\n" +
|
||||
" 4. ~/.unsandbox/accounts.csv\n" +
|
||||
" 5. ./accounts.csv"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -2860,6 +2882,7 @@ public class Un {
|
|||
String language = null;
|
||||
String networkMode = "zerotrust";
|
||||
int vcpu = 1;
|
||||
int accountIndex = -1;
|
||||
List<String> envVars = new ArrayList<>();
|
||||
List<String> files = new ArrayList<>();
|
||||
List<String> positionalArgs = new ArrayList<>();
|
||||
|
|
@ -2871,6 +2894,18 @@ public class Un {
|
|||
if (arg.equals("-h") || arg.equals("--help")) {
|
||||
showHelp = true;
|
||||
i++;
|
||||
} else if (arg.equals("--account")) {
|
||||
if (i + 1 >= args.length) {
|
||||
System.err.println("Error: --account requires an argument");
|
||||
System.exit(2);
|
||||
}
|
||||
try {
|
||||
accountIndex = Integer.parseInt(args[++i]);
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("Error: --account requires an integer argument");
|
||||
System.exit(2);
|
||||
}
|
||||
i++;
|
||||
} else if (arg.equals("-s") || arg.equals("--shell")) {
|
||||
if (i + 1 >= args.length) {
|
||||
System.err.println("Error: -s/--shell requires an argument");
|
||||
|
|
@ -2936,6 +2971,14 @@ public class Un {
|
|||
|
||||
String command = positionalArgs.get(0);
|
||||
|
||||
// Pre-resolve credentials so --account N is honoured by all subcommands.
|
||||
// Only resolve if explicit keys were not supplied via -p/-k flags.
|
||||
if (publicKey == null || publicKey.isEmpty() || secretKey == null || secretKey.isEmpty()) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey, accountIndex);
|
||||
publicKey = creds[0];
|
||||
secretKey = creds[1];
|
||||
}
|
||||
|
||||
// Route to subcommand handlers
|
||||
switch (command) {
|
||||
case "session":
|
||||
|
|
@ -2982,6 +3025,7 @@ public class Un {
|
|||
System.out.println(" -f, --file FILE Add input file to /tmp/");
|
||||
System.out.println(" -p, --public-key KEY API public key");
|
||||
System.out.println(" -k, --secret-key KEY API secret key");
|
||||
System.out.println(" --account N Use row N from accounts.csv (overrides env vars)");
|
||||
System.out.println(" -n, --network MODE Network: zerotrust or semitrusted");
|
||||
System.out.println(" -v, --vcpu N vCPU count (1-8)");
|
||||
System.out.println(" -h, --help Show help");
|
||||
|
|
|
|||
113
clients/java/sync/tests/test_account_flag.sh
Executable file
113
clients/java/sync/tests/test_account_flag.sh
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env bash
|
||||
# Integration test: --account N credential selection in Java SDK CLI
|
||||
#
|
||||
# Tests that --account N loads row N from accounts.csv and takes priority
|
||||
# over environment variables UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY.
|
||||
#
|
||||
# Requires: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY set in the environment.
|
||||
# SKIP if not set.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SRC_DIR="$(cd "$SCRIPT_DIR/../src" && pwd)"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
SKIP=0
|
||||
|
||||
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
|
||||
fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); }
|
||||
skip() { echo "SKIP: $1"; SKIP=$((SKIP + 1)); }
|
||||
|
||||
# ---- Prerequisites ----
|
||||
|
||||
if [ -z "${UNSANDBOX_PUBLIC_KEY:-}" ] || [ -z "${UNSANDBOX_SECRET_KEY:-}" ]; then
|
||||
skip "UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY not set"
|
||||
echo ""
|
||||
echo "Results: PASS=$PASS FAIL=$FAIL SKIP=$SKIP"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Compile Un.java if needed
|
||||
CLASS_FILE="$SRC_DIR/Un.class"
|
||||
if [ ! -f "$CLASS_FILE" ] || [ "$SRC_DIR/Un.java" -nt "$CLASS_FILE" ]; then
|
||||
echo "Compiling Un.java..."
|
||||
if ! javac -cp "$SRC_DIR" "$SRC_DIR/Un.java" 2>&1; then
|
||||
fail "javac compilation failed"
|
||||
echo ""
|
||||
echo "Results: PASS=$PASS FAIL=$FAIL SKIP=$SKIP"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- Temp home setup ----
|
||||
|
||||
TMPHOME="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMPHOME"' EXIT
|
||||
|
||||
mkdir -p "$TMPHOME/.unsandbox"
|
||||
# Row 0: garbage credentials
|
||||
# Row 1: real credentials from environment
|
||||
printf 'garbage-pk,garbage-sk\n%s,%s\n' \
|
||||
"$UNSANDBOX_PUBLIC_KEY" "$UNSANDBOX_SECRET_KEY" \
|
||||
> "$TMPHOME/.unsandbox/accounts.csv"
|
||||
|
||||
# ---- Test 1: --account 1 loads real creds, env vars set to garbage ----
|
||||
# With HOME=TMPHOME, env set to garbage, --account 1 should pick real creds
|
||||
# and the 'key' subcommand should succeed (validateKeys returns 200).
|
||||
|
||||
RESULT=$(
|
||||
HOME="$TMPHOME" \
|
||||
UNSANDBOX_PUBLIC_KEY="garbage-pk-env" \
|
||||
UNSANDBOX_SECRET_KEY="garbage-sk-env" \
|
||||
java -cp "$SRC_DIR" Un --account 1 key 2>&1
|
||||
) && RC=$? || RC=$?
|
||||
|
||||
if [ $RC -eq 0 ]; then
|
||||
pass "--account 1 loads row 1 from accounts.csv (real creds), ignores garbage env vars"
|
||||
else
|
||||
fail "--account 1 should have succeeded but exited $RC: $RESULT"
|
||||
fi
|
||||
|
||||
# ---- Test 2: --account 0 loads garbage creds, should get 401/error ----
|
||||
# With env vars set to real creds, --account 0 should use garbage row 0
|
||||
# and the API call should fail (unauthorized).
|
||||
|
||||
RESULT=$(
|
||||
HOME="$TMPHOME" \
|
||||
UNSANDBOX_PUBLIC_KEY="$UNSANDBOX_PUBLIC_KEY" \
|
||||
UNSANDBOX_SECRET_KEY="$UNSANDBOX_SECRET_KEY" \
|
||||
java -cp "$SRC_DIR" Un --account 0 key 2>&1
|
||||
) && RC=$? || RC=$?
|
||||
|
||||
if [ $RC -ne 0 ]; then
|
||||
pass "--account 0 loads garbage creds from row 0, API rejects them (env vars ignored)"
|
||||
else
|
||||
fail "--account 0 should have failed (garbage creds) but succeeded: $RESULT"
|
||||
fi
|
||||
|
||||
# ---- Test 3: no --account flag, env vars set to real creds → success ----
|
||||
|
||||
RESULT=$(
|
||||
HOME="$TMPHOME" \
|
||||
UNSANDBOX_PUBLIC_KEY="$UNSANDBOX_PUBLIC_KEY" \
|
||||
UNSANDBOX_SECRET_KEY="$UNSANDBOX_SECRET_KEY" \
|
||||
java -cp "$SRC_DIR" Un key 2>&1
|
||||
) && RC=$? || RC=$?
|
||||
|
||||
if [ $RC -eq 0 ]; then
|
||||
pass "no --account flag uses env vars (real creds), succeeds"
|
||||
else
|
||||
fail "no --account flag should succeed with real env var creds but exited $RC: $RESULT"
|
||||
fi
|
||||
|
||||
# ---- Summary ----
|
||||
|
||||
echo ""
|
||||
echo "Results: PASS=$PASS FAIL=$FAIL SKIP=$SKIP"
|
||||
|
||||
if [ $FAIL -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue