Add create and delete scripts creating Public Client Application (Remove unused JavaScript version).
This commit is contained in:
@@ -1,256 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
|
|
||||||
import fs from "node:fs";
|
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
import readline from "node:readline";
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
import { parseArgs } from "node:util";
|
|
||||||
|
|
||||||
function runAz(args, options = {}) {
|
|
||||||
const result = spawnSync("az", args, {
|
|
||||||
encoding: "utf8",
|
|
||||||
stdio: options.quiet
|
|
||||||
? ["ignore", "ignore", "ignore"]
|
|
||||||
: ["ignore", "pipe", "pipe"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
throw result.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.status !== 0 && options.allowFailure !== true) {
|
|
||||||
throw new Error(
|
|
||||||
(result.stderr || "").trim() || `az ${args.join(" ")} failed`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: result.status ?? 1,
|
|
||||||
stdout: (result.stdout || "").trim(),
|
|
||||||
stderr: (result.stderr || "").trim(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const usageText = `Usage: ${path.basename(process.argv[1])} [options] <app-name>
|
|
||||||
Options:
|
|
||||||
-c, --config <path> Write JSON config to file (optional)
|
|
||||||
-h, --help Show this help message and exit`;
|
|
||||||
let values;
|
|
||||||
let positionals;
|
|
||||||
try {
|
|
||||||
({ values, positionals } = parseArgs({
|
|
||||||
args: process.argv.slice(2),
|
|
||||||
options: {
|
|
||||||
help: { type: "boolean", short: "h" },
|
|
||||||
config: { type: "string", short: "c" },
|
|
||||||
},
|
|
||||||
strict: true,
|
|
||||||
allowPositionals: true,
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error: ${err.message}`);
|
|
||||||
console.error(usageText);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.help) {
|
|
||||||
console.log(usageText);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (positionals.length > 1) {
|
|
||||||
console.error(
|
|
||||||
"Error: Too many positional arguments. Only one app name positional argument is allowed.",
|
|
||||||
);
|
|
||||||
console.error(usageText);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const appName = positionals[0] || "";
|
|
||||||
const configPath = values.config || "";
|
|
||||||
|
|
||||||
if (!appName) {
|
|
||||||
console.error("Error: Application name is required.");
|
|
||||||
console.error(usageText);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let appId = runAz([
|
|
||||||
"ad",
|
|
||||||
"app",
|
|
||||||
"list",
|
|
||||||
"--display-name",
|
|
||||||
appName,
|
|
||||||
"--query",
|
|
||||||
"[0].appId",
|
|
||||||
"-o",
|
|
||||||
"tsv",
|
|
||||||
]).stdout;
|
|
||||||
|
|
||||||
if (appId) {
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stderr,
|
|
||||||
});
|
|
||||||
const answer = await new Promise((resolve) => {
|
|
||||||
rl.question(
|
|
||||||
`Application '${appName}' already exists. Update it? [y/N]: `,
|
|
||||||
(answer) => {
|
|
||||||
rl.close();
|
|
||||||
resolve(answer.trim());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!/^(yes|y)$/i.test(answer)) {
|
|
||||||
console.error("Canceled.");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!appId) {
|
|
||||||
appId = runAz([
|
|
||||||
"ad",
|
|
||||||
"app",
|
|
||||||
"create",
|
|
||||||
"--display-name",
|
|
||||||
appName,
|
|
||||||
"--query",
|
|
||||||
"appId",
|
|
||||||
"-o",
|
|
||||||
"tsv",
|
|
||||||
]).stdout;
|
|
||||||
|
|
||||||
if (!appId) {
|
|
||||||
console.error(`Error: Failed to create application '${appName}'.`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const requiredResourceAccess = [
|
|
||||||
{
|
|
||||||
resourceAppId: "00000003-0000-0000-c000-000000000000",
|
|
||||||
resourceAccess: [
|
|
||||||
{ id: "0e263e50-5827-48a4-b97c-d940288653c7", type: "Scope" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resourceAppId: "499b84ac-1321-427f-aa17-267ca6975798",
|
|
||||||
resourceAccess: [
|
|
||||||
{ id: "ee69721e-6c3a-468f-a9ec-302d16a4c599", type: "Scope" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resourceAppId: "797f4846-ba00-4fd7-ba43-dac1f8f63013",
|
|
||||||
resourceAccess: [
|
|
||||||
{ id: "41094075-9dad-400e-a0bd-54e686782033", type: "Scope" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "create-pca-"));
|
|
||||||
const requiredResourceAccessFile = path.join(
|
|
||||||
tempDir,
|
|
||||||
"required-resource-accesses.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(
|
|
||||||
requiredResourceAccessFile,
|
|
||||||
JSON.stringify(requiredResourceAccess, null, 2),
|
|
||||||
"utf8",
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
runAz(
|
|
||||||
[
|
|
||||||
"ad",
|
|
||||||
"app",
|
|
||||||
"update",
|
|
||||||
"--id",
|
|
||||||
appId,
|
|
||||||
"--sign-in-audience",
|
|
||||||
"AzureADMyOrg",
|
|
||||||
"--is-fallback-public-client",
|
|
||||||
"true",
|
|
||||||
"--required-resource-accesses",
|
|
||||||
`@${requiredResourceAccessFile}`,
|
|
||||||
"--public-client-redirect-uris",
|
|
||||||
"http://localhost",
|
|
||||||
`msal${appId}://auth`,
|
|
||||||
"--enable-access-token-issuance",
|
|
||||||
"true",
|
|
||||||
"--enable-id-token-issuance",
|
|
||||||
"true",
|
|
||||||
],
|
|
||||||
{ quiet: true },
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
console.error(
|
|
||||||
`Error: Failed to configure application '${appName}' (${appId}).`,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
runAz(["ad", "sp", "create", "--id", appId], {
|
|
||||||
quiet: true,
|
|
||||||
allowFailure: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const adminConsentResult = runAz(
|
|
||||||
["ad", "app", "permission", "admin-consent", "--id", appId],
|
|
||||||
{ quiet: true, allowFailure: true },
|
|
||||||
);
|
|
||||||
if (adminConsentResult.status !== 0) {
|
|
||||||
console.warn(
|
|
||||||
`Warning: Failed to grant admin consent for '${appName}' (${appId}). Continuing without failing.`,
|
|
||||||
);
|
|
||||||
if (adminConsentResult.stderr) {
|
|
||||||
console.warn(adminConsentResult.stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tenantId = runAz([
|
|
||||||
"account",
|
|
||||||
"show",
|
|
||||||
"--query",
|
|
||||||
"tenantId",
|
|
||||||
"-o",
|
|
||||||
"tsv",
|
|
||||||
]).stdout;
|
|
||||||
if (!tenantId) {
|
|
||||||
console.error(
|
|
||||||
"Error: Failed to resolve tenantId from current Azure CLI context.",
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const configTemplate = JSON.stringify(
|
|
||||||
{
|
|
||||||
tenantId,
|
|
||||||
clientId: appId,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (configPath) {
|
|
||||||
const targetPath = path.resolve(configPath);
|
|
||||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
||||||
fs.writeFileSync(targetPath, `${configTemplate}\n`, "utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(configTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((err) => {
|
|
||||||
console.error(`Error: ${err.message}`);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
139
scripts/create-pca.sh
Executable file
139
scripts/create-pca.sh
Executable file
@@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") [options] <app-name>
|
||||||
|
Options:
|
||||||
|
-c, --config <path> Write JSON config to file (optional)
|
||||||
|
-h, --help Show this help message and exit
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_PATH=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-c|--config)
|
||||||
|
if [[ $# -le 0 ]]; then
|
||||||
|
echo "Error: Missing value for --config" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
CONFIG_PATH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Error: Unknown option: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_NAME="${1:-}"
|
||||||
|
if [[ -z "$APP_NAME" ]]; then
|
||||||
|
echo "Error: Application name is required." >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_ID="$(az ad app list --display-name "$APP_NAME" | jq -r '[.[].appId] | join(",")')"
|
||||||
|
if [[ "$APP_ID" =~ "," ]]; then
|
||||||
|
echo "Error: The application name '$APP_NAME' is not unique." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$APP_ID" ]]; then
|
||||||
|
APP_ID="$(az ad app create --display-name "$APP_NAME" --query appId -o tsv)"
|
||||||
|
if [[ -z "$APP_ID" ]]; then
|
||||||
|
echo "Error: Failed to create application '$APP_NAME'." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Created application '$APP_NAME' with appId '$APP_ID'."
|
||||||
|
else
|
||||||
|
printf "Application '%s' already exists. Update it? [y/N]: " "$APP_NAME" >&2
|
||||||
|
read -r ANSWER
|
||||||
|
if [[ ! "$ANSWER" =~ ^[Yy]([Ee][Ss])*$ ]]; then
|
||||||
|
echo "Canceled." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
RESOURCE_ACCESS='[
|
||||||
|
{
|
||||||
|
"resourceAppId": "00000003-0000-0000-c000-000000000000",
|
||||||
|
"resourceAccess": [
|
||||||
|
{ "id": "0e263e50-5827-48a4-b97c-d940288653c7", "type": "Scope" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceAppId": "499b84ac-1321-427f-aa17-267ca6975798",
|
||||||
|
"resourceAccess": [
|
||||||
|
{ "id": "ee69721e-6c3a-468f-a9ec-302d16a4c599", "type": "Scope" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceAppId": "797f4846-ba00-4fd7-ba43-dac1f8f63013",
|
||||||
|
"resourceAccess": [
|
||||||
|
{ "id": "41094075-9dad-400e-a0bd-54e686782033", "type": "Scope" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]'
|
||||||
|
|
||||||
|
if ! az ad app update \
|
||||||
|
--id "$APP_ID" \
|
||||||
|
--sign-in-audience AzureADMyOrg \
|
||||||
|
--is-fallback-public-client true \
|
||||||
|
--required-resource-accesses "$RESOURCE_ACCESS" \
|
||||||
|
--public-client-redirect-uris http://localhost "msal${APP_ID}://auth" \
|
||||||
|
--enable-access-token-issuance true \
|
||||||
|
--enable-id-token-issuance true \
|
||||||
|
>/dev/null 2>&1; then
|
||||||
|
echo "Error: Failed to configure application '$APP_NAME' ($APP_ID)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SP_ID="$(az ad sp show --id "$APP_ID" --query id -o tsv)"
|
||||||
|
if [[ -z "$SP_ID" ]]; then
|
||||||
|
SP_ID="$(az ad sp create --id "$APP_ID" --query id -o tsv)"
|
||||||
|
if [[ -z "$SP_ID" ]]; then
|
||||||
|
echo "Error: Failed to create service principal for application '$APP_NAME' ($APP_ID)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Service principal for application '$APP_NAME' already exists with id '$SP_ID'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
az ad app permission admin-consent --id "$APP_ID"
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
echo "Error: Failed to grant admin consent for application '$APP_NAME' ($APP_ID)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TENANT_ID="$(az account show --query tenantId -o tsv)"
|
||||||
|
if [[ -z "$TENANT_ID" ]]; then
|
||||||
|
echo "Error: Failed to resolve tenantId from current Azure CLI context." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONFIG="{
|
||||||
|
\"tenantId\": \"$TENANT_ID\",
|
||||||
|
\"clientId\": \"$APP_ID\"
|
||||||
|
}
|
||||||
|
"
|
||||||
|
|
||||||
|
if [[ -n "$CONFIG_PATH" ]]; then
|
||||||
|
mkdir -p "$(dirname "$CONFIG_PATH")"
|
||||||
|
else
|
||||||
|
CONFIG_PATH="/dev/null"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$CONFIG" | tee "$CONFIG_PATH"
|
||||||
44
scripts/delete-pca.sh
Executable file
44
scripts/delete-pca.sh
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
APP_NAME="${1:-}"
|
||||||
|
if [[ -z "$APP_NAME" ]]; then
|
||||||
|
echo "Error: Application name is required." >&2
|
||||||
|
echo "Usage: $(basename "$0") <app-name>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_ID="$(az ad app list --display-name "$APP_NAME" | jq -r '[.[].appId] | join(",")')"
|
||||||
|
if [[ "$APP_ID" =~ "," ]]; then
|
||||||
|
echo "Error: The application name '$APP_NAME' is not unique." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$APP_ID" ]]; then
|
||||||
|
echo "Error: No application found with name '$APP_NAME'." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SP_ID="$(az ad sp show --id "$APP_ID" --query id -o tsv)"
|
||||||
|
|
||||||
|
if [[ -z "$SP_ID" ]]; then
|
||||||
|
echo "No service principal found for application '$APP_NAME' ($APP_ID)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get confirmation from user before deleting
|
||||||
|
read -p "Are you sure you want to delete application '$APP_NAME' with appId '$APP_ID' and its service principal? (y/N) " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborting deletion."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$SP_ID" ]]; then
|
||||||
|
az ad sp delete --id "$SP_ID"
|
||||||
|
echo "Deleted service principal with id '$SP_ID' for application '$APP_NAME' ($APP_ID)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
az ad app delete --id "$APP_ID"
|
||||||
|
echo "Deleted application '$APP_NAME' with appId '$APP_ID'."
|
||||||
Reference in New Issue
Block a user