Remove MDM and organization membership enforcement (#12323)

This commit is contained in:
Matt Rubens 2026-05-11 15:42:02 -04:00 committed by GitHub
parent ad25634905
commit 3d37e054dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 11 additions and 914 deletions

View file

@ -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"

View file

@ -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),
}))

View file

@ -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

View file

@ -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

View file

@ -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
*/

View file

@ -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()

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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é",

View file

@ -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": "कस्टम मोड हटाएं",

View file

@ -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",

View file

@ -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",

View file

@ -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": "カスタムモードの削除",

View file

@ -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": "사용자 정의 모드 삭제",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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": "Удалить пользовательский режим",

View file

@ -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",

View file

@ -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",

View file

@ -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": "删除自定义模式",

View file

@ -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": "刪除自訂模式",

View file

@ -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
}
}

View file

@ -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)
})
})
})

View file

@ -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)

View file

@ -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