mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(msteams): ignore blank certificate settings (#109112)
* fix(msteams): ignore blank certificate settings * fix(msteams): preserve federated certificate path behavior --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
// Msteams tests cover token plugin behavior.
|
||||
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
||||
import { generateKeyPairSync } from "node:crypto";
|
||||
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { MSTeamsConfig } from "../runtime-api.js";
|
||||
import { setMSTeamsRuntime } from "./runtime.js";
|
||||
import { loadMSTeamsSdkWithAuth } from "./sdk.js";
|
||||
import { msteamsRuntimeStub } from "./test-support/runtime.js";
|
||||
import { readAccessToken } from "./token-response.js";
|
||||
import {
|
||||
@@ -143,6 +145,119 @@ describe("token – federated credentials (certificate)", () => {
|
||||
expect(hasConfiguredMSTeamsCredentials(cfg)).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores blank certificate settings", () => {
|
||||
process.env.MSTEAMS_CERTIFICATE_PATH = " ";
|
||||
const cfg = {
|
||||
appId: "app-id",
|
||||
tenantId: "tenant-id",
|
||||
authType: "federated",
|
||||
certificatePath: " ",
|
||||
} satisfies MSTeamsConfig;
|
||||
|
||||
expect(hasConfiguredMSTeamsCredentials(cfg)).toBe(false);
|
||||
expect(resolveMSTeamsCredentials(cfg)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("falls back to env certificate settings without changing path bytes", () => {
|
||||
process.env.MSTEAMS_CERTIFICATE_PATH = " /env/cert.pem ";
|
||||
process.env.MSTEAMS_CERTIFICATE_THUMBPRINT = "environment-thumbprint";
|
||||
process.env.MSTEAMS_MANAGED_IDENTITY_CLIENT_ID = "environment-managed-identity";
|
||||
const cfg = {
|
||||
appId: "app-id",
|
||||
tenantId: "tenant-id",
|
||||
authType: "federated",
|
||||
certificatePath: " ",
|
||||
certificateThumbprint: " configured-thumbprint ",
|
||||
managedIdentityClientId: " configured-managed-identity ",
|
||||
} satisfies MSTeamsConfig;
|
||||
|
||||
expect(hasConfiguredMSTeamsCredentials(cfg)).toBe(true);
|
||||
expect(resolveMSTeamsCredentials(cfg)).toEqual({
|
||||
type: "federated",
|
||||
appId: "app-id",
|
||||
tenantId: "tenant-id",
|
||||
certificatePath: " /env/cert.pem ",
|
||||
certificateThumbprint: " configured-thumbprint ",
|
||||
useManagedIdentity: undefined,
|
||||
managedIdentityClientId: " configured-managed-identity ",
|
||||
});
|
||||
});
|
||||
|
||||
it("opens the exact environment certificate path when the configured path is blank", async () => {
|
||||
const certificateDirectory = mkdtempSync(
|
||||
path.join(os.tmpdir(), "openclaw-msteams-real-certificate-"),
|
||||
);
|
||||
const certificatePath = path.join(certificateDirectory, " certificate.pem");
|
||||
|
||||
try {
|
||||
const { privateKey } = generateKeyPairSync("rsa", {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: { type: "spki", format: "pem" },
|
||||
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
||||
});
|
||||
writeFileSync(certificatePath, privateKey, { mode: 0o600 });
|
||||
process.env.MSTEAMS_CERTIFICATE_PATH = certificatePath;
|
||||
|
||||
const cfg = {
|
||||
appId: "app-id",
|
||||
tenantId: "tenant-id",
|
||||
authType: "federated",
|
||||
certificatePath: " ",
|
||||
} satisfies MSTeamsConfig;
|
||||
|
||||
expect(hasConfiguredMSTeamsCredentials(cfg)).toBe(true);
|
||||
const credentials = resolveMSTeamsCredentials(cfg);
|
||||
expect(credentials).toMatchObject({
|
||||
type: "federated",
|
||||
certificatePath,
|
||||
});
|
||||
if (!credentials) {
|
||||
throw new Error("expected configured Microsoft Teams certificate credentials");
|
||||
}
|
||||
|
||||
const { app } = await loadMSTeamsSdkWithAuth(credentials);
|
||||
expect(app.tokenManager).toBeDefined();
|
||||
} finally {
|
||||
rmSync(certificateDirectory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves configured nonblank certificate path bytes and precedence", () => {
|
||||
process.env.MSTEAMS_CERTIFICATE_PATH = "/environment/certificate.pem";
|
||||
const certificatePath = " /configured/certificate.pem ";
|
||||
const cfg = {
|
||||
appId: "app-id",
|
||||
tenantId: "tenant-id",
|
||||
authType: "federated",
|
||||
certificatePath,
|
||||
} satisfies MSTeamsConfig;
|
||||
|
||||
expect(hasConfiguredMSTeamsCredentials(cfg)).toBe(true);
|
||||
expect(resolveMSTeamsCredentials(cfg)).toMatchObject({ certificatePath });
|
||||
});
|
||||
|
||||
it("keeps managed identity configured when the optional certificate path is blank", () => {
|
||||
const cfg = {
|
||||
appId: "app-id",
|
||||
tenantId: "tenant-id",
|
||||
authType: "federated",
|
||||
certificatePath: " ",
|
||||
useManagedIdentity: true,
|
||||
managedIdentityClientId: " configured-managed-identity ",
|
||||
} satisfies MSTeamsConfig;
|
||||
|
||||
expect(hasConfiguredMSTeamsCredentials(cfg)).toBe(true);
|
||||
expect(resolveMSTeamsCredentials(cfg)).toEqual({
|
||||
type: "federated",
|
||||
appId: "app-id",
|
||||
tenantId: "tenant-id",
|
||||
certificatePath: undefined,
|
||||
certificateThumbprint: undefined,
|
||||
useManagedIdentity: true,
|
||||
managedIdentityClientId: " configured-managed-identity ",
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves federated credentials with certificate from config", () => {
|
||||
const cfg = {
|
||||
appId: "app-id",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Msteams plugin module implements token behavior.
|
||||
import { isFutureDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { MSTeamsConfig } from "../runtime-api.js";
|
||||
import { loadMSTeamsDelegatedTokens, saveMSTeamsDelegatedTokens } from "./delegated-state.js";
|
||||
import type { MSTeamsDelegatedTokens } from "./oauth.shared.js";
|
||||
@@ -47,6 +48,18 @@ function resolveAuthType(cfg?: MSTeamsConfig): "secret" | "federated" {
|
||||
return "secret";
|
||||
}
|
||||
|
||||
function resolveFederatedPath(configValue?: string, envValue?: string): string | undefined {
|
||||
// Reject blank settings without trimming a real path: surrounding whitespace
|
||||
// can be part of the certificate filename on the filesystem.
|
||||
if (normalizeOptionalString(configValue)) {
|
||||
return configValue;
|
||||
}
|
||||
if (normalizeOptionalString(envValue)) {
|
||||
return envValue;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ── hasConfiguredMSTeamsCredentials ────────────────────────────────────────
|
||||
|
||||
export function hasConfiguredMSTeamsCredentials(cfg?: MSTeamsConfig): boolean {
|
||||
@@ -62,7 +75,9 @@ export function hasConfiguredMSTeamsCredentials(cfg?: MSTeamsConfig): boolean {
|
||||
);
|
||||
|
||||
if (authType === "federated") {
|
||||
const hasCert = Boolean(cfg?.certificatePath || process.env.MSTEAMS_CERTIFICATE_PATH);
|
||||
const hasCert = Boolean(
|
||||
resolveFederatedPath(cfg?.certificatePath, process.env.MSTEAMS_CERTIFICATE_PATH),
|
||||
);
|
||||
const hasManagedIdentity =
|
||||
cfg?.useManagedIdentity ?? process.env.MSTEAMS_USE_MANAGED_IDENTITY === "true";
|
||||
|
||||
@@ -95,8 +110,10 @@ export function resolveMSTeamsCredentials(cfg?: MSTeamsConfig): MSTeamsCredentia
|
||||
}
|
||||
|
||||
if (authType === "federated") {
|
||||
const certificatePath =
|
||||
cfg?.certificatePath || process.env.MSTEAMS_CERTIFICATE_PATH || undefined;
|
||||
const certificatePath = resolveFederatedPath(
|
||||
cfg?.certificatePath,
|
||||
process.env.MSTEAMS_CERTIFICATE_PATH,
|
||||
);
|
||||
|
||||
const certificateThumbprint =
|
||||
cfg?.certificateThumbprint || process.env.MSTEAMS_CERTIFICATE_THUMBPRINT || undefined;
|
||||
|
||||
Reference in New Issue
Block a user