mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Remove MDM and organization membership enforcement (#12323)
This commit is contained in:
parent
ad25634905
commit
3d37e054dd
28 changed files with 11 additions and 914 deletions
|
|
@ -370,7 +370,6 @@ export type ExtensionState = Pick<
|
|||
lastShownAnnouncementId?: string
|
||||
apiModelId?: string
|
||||
mcpServers?: McpServer[]
|
||||
mdmCompliant?: boolean
|
||||
taskSyncEnabled: boolean
|
||||
openAiCodexIsAuthenticated?: boolean
|
||||
debug?: boolean
|
||||
|
|
@ -542,7 +541,6 @@ export interface WebviewMessage {
|
|||
| "deleteCommand"
|
||||
| "createCommand"
|
||||
| "insertTextIntoTextarea"
|
||||
| "showMdmAuthRequiredNotification"
|
||||
| "imageGenerationSettings"
|
||||
| "queueMessage"
|
||||
| "removeQueuedMessage"
|
||||
|
|
|
|||
|
|
@ -143,12 +143,6 @@ vi.mock("../services/code-index/manager", () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
vi.mock("../services/mdm/MdmService", () => ({
|
||||
MdmService: {
|
||||
createInstance: vi.fn().mockResolvedValue(null),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("../utils/migrateSettings", () => ({
|
||||
migrateSettings: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { focusPanel } from "../utils/focusPanel"
|
|||
import { handleNewTask } from "./handleTask"
|
||||
import { CodeIndexManager } from "../services/code-index/manager"
|
||||
import { importSettingsWithFeedback } from "../core/config/importExport"
|
||||
import { MdmService } from "../services/mdm/MdmService"
|
||||
import { t } from "../i18n"
|
||||
|
||||
/**
|
||||
|
|
@ -205,16 +204,7 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit<Registe
|
|||
const contextProxy = await ContextProxy.getInstance(context)
|
||||
const codeIndexManager = CodeIndexManager.getInstance(context)
|
||||
|
||||
// Get the existing MDM service instance to ensure consistent policy enforcement
|
||||
let mdmService: MdmService | undefined
|
||||
try {
|
||||
mdmService = MdmService.getInstance()
|
||||
} catch (error) {
|
||||
// MDM service not initialized, which is fine - extension can work without it
|
||||
mdmService = undefined
|
||||
}
|
||||
|
||||
const tabProvider = new ClineProvider(context, outputChannel, "editor", contextProxy, mdmService)
|
||||
const tabProvider = new ClineProvider(context, outputChannel, "editor", contextProxy)
|
||||
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
|
||||
|
||||
// Check if there are any visible text editors, otherwise open a new group
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ import { MarketplaceManager } from "../../services/marketplace"
|
|||
import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService"
|
||||
import { CodeIndexManager } from "../../services/code-index/manager"
|
||||
import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
|
||||
import { MdmService } from "../../services/mdm/MdmService"
|
||||
import { SkillsManager } from "../../services/skills/SkillsManager"
|
||||
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
|
|
@ -143,7 +142,6 @@ export class ClineProvider
|
|||
protected mcpHub?: McpHub // Change from private to protected
|
||||
protected skillsManager?: SkillsManager
|
||||
private marketplaceManager: MarketplaceManager
|
||||
private mdmService?: MdmService
|
||||
private taskCreationCallback: (task: Task) => void
|
||||
private taskEventListeners: WeakMap<Task, Array<() => void>> = new WeakMap()
|
||||
private currentWorkspacePath: string | undefined
|
||||
|
|
@ -178,14 +176,12 @@ export class ClineProvider
|
|||
private readonly outputChannel: vscode.OutputChannel,
|
||||
private readonly renderContext: "sidebar" | "editor" = "sidebar",
|
||||
public readonly contextProxy: ContextProxy,
|
||||
mdmService?: MdmService,
|
||||
) {
|
||||
super()
|
||||
this.currentWorkspacePath = getWorkspacePath()
|
||||
|
||||
ClineProvider.activeInstances.add(this)
|
||||
|
||||
this.mdmService = mdmService
|
||||
this.updateGlobalState("codebaseIndexModels", EMBEDDING_MODEL_PROFILES)
|
||||
|
||||
// Initialize the per-task file-based history store.
|
||||
|
|
@ -1971,12 +1967,6 @@ export class ClineProvider
|
|||
this.clineMessagesSeq++
|
||||
state.clineMessagesSeq = this.clineMessagesSeq
|
||||
this.postMessageToWebview({ type: "state", state })
|
||||
|
||||
// Check MDM compliance and send user to account tab if not compliant
|
||||
// Only redirect if there's an actual MDM policy requiring authentication
|
||||
if (this.mdmService?.requiresCloudAuth() && !this.checkMdmCompliance()) {
|
||||
await this.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1993,11 +1983,6 @@ export class ClineProvider
|
|||
state.clineMessagesSeq = this.clineMessagesSeq
|
||||
const { taskHistory: _omit, ...rest } = state
|
||||
this.postMessageToWebview({ type: "state", state: rest })
|
||||
|
||||
// Preserve existing MDM redirect behavior
|
||||
if (this.mdmService?.requiresCloudAuth() && !this.checkMdmCompliance()) {
|
||||
await this.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2015,11 +2000,6 @@ export class ClineProvider
|
|||
const state = await this.getStateToPostToWebview()
|
||||
const { clineMessages: _omitMessages, taskHistory: _omitHistory, ...rest } = state
|
||||
this.postMessageToWebview({ type: "state", state: rest })
|
||||
|
||||
// Preserve existing MDM redirect behavior
|
||||
if (this.mdmService?.requiresCloudAuth() && !this.checkMdmCompliance()) {
|
||||
await this.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2335,9 +2315,6 @@ export class ClineProvider
|
|||
codebaseIndexBedrockProfile: codebaseIndexConfig?.codebaseIndexBedrockProfile,
|
||||
codebaseIndexOpenRouterSpecificProvider: codebaseIndexConfig?.codebaseIndexOpenRouterSpecificProvider,
|
||||
},
|
||||
// Only set mdmCompliant if there's an actual MDM policy
|
||||
// undefined means no MDM policy, true means compliant, false means non-compliant
|
||||
mdmCompliant: this.mdmService?.requiresCloudAuth() ? this.checkMdmCompliance() : undefined,
|
||||
profileThresholds: profileThresholds ?? {},
|
||||
cloudApiUrl: getRooCodeApiUrl(),
|
||||
hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false,
|
||||
|
|
@ -2751,24 +2728,6 @@ export class ClineProvider
|
|||
return this.skillsManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current state is compliant with MDM policy
|
||||
* @returns true if compliant or no MDM policy exists, false if MDM policy exists and user is non-compliant
|
||||
*/
|
||||
public checkMdmCompliance(): boolean {
|
||||
if (!this.mdmService) {
|
||||
return true // No MDM service, allow operation
|
||||
}
|
||||
|
||||
const compliance = this.mdmService.isCompliant()
|
||||
|
||||
if (!compliance.compliant) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CodeIndexManager for the current active workspace
|
||||
* @returns CodeIndexManager instance for the current workspace or the default one
|
||||
|
|
|
|||
|
|
@ -3229,12 +3229,6 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
break
|
||||
}
|
||||
case "showMdmAuthRequiredNotification": {
|
||||
// Show notification that organization requires authentication
|
||||
vscode.window.showWarningMessage(t("common:mdm.info.organization_requires_auth"))
|
||||
break
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat Message Queue
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import { TerminalRegistry } from "./integrations/terminal/TerminalRegistry"
|
|||
import { openAiCodexOAuthManager } from "./integrations/openai-codex/oauth"
|
||||
import { McpServerManager } from "./services/mcp/McpServerManager"
|
||||
import { CodeIndexManager } from "./services/code-index/manager"
|
||||
import { MdmService } from "./services/mdm/MdmService"
|
||||
import { migrateSettings } from "./utils/migrateSettings"
|
||||
import { autoImportSettings } from "./utils/autoImportSettings"
|
||||
import { API } from "./extension/api"
|
||||
|
|
@ -146,9 +145,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
// Create logger for cloud services.
|
||||
const cloudLogger = createDualLogger(createOutputChannelLogger(outputChannel))
|
||||
|
||||
// Initialize MDM service
|
||||
const mdmService = await MdmService.createInstance(cloudLogger)
|
||||
|
||||
// Initialize i18n for internationalization support.
|
||||
initializeI18n(context.globalState.get("language") ?? formatLanguage(vscode.env.language))
|
||||
|
||||
|
|
@ -192,7 +188,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
}
|
||||
|
||||
// Initialize the provider *before* the Roo Code Cloud service.
|
||||
const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, mdmService)
|
||||
const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy)
|
||||
|
||||
// Initialize Roo Code Cloud service.
|
||||
const postStateListener = () => ClineProvider.getVisibleInstance()?.postStateToWebviewWithoutClineMessages()
|
||||
|
|
|
|||
15
src/i18n/locales/ca/common.json
generated
15
src/i18n/locales/ca/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "El mode s'ha eliminat correctament, però no s'ha pogut eliminar la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis d'eliminar manualment."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "La teva organització requereix autenticació de Roo Code Cloud. Si us plau, inicia sessió per continuar.",
|
||||
"organization_mismatch": "Has d'estar autenticat amb el compte de Roo Code Cloud de la teva organització.",
|
||||
"manual_url_empty": "Si us plau, introdueix una URL de callback vàlida",
|
||||
"manual_url_no_query": "URL de callback no vàlida: falten paràmetres de consulta",
|
||||
"manual_url_missing_params": "URL de callback no vàlida: falten paràmetres requerits (code i state)",
|
||||
"manual_url_auth_failed": "Autenticació manual per URL ha fallat",
|
||||
"manual_url_auth_error": "Autenticació fallida",
|
||||
"verification_failed": "No s'ha pogut verificar l'autenticació de l'organització."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "La teva organització requereix autenticació."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Suprimeix el mode personalitzat",
|
||||
|
|
|
|||
10
src/i18n/locales/de/common.json
generated
10
src/i18n/locales/de/common.json
generated
|
|
@ -220,16 +220,6 @@
|
|||
"rulesCleanupFailed": "Der Modus wurde erfolgreich entfernt, aber der Regelordner unter {{rulesFolderPath}} konnte nicht gelöscht werden. Möglicherweise musst du ihn manuell löschen."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Deine Organisation erfordert eine Roo Code Cloud-Authentifizierung. Bitte melde dich an, um fortzufahren.",
|
||||
"organization_mismatch": "Du musst mit dem Roo Code Cloud-Konto deiner Organisation authentifiziert sein.",
|
||||
"verification_failed": "Die Organisationsauthentifizierung konnte nicht verifiziert werden."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Deine Organisation erfordert eine Authentifizierung."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Benutzerdefinierten Modus löschen",
|
||||
|
|
|
|||
|
|
@ -217,16 +217,6 @@
|
|||
"rulesCleanupFailed": "Mode removed successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Your organization requires Roo Code Cloud authentication. Please sign in to continue.",
|
||||
"organization_mismatch": "You must be authenticated with your organization's Roo Code Cloud account.",
|
||||
"verification_failed": "Unable to verify organization authentication."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Your organization requires authentication."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Delete Custom Mode",
|
||||
|
|
|
|||
10
src/i18n/locales/es/common.json
generated
10
src/i18n/locales/es/common.json
generated
|
|
@ -220,16 +220,6 @@
|
|||
"rulesCleanupFailed": "El modo se eliminó correctamente, pero no se pudo eliminar la carpeta de reglas en {{rulesFolderPath}}. Es posible que debas eliminarla manually."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Tu organización requiere autenticación de Roo Code Cloud. Por favor, inicia sesión para continuar.",
|
||||
"organization_mismatch": "Debes estar autenticado con la cuenta de Roo Code Cloud de tu organización.",
|
||||
"verification_failed": "No se pudo verificar la autenticación de la organización."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Tu organización requiere autenticación."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Eliminar modo personalizado",
|
||||
|
|
|
|||
15
src/i18n/locales/fr/common.json
generated
15
src/i18n/locales/fr/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "Le mode a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Votre organisation nécessite une authentification Roo Code Cloud. Veuillez vous connecter pour continuer.",
|
||||
"organization_mismatch": "Vous devez être authentifié avec le compte Roo Code Cloud de votre organisation.",
|
||||
"manual_url_empty": "Veuillez entrer une URL de callback valide",
|
||||
"manual_url_no_query": "URL de callback invalide : paramètres de requête manquants",
|
||||
"manual_url_missing_params": "URL de callback invalide : paramètres requis manquants (code et state)",
|
||||
"manual_url_auth_failed": "Authentification par URL manuelle échouée",
|
||||
"manual_url_auth_error": "Échec de l'authentification",
|
||||
"verification_failed": "Impossible de vérifier l'authentification de l'organisation."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Votre organisation nécessite une authentification."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Supprimer le mode personnalisé",
|
||||
|
|
|
|||
15
src/i18n/locales/hi/common.json
generated
15
src/i18n/locales/hi/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "मोड सफलतापूर्वक हटा दिया गया, लेकिन {{rulesFolderPath}} पर नियम फ़ोल्डर को हटाने में विफल रहा। आपको इसे मैन्युअल रूप से हटाना पड़ सकता है।"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "आपके संगठन को Roo Code Cloud प्रमाणीकरण की आवश्यकता है। कृपया जारी रखने के लिए साइन इन करें।",
|
||||
"organization_mismatch": "आपको अपने संगठन के Roo Code Cloud खाते से प्रमाणित होना होगा।",
|
||||
"manual_url_empty": "कृपया एक वैध callback URL दर्ज करें",
|
||||
"manual_url_no_query": "अवैध callback URL: क्वेरी पैरामीटर गुम हैं",
|
||||
"manual_url_missing_params": "अवैध callback URL: आवश्यक पैरामीटर गुम हैं (code और state)",
|
||||
"manual_url_auth_failed": "मैनुअल URL प्रमाणीकरण असफल",
|
||||
"manual_url_auth_error": "प्रमाणीकरण असफल",
|
||||
"verification_failed": "संगठन प्रमाणीकरण सत्यापित करने में असमर्थ।"
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "आपके संगठन को प्रमाणीकरण की आवश्यकता है।"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "कस्टम मोड हटाएं",
|
||||
|
|
|
|||
15
src/i18n/locales/id/common.json
generated
15
src/i18n/locales/id/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "Mode berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Organisasi kamu memerlukan autentikasi Roo Code Cloud. Silakan masuk untuk melanjutkan.",
|
||||
"organization_mismatch": "Kamu harus diautentikasi dengan akun Roo Code Cloud organisasi kamu.",
|
||||
"manual_url_empty": "Silakan masukkan URL callback yang valid",
|
||||
"manual_url_no_query": "URL callback tidak valid: parameter query hilang",
|
||||
"manual_url_missing_params": "URL callback tidak valid: parameter yang diperlukan hilang (code dan state)",
|
||||
"manual_url_auth_failed": "Autentikasi URL manual gagal",
|
||||
"manual_url_auth_error": "Autentikasi gagal",
|
||||
"verification_failed": "Tidak dapat memverifikasi autentikasi organisasi."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Organisasi kamu memerlukan autentikasi."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Hapus Mode Kustom",
|
||||
|
|
|
|||
15
src/i18n/locales/it/common.json
generated
15
src/i18n/locales/it/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "La modalità è stata rimossa con successo, ma non è stato possibile eliminare la cartella delle regole in {{rulesFolderPath}}. Potrebbe essere necessario eliminarla manualmente."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "La tua organizzazione richiede l'autenticazione Roo Code Cloud. Accedi per continuare.",
|
||||
"organization_mismatch": "Devi essere autenticato con l'account Roo Code Cloud della tua organizzazione.",
|
||||
"manual_url_empty": "Inserisci un URL di callback valido",
|
||||
"manual_url_no_query": "URL di callback non valido: parametri di query mancanti",
|
||||
"manual_url_missing_params": "URL di callback non valido: parametri richiesti mancanti (code e state)",
|
||||
"manual_url_auth_failed": "Autenticazione manuale tramite URL fallita",
|
||||
"manual_url_auth_error": "Autenticazione fallita",
|
||||
"verification_failed": "Impossibile verificare l'autenticazione dell'organizzazione."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "La tua organizzazione richiede l'autenticazione."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Elimina Modalità Personalizzata",
|
||||
|
|
|
|||
15
src/i18n/locales/ja/common.json
generated
15
src/i18n/locales/ja/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "モードは正常に削除されましたが、{{rulesFolderPath}} にあるルールフォルダの削除に失敗しました。手動で削除する必要がある場合があります。"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "あなたの組織では Roo Code Cloud 認証が必要です。続行するにはサインインしてください。",
|
||||
"organization_mismatch": "組織の Roo Code Cloud アカウントで認証する必要があります。",
|
||||
"manual_url_empty": "有効なコールバック URL を入力してください",
|
||||
"manual_url_no_query": "無効なコールバック URL:クエリパラメータがありません",
|
||||
"manual_url_missing_params": "無効なコールバック URL:必要なパラメータ(code と state)がありません",
|
||||
"manual_url_auth_failed": "手動 URL 認証が失敗しました",
|
||||
"manual_url_auth_error": "認証に失敗しました",
|
||||
"verification_failed": "組織認証の確認ができませんでした。"
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "あなたの組織では認証が必要です。"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "カスタムモードの削除",
|
||||
|
|
|
|||
15
src/i18n/locales/ko/common.json
generated
15
src/i18n/locales/ko/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "모드가 성공적으로 제거되었지만 {{rulesFolderPath}}의 규칙 폴더를 삭제하지 못했습니다. 수동으로 삭제해야 할 수도 있습니다."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "조직에서 Roo Code Cloud 인증이 필요합니다. 계속하려면 로그인하세요.",
|
||||
"organization_mismatch": "조직의 Roo Code Cloud 계정으로 인증해야 합니다.",
|
||||
"manual_url_empty": "유효한 콜백 URL을 입력하세요",
|
||||
"manual_url_no_query": "유효하지 않은 콜백 URL: 쿼리 매개변수 누락",
|
||||
"manual_url_missing_params": "유효하지 않은 콜백 URL: 필요한 매개변수 누락 (code와 state)",
|
||||
"manual_url_auth_failed": "수동 URL 인증 실패",
|
||||
"manual_url_auth_error": "인증 실패",
|
||||
"verification_failed": "조직 인증을 확인할 수 없습니다."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "조직에서 인증이 필요합니다."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "사용자 정의 모드 삭제",
|
||||
|
|
|
|||
15
src/i18n/locales/nl/common.json
generated
15
src/i18n/locales/nl/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "Modus succesvol verwijderd, maar het verwijderen van de regelsmap op {{rulesFolderPath}} is mislukt. Je moet deze mogelijk handmatig verwijderen."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Je organisatie vereist Roo Code Cloud-authenticatie. Log in om door te gaan.",
|
||||
"organization_mismatch": "Je moet geauthenticeerd zijn met het Roo Code Cloud-account van je organisatie.",
|
||||
"manual_url_empty": "Voer een geldige callback-URL in",
|
||||
"manual_url_no_query": "Ongeldige callback-URL: query-parameters ontbreken",
|
||||
"manual_url_missing_params": "Ongeldige callback-URL: vereiste parameters ontbreken (code en state)",
|
||||
"manual_url_auth_failed": "Handmatige URL-authenticatie mislukt",
|
||||
"manual_url_auth_error": "Authenticatie mislukt",
|
||||
"verification_failed": "Kan organisatie-authenticatie niet verifiëren."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Je organisatie vereist authenticatie."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Aangepaste modus verwijderen",
|
||||
|
|
|
|||
15
src/i18n/locales/pl/common.json
generated
15
src/i18n/locales/pl/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "Tryb został pomyślnie usunięty, ale nie udało się usunąć folderu reguł w {{rulesFolderPath}}. Może być konieczne ręczne usunięcie."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Twoja organizacja wymaga uwierzytelnienia Roo Code Cloud. Zaloguj się, aby kontynuować.",
|
||||
"organization_mismatch": "Musisz być uwierzytelniony kontem Roo Code Cloud swojej organizacji.",
|
||||
"manual_url_empty": "Wprowadź prawidłowy URL callback",
|
||||
"manual_url_no_query": "Nieprawidłowy URL callback: brak parametrów zapytania",
|
||||
"manual_url_missing_params": "Nieprawidłowy URL callback: brak wymaganych parametrów (code i state)",
|
||||
"manual_url_auth_failed": "Ręczne uwierzytelnienie URL nie powiodło się",
|
||||
"manual_url_auth_error": "Uwierzytelnienie nie powiodło się",
|
||||
"verification_failed": "Nie można zweryfikować uwierzytelnienia organizacji."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Twoja organizacja wymaga uwierzytelnienia."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Usuń tryb niestandardowy",
|
||||
|
|
|
|||
15
src/i18n/locales/pt-BR/common.json
generated
15
src/i18n/locales/pt-BR/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "O modo foi removido com sucesso, mas falhou ao excluir a pasta de regras em {{rulesFolderPath}}. Você pode precisar excluí-la manualmente."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Sua organização requer autenticação do Roo Code Cloud. Faça login para continuar.",
|
||||
"organization_mismatch": "Você deve estar autenticado com a conta Roo Code Cloud da sua organização.",
|
||||
"manual_url_empty": "Por favor, insira uma URL de callback válida",
|
||||
"manual_url_no_query": "URL de callback inválida: parâmetros de consulta ausentes",
|
||||
"manual_url_missing_params": "URL de callback inválida: parâmetros obrigatórios ausentes (code e state)",
|
||||
"manual_url_auth_failed": "Autenticação manual por URL falhou",
|
||||
"manual_url_auth_error": "Falha na autenticação",
|
||||
"verification_failed": "Não foi possível verificar a autenticação da organização."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Sua organização requer autenticação."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Excluir Modo Personalizado",
|
||||
|
|
|
|||
15
src/i18n/locales/ru/common.json
generated
15
src/i18n/locales/ru/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "Режим успешно удален, но не удалось удалить папку правил в {{rulesFolderPath}}. Возможно, вам придется удалить ее вручную."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Ваша организация требует аутентификации Roo Code Cloud. Войдите в систему, чтобы продолжить.",
|
||||
"organization_mismatch": "Вы должны быть аутентифицированы с учетной записью Roo Code Cloud вашей организации.",
|
||||
"manual_url_empty": "Введи действительный URL обратного вызова",
|
||||
"manual_url_no_query": "Недействительный URL обратного вызова: отсутствуют параметры запроса",
|
||||
"manual_url_missing_params": "Недействительный URL обратного вызова: отсутствуют обязательные параметры (code и state)",
|
||||
"manual_url_auth_failed": "Ручная аутентификация по URL не удалась",
|
||||
"manual_url_auth_error": "Аутентификация не удалась",
|
||||
"verification_failed": "Не удается проверить аутентификацию организации."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Ваша организация требует аутентификации."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Удалить пользовательский режим",
|
||||
|
|
|
|||
15
src/i18n/locales/tr/common.json
generated
15
src/i18n/locales/tr/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "Mod başarıyla kaldırıldı, ancak {{rulesFolderPath}} konumundaki kurallar klasörü silinemedi. Manuel olarak silmeniz gerekebilir."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Kuruluşunuz Roo Code Cloud kimlik doğrulaması gerektiriyor. Devam etmek için giriş yapın.",
|
||||
"organization_mismatch": "Kuruluşunuzun Roo Code Cloud hesabıyla kimlik doğrulaması yapmalısınız.",
|
||||
"manual_url_empty": "Lütfen geçerli bir callback URL'si girin",
|
||||
"manual_url_no_query": "Geçersiz callback URL'si: sorgu parametreleri eksik",
|
||||
"manual_url_missing_params": "Geçersiz callback URL'si: gerekli parametreler eksik (code ve state)",
|
||||
"manual_url_auth_failed": "Manuel URL kimlik doğrulama başarısız",
|
||||
"manual_url_auth_error": "Kimlik doğrulama başarısız",
|
||||
"verification_failed": "Kuruluş kimlik doğrulaması doğrulanamıyor."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Kuruluşunuz kimlik doğrulaması gerektiriyor."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Özel Modu Sil",
|
||||
|
|
|
|||
15
src/i18n/locales/vi/common.json
generated
15
src/i18n/locales/vi/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "Đã xóa chế độ thành công, nhưng không thể xóa thư mục quy tắc tại {{rulesFolderPath}}. Bạn có thể cần xóa thủ công."
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "Tổ chức của bạn yêu cầu xác thực Roo Code Cloud. Vui lòng đăng nhập để tiếp tục.",
|
||||
"organization_mismatch": "Bạn phải được xác thực bằng tài khoản Roo Code Cloud của tổ chức.",
|
||||
"manual_url_empty": "Vui lòng nhập URL callback hợp lệ",
|
||||
"manual_url_no_query": "URL callback không hợp lệ: thiếu tham số truy vấn",
|
||||
"manual_url_missing_params": "URL callback không hợp lệ: thiếu tham số bắt buộc (code và state)",
|
||||
"manual_url_auth_failed": "Xác thực URL thủ công thất bại",
|
||||
"manual_url_auth_error": "Xác thực thất bại",
|
||||
"verification_failed": "Không thể xác minh xác thực tổ chức."
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "Tổ chức của bạn yêu cầu xác thực."
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "Xóa chế độ tùy chỉnh",
|
||||
|
|
|
|||
15
src/i18n/locales/zh-CN/common.json
generated
15
src/i18n/locales/zh-CN/common.json
generated
|
|
@ -225,21 +225,6 @@
|
|||
"rulesCleanupFailed": "模式已成功移除,但无法删除位于 {{rulesFolderPath}} 的规则文件夹。您可能需要手动删除。"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "您的组织需要 Roo Code Cloud 身份验证。请登录以继续。",
|
||||
"organization_mismatch": "您必须使用组织的 Roo Code Cloud 账户进行身份验证。",
|
||||
"manual_url_empty": "请输入有效的回调 URL",
|
||||
"manual_url_no_query": "无效的回调 URL:缺少查询参数",
|
||||
"manual_url_missing_params": "无效的回调 URL:缺少必需参数(code 和 state)",
|
||||
"manual_url_auth_failed": "手动 URL 身份验证失败",
|
||||
"manual_url_auth_error": "身份验证失败",
|
||||
"verification_failed": "无法验证组织身份验证。"
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "您的组织需要身份验证。"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "删除自定义模式",
|
||||
|
|
|
|||
15
src/i18n/locales/zh-TW/common.json
generated
15
src/i18n/locales/zh-TW/common.json
generated
|
|
@ -220,21 +220,6 @@
|
|||
"rulesCleanupFailed": "模式已成功移除,但無法刪除位於 {{rulesFolderPath}} 的規則資料夾。您可能需要手動刪除。"
|
||||
}
|
||||
},
|
||||
"mdm": {
|
||||
"errors": {
|
||||
"cloud_auth_required": "您的組織需要 Roo Code Cloud 身份驗證。請登入以繼續。",
|
||||
"organization_mismatch": "您必須使用組織的 Roo Code Cloud 帳戶進行身份驗證。",
|
||||
"manual_url_empty": "請輸入有效的回呼 URL",
|
||||
"manual_url_no_query": "無效的回呼 URL:缺少查詢參數",
|
||||
"manual_url_missing_params": "無效的回呼 URL:缺少必要參數(code 和 state)",
|
||||
"manual_url_auth_failed": "手動 URL 身份驗證失敗",
|
||||
"manual_url_auth_error": "身份驗證失敗",
|
||||
"verification_failed": "無法驗證組織身份驗證。"
|
||||
},
|
||||
"info": {
|
||||
"organization_requires_auth": "您的組織需要身份驗證。"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"deleteMode": {
|
||||
"title": "刪除自訂模式",
|
||||
|
|
|
|||
|
|
@ -1,202 +0,0 @@
|
|||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
import { z } from "zod"
|
||||
|
||||
import { CloudService, getClerkBaseUrl, PRODUCTION_CLERK_BASE_URL } from "@roo-code/cloud"
|
||||
|
||||
import { t } from "../../i18n"
|
||||
|
||||
// MDM Configuration Schema
|
||||
const mdmConfigSchema = z.object({
|
||||
requireCloudAuth: z.boolean(),
|
||||
organizationId: z.string().optional(),
|
||||
})
|
||||
|
||||
export type MdmConfig = z.infer<typeof mdmConfigSchema>
|
||||
|
||||
export type ComplianceResult = { compliant: true } | { compliant: false; reason: string }
|
||||
|
||||
export class MdmService {
|
||||
private static _instance: MdmService | null = null
|
||||
private mdmConfig: MdmConfig | null = null
|
||||
private log: (...args: unknown[]) => void
|
||||
|
||||
private constructor(log?: (...args: unknown[]) => void) {
|
||||
this.log = log || console.log
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the MDM service by loading configuration
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
try {
|
||||
this.mdmConfig = await this.loadMdmConfig()
|
||||
|
||||
if (this.mdmConfig) {
|
||||
this.log(`[MDM] Loaded MDM configuration: ${JSON.stringify(this.mdmConfig)}`)
|
||||
}
|
||||
} catch (error) {
|
||||
this.log(`[MDM] Error loading MDM configuration: ${error instanceof Error ? error.message : String(error)}`)
|
||||
// Don't throw - extension should work without MDM config.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cloud authentication is required by MDM policy
|
||||
*/
|
||||
public requiresCloudAuth(): boolean {
|
||||
return this.mdmConfig?.requireCloudAuth ?? false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required organization ID from MDM policy
|
||||
*/
|
||||
public getRequiredOrganizationId(): string | undefined {
|
||||
return this.mdmConfig?.organizationId
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current state is compliant with MDM policy
|
||||
*/
|
||||
public isCompliant(): ComplianceResult {
|
||||
// If no MDM policy, always compliant
|
||||
if (!this.requiresCloudAuth()) {
|
||||
return { compliant: true }
|
||||
}
|
||||
|
||||
// Check if cloud service is available and has active or attempting session
|
||||
if (!CloudService.hasInstance() || !CloudService.instance.hasOrIsAcquiringActiveSession()) {
|
||||
return {
|
||||
compliant: false,
|
||||
reason: t("mdm.errors.cloud_auth_required"),
|
||||
}
|
||||
}
|
||||
|
||||
// Check organization match if specified
|
||||
const requiredOrgId = this.getRequiredOrganizationId()
|
||||
if (requiredOrgId) {
|
||||
try {
|
||||
// First try to get from active session
|
||||
let currentOrgId = CloudService.instance.getOrganizationId()
|
||||
|
||||
// If no active session, check stored credentials
|
||||
if (!currentOrgId) {
|
||||
const storedOrgId = CloudService.instance.getStoredOrganizationId()
|
||||
|
||||
// null means personal account, which is not compliant for org requirements
|
||||
if (storedOrgId === null || storedOrgId !== requiredOrgId) {
|
||||
return {
|
||||
compliant: false,
|
||||
reason: t("mdm.errors.organization_mismatch"),
|
||||
}
|
||||
}
|
||||
|
||||
currentOrgId = storedOrgId
|
||||
}
|
||||
|
||||
if (currentOrgId !== requiredOrgId) {
|
||||
return {
|
||||
compliant: false,
|
||||
reason: t("mdm.errors.organization_mismatch"),
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.log("[MDM] Error checking organization ID:", error)
|
||||
return {
|
||||
compliant: false,
|
||||
reason: t("mdm.errors.verification_failed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { compliant: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Load MDM configuration from system location
|
||||
*/
|
||||
private async loadMdmConfig(): Promise<MdmConfig | null> {
|
||||
const configPath = this.getMdmConfigPath()
|
||||
|
||||
try {
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Read and parse the configuration file
|
||||
const configContent = fs.readFileSync(configPath, "utf-8")
|
||||
const parsedConfig = JSON.parse(configContent)
|
||||
|
||||
// Validate against schema
|
||||
return mdmConfigSchema.parse(parsedConfig)
|
||||
} catch (error) {
|
||||
this.log(`[MDM] Error reading MDM config from ${configPath}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platform-specific MDM configuration file path
|
||||
*/
|
||||
private getMdmConfigPath(): string {
|
||||
const platform = os.platform()
|
||||
const isProduction = getClerkBaseUrl() === PRODUCTION_CLERK_BASE_URL
|
||||
const configFileName = isProduction ? "mdm.json" : "mdm.dev.json"
|
||||
|
||||
switch (platform) {
|
||||
case "win32": {
|
||||
// Windows: %ProgramData%\RooCode\mdm.json or mdm.dev.json
|
||||
const programData = process.env.PROGRAMDATA || "C:\\ProgramData"
|
||||
return path.join(programData, "RooCode", configFileName)
|
||||
}
|
||||
|
||||
case "darwin":
|
||||
// macOS: /Library/Application Support/RooCode/mdm.json or mdm.dev.json
|
||||
return `/Library/Application Support/RooCode/${configFileName}`
|
||||
|
||||
case "linux":
|
||||
default:
|
||||
// Linux: /etc/roo-code/mdm.json or mdm.dev.json
|
||||
return `/etc/roo-code/${configFileName}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance
|
||||
*/
|
||||
public static getInstance(): MdmService {
|
||||
if (!this._instance) {
|
||||
throw new Error("MdmService not initialized. Call createInstance() first.")
|
||||
}
|
||||
return this._instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and initialize the singleton instance
|
||||
*/
|
||||
public static async createInstance(log?: (...args: unknown[]) => void): Promise<MdmService> {
|
||||
if (this._instance) {
|
||||
throw new Error("MdmService instance already exists")
|
||||
}
|
||||
|
||||
this._instance = new MdmService(log)
|
||||
await this._instance.initialize()
|
||||
return this._instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if instance exists
|
||||
*/
|
||||
public static hasInstance(): boolean {
|
||||
return this._instance !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the instance (for testing)
|
||||
*/
|
||||
public static resetInstance(): void {
|
||||
this._instance = null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,364 +0,0 @@
|
|||
import * as path from "path"
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("fs", () => ({
|
||||
existsSync: vi.fn(),
|
||||
readFileSync: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("os", () => ({
|
||||
platform: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("@roo-code/cloud", () => ({
|
||||
CloudService: {
|
||||
hasInstance: vi.fn(),
|
||||
instance: {
|
||||
hasActiveSession: vi.fn(),
|
||||
hasOrIsAcquiringActiveSession: vi.fn(),
|
||||
getOrganizationId: vi.fn(),
|
||||
},
|
||||
},
|
||||
getClerkBaseUrl: vi.fn(),
|
||||
PRODUCTION_CLERK_BASE_URL: "https://clerk.roocode.com",
|
||||
}))
|
||||
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
getConfiguration: vi.fn(),
|
||||
},
|
||||
ConfigurationTarget: {
|
||||
Global: 1,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("../../../shared/package", () => ({
|
||||
Package: {
|
||||
publisher: "roo-code",
|
||||
name: "roo-cline",
|
||||
version: "1.0.0",
|
||||
outputChannel: "Roo-Code",
|
||||
sha: undefined,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("../../../i18n", () => ({
|
||||
t: vi.fn((key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
"mdm.errors.cloud_auth_required":
|
||||
"Your organization requires Roo Code Cloud authentication. Please sign in to continue.",
|
||||
"mdm.errors.organization_mismatch":
|
||||
"You must be authenticated with your organization's Roo Code Cloud account.",
|
||||
"mdm.errors.verification_failed": "Unable to verify organization authentication.",
|
||||
}
|
||||
return translations[key] || key
|
||||
}),
|
||||
}))
|
||||
|
||||
import * as fs from "fs"
|
||||
import * as os from "os"
|
||||
import * as vscode from "vscode"
|
||||
import { MdmService } from "../MdmService"
|
||||
import { CloudService, getClerkBaseUrl, PRODUCTION_CLERK_BASE_URL } from "@roo-code/cloud"
|
||||
|
||||
const mockFs = fs as any
|
||||
const mockOs = os as any
|
||||
const mockCloudService = CloudService as any
|
||||
const mockVscode = vscode as any
|
||||
const mockGetClerkBaseUrl = getClerkBaseUrl as any
|
||||
|
||||
describe("MdmService", () => {
|
||||
let originalPlatform: string
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset singleton
|
||||
MdmService.resetInstance()
|
||||
|
||||
// Store original platform
|
||||
originalPlatform = process.platform
|
||||
|
||||
// Set default platform for tests
|
||||
mockOs.platform.mockReturnValue("darwin")
|
||||
|
||||
// Setup default mock for getClerkBaseUrl to return development URL
|
||||
mockGetClerkBaseUrl.mockReturnValue("https://dev.clerk.roocode.com")
|
||||
|
||||
// Setup VSCode mocks
|
||||
const mockConfig = {
|
||||
get: vi.fn().mockReturnValue(false),
|
||||
update: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
mockVscode.workspace.getConfiguration.mockReturnValue(mockConfig)
|
||||
|
||||
// Reset mocks
|
||||
vi.clearAllMocks()
|
||||
// Re-setup the default after clearing
|
||||
mockGetClerkBaseUrl.mockReturnValue("https://dev.clerk.roocode.com")
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original platform
|
||||
Object.defineProperty(process, "platform", {
|
||||
value: originalPlatform,
|
||||
})
|
||||
})
|
||||
|
||||
describe("initialization", () => {
|
||||
it("should create instance successfully", async () => {
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
expect(service).toBeInstanceOf(MdmService)
|
||||
})
|
||||
|
||||
it("should load MDM config if file exists", async () => {
|
||||
const mockConfig = {
|
||||
requireCloudAuth: true,
|
||||
organizationId: "test-org-123",
|
||||
}
|
||||
|
||||
mockFs.existsSync.mockReturnValue(true)
|
||||
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
|
||||
expect(service.requiresCloudAuth()).toBe(true)
|
||||
expect(service.getRequiredOrganizationId()).toBe("test-org-123")
|
||||
})
|
||||
|
||||
it("should handle missing MDM config file gracefully", async () => {
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
|
||||
expect(service.requiresCloudAuth()).toBe(false)
|
||||
expect(service.getRequiredOrganizationId()).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should handle invalid JSON gracefully", async () => {
|
||||
mockFs.existsSync.mockReturnValue(true)
|
||||
mockFs.readFileSync.mockReturnValue("invalid json")
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
|
||||
expect(service.requiresCloudAuth()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("platform-specific config paths", () => {
|
||||
let originalNodeEnv: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
originalNodeEnv = process.env.NODE_ENV
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalNodeEnv !== undefined) {
|
||||
process.env.NODE_ENV = originalNodeEnv
|
||||
} else {
|
||||
delete process.env.NODE_ENV
|
||||
}
|
||||
})
|
||||
|
||||
it("should use correct path for Windows in production", async () => {
|
||||
mockOs.platform.mockReturnValue("win32")
|
||||
process.env.PROGRAMDATA = "C:\\ProgramData"
|
||||
mockGetClerkBaseUrl.mockReturnValue(PRODUCTION_CLERK_BASE_URL)
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
await MdmService.createInstance()
|
||||
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith(path.join("C:\\ProgramData", "RooCode", "mdm.json"))
|
||||
})
|
||||
|
||||
it("should use correct path for Windows in development", async () => {
|
||||
mockOs.platform.mockReturnValue("win32")
|
||||
process.env.PROGRAMDATA = "C:\\ProgramData"
|
||||
mockGetClerkBaseUrl.mockReturnValue("https://dev.clerk.roocode.com")
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
await MdmService.createInstance()
|
||||
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith(path.join("C:\\ProgramData", "RooCode", "mdm.dev.json"))
|
||||
})
|
||||
|
||||
it("should use correct path for macOS in production", async () => {
|
||||
mockOs.platform.mockReturnValue("darwin")
|
||||
mockGetClerkBaseUrl.mockReturnValue(PRODUCTION_CLERK_BASE_URL)
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
await MdmService.createInstance()
|
||||
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith("/Library/Application Support/RooCode/mdm.json")
|
||||
})
|
||||
|
||||
it("should use correct path for macOS in development", async () => {
|
||||
mockOs.platform.mockReturnValue("darwin")
|
||||
mockGetClerkBaseUrl.mockReturnValue("https://dev.clerk.roocode.com")
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
await MdmService.createInstance()
|
||||
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith("/Library/Application Support/RooCode/mdm.dev.json")
|
||||
})
|
||||
|
||||
it("should use correct path for Linux in production", async () => {
|
||||
mockOs.platform.mockReturnValue("linux")
|
||||
mockGetClerkBaseUrl.mockReturnValue(PRODUCTION_CLERK_BASE_URL)
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
await MdmService.createInstance()
|
||||
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith("/etc/roo-code/mdm.json")
|
||||
})
|
||||
|
||||
it("should use correct path for Linux in development", async () => {
|
||||
mockOs.platform.mockReturnValue("linux")
|
||||
mockGetClerkBaseUrl.mockReturnValue("https://dev.clerk.roocode.com")
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
await MdmService.createInstance()
|
||||
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith("/etc/roo-code/mdm.dev.json")
|
||||
})
|
||||
|
||||
it("should default to dev config when NODE_ENV is not set", async () => {
|
||||
mockOs.platform.mockReturnValue("darwin")
|
||||
mockGetClerkBaseUrl.mockReturnValue("https://dev.clerk.roocode.com")
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
await MdmService.createInstance()
|
||||
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith("/Library/Application Support/RooCode/mdm.dev.json")
|
||||
})
|
||||
})
|
||||
|
||||
describe("compliance checking", () => {
|
||||
it("should be compliant when no MDM policy exists", async () => {
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
const compliance = service.isCompliant()
|
||||
|
||||
expect(compliance.compliant).toBe(true)
|
||||
})
|
||||
|
||||
it("should be compliant when authenticated and no org requirement", async () => {
|
||||
const mockConfig = { requireCloudAuth: true }
|
||||
mockFs.existsSync.mockReturnValue(true)
|
||||
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
|
||||
|
||||
mockCloudService.hasInstance.mockReturnValue(true)
|
||||
mockCloudService.instance.hasOrIsAcquiringActiveSession.mockReturnValue(true)
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
const compliance = service.isCompliant()
|
||||
|
||||
expect(compliance.compliant).toBe(true)
|
||||
})
|
||||
|
||||
it("should be non-compliant when not authenticated", async () => {
|
||||
const mockConfig = { requireCloudAuth: true }
|
||||
mockFs.existsSync.mockReturnValue(true)
|
||||
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
|
||||
|
||||
// Mock CloudService to indicate no instance or no active session
|
||||
mockCloudService.hasInstance.mockReturnValue(false)
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
const compliance = service.isCompliant()
|
||||
|
||||
expect(compliance.compliant).toBe(false)
|
||||
if (!compliance.compliant) {
|
||||
expect(compliance.reason).toContain("Your organization requires Roo Code Cloud authentication")
|
||||
}
|
||||
})
|
||||
|
||||
it("should be non-compliant when wrong organization", async () => {
|
||||
const mockConfig = {
|
||||
requireCloudAuth: true,
|
||||
organizationId: "required-org-123",
|
||||
}
|
||||
mockFs.existsSync.mockReturnValue(true)
|
||||
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
|
||||
|
||||
// Mock CloudService to have instance and active session but wrong org
|
||||
mockCloudService.hasInstance.mockReturnValue(true)
|
||||
mockCloudService.instance.hasOrIsAcquiringActiveSession.mockReturnValue(true)
|
||||
mockCloudService.instance.getOrganizationId.mockReturnValue("different-org-456")
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
const compliance = service.isCompliant()
|
||||
|
||||
expect(compliance.compliant).toBe(false)
|
||||
if (!compliance.compliant) {
|
||||
expect(compliance.reason).toContain(
|
||||
"You must be authenticated with your organization's Roo Code Cloud account",
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should be compliant when correct organization", async () => {
|
||||
const mockConfig = {
|
||||
requireCloudAuth: true,
|
||||
organizationId: "correct-org-123",
|
||||
}
|
||||
mockFs.existsSync.mockReturnValue(true)
|
||||
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
|
||||
|
||||
mockCloudService.hasInstance.mockReturnValue(true)
|
||||
mockCloudService.instance.hasOrIsAcquiringActiveSession.mockReturnValue(true)
|
||||
mockCloudService.instance.getOrganizationId.mockReturnValue("correct-org-123")
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
const compliance = service.isCompliant()
|
||||
|
||||
expect(compliance.compliant).toBe(true)
|
||||
})
|
||||
|
||||
it("should be compliant when in attempting-session state", async () => {
|
||||
const mockConfig = { requireCloudAuth: true }
|
||||
mockFs.existsSync.mockReturnValue(true)
|
||||
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig))
|
||||
|
||||
mockCloudService.hasInstance.mockReturnValue(true)
|
||||
// Mock attempting session (not active, but acquiring)
|
||||
mockCloudService.instance.hasOrIsAcquiringActiveSession.mockReturnValue(true)
|
||||
|
||||
const service = await MdmService.createInstance()
|
||||
const compliance = service.isCompliant()
|
||||
|
||||
expect(compliance.compliant).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("singleton pattern", () => {
|
||||
it("should throw error when accessing instance before creation", () => {
|
||||
expect(() => MdmService.getInstance()).toThrow("MdmService not initialized")
|
||||
})
|
||||
|
||||
it("should throw error when creating instance twice", async () => {
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
await MdmService.createInstance()
|
||||
|
||||
await expect(MdmService.createInstance()).rejects.toThrow("instance already exists")
|
||||
})
|
||||
|
||||
it("should return same instance", async () => {
|
||||
mockFs.existsSync.mockReturnValue(false)
|
||||
|
||||
const service1 = await MdmService.createInstance()
|
||||
const service2 = MdmService.getInstance()
|
||||
|
||||
expect(service1).toBe(service2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -65,7 +65,6 @@ const App = () => {
|
|||
cloudApiUrl,
|
||||
cloudOrganizations,
|
||||
renderContext,
|
||||
mdmCompliant,
|
||||
} = useExtensionState()
|
||||
|
||||
// Create a persistent state manager
|
||||
|
|
@ -91,27 +90,16 @@ const App = () => {
|
|||
const settingsRef = useRef<SettingsViewRef>(null)
|
||||
const chatViewRef = useRef<ChatViewRef>(null)
|
||||
|
||||
const switchTab = useCallback(
|
||||
(newTab: Tab) => {
|
||||
// Only check MDM compliance if mdmCompliant is explicitly false (meaning there's an MDM policy and user is non-compliant)
|
||||
// If mdmCompliant is undefined or true, allow tab switching
|
||||
if (mdmCompliant === false && newTab !== "cloud") {
|
||||
// Notify the user that authentication is required by their organization
|
||||
vscode.postMessage({ type: "showMdmAuthRequiredNotification" })
|
||||
return
|
||||
}
|
||||
const switchTab = useCallback((newTab: Tab) => {
|
||||
setCurrentSection(undefined)
|
||||
setCurrentMarketplaceTab(undefined)
|
||||
|
||||
setCurrentSection(undefined)
|
||||
setCurrentMarketplaceTab(undefined)
|
||||
|
||||
if (settingsRef.current?.checkUnsaveChanges) {
|
||||
settingsRef.current.checkUnsaveChanges(() => setTab(newTab))
|
||||
} else {
|
||||
setTab(newTab)
|
||||
}
|
||||
},
|
||||
[mdmCompliant],
|
||||
)
|
||||
if (settingsRef.current?.checkUnsaveChanges) {
|
||||
settingsRef.current.checkUnsaveChanges(() => setTab(newTab))
|
||||
} else {
|
||||
setTab(newTab)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [currentSection, setCurrentSection] = useState<string | undefined>(undefined)
|
||||
const [currentMarketplaceTab, setCurrentMarketplaceTab] = useState<string | undefined>(undefined)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||
cloudOrganizations?: CloudOrganizationMembership[]
|
||||
sharingEnabled: boolean
|
||||
publicSharingEnabled: boolean
|
||||
mdmCompliant?: boolean
|
||||
hasOpenedModeSelector: boolean // New property to track if user has opened mode selector
|
||||
setHasOpenedModeSelector: (value: boolean) => void // Setter for the new property
|
||||
alwaysAllowFollowupQuestions: boolean // New property for follow-up questions auto-approve
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue