mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-11 03:46:49 -07:00
fix(acp/auth): prevent conflicting credentials on enterprise gateways and support optional API keys natively (#27021)
This commit is contained in:
@@ -69,7 +69,10 @@ export class AcpSessionManager {
|
||||
);
|
||||
|
||||
const authType =
|
||||
loadedSettings.merged.security.auth.selectedType || AuthType.USE_GEMINI;
|
||||
loadedSettings.merged.security.auth.selectedType ||
|
||||
(authDetails.baseUrl || process.env['GOOGLE_GEMINI_BASE_URL']
|
||||
? AuthType.GATEWAY
|
||||
: AuthType.USE_GEMINI);
|
||||
|
||||
let isAuthenticated = false;
|
||||
let authErrorMessage = '';
|
||||
@@ -231,7 +234,12 @@ export class AcpSessionManager {
|
||||
mcpServers: acp.McpServer[],
|
||||
authDetails: AuthDetails,
|
||||
): Promise<Config> {
|
||||
const selectedAuthType = this.settings.merged.security.auth.selectedType;
|
||||
const selectedAuthType =
|
||||
this.settings.merged.security.auth.selectedType ||
|
||||
(authDetails.baseUrl || process.env['GOOGLE_GEMINI_BASE_URL']
|
||||
? AuthType.GATEWAY
|
||||
: undefined);
|
||||
|
||||
if (!selectedAuthType) {
|
||||
throw acp.RequestError.authRequired();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
createContentGenerator,
|
||||
AuthType,
|
||||
createContentGeneratorConfig,
|
||||
getAuthTypeFromEnv,
|
||||
type ContentGenerator,
|
||||
} from './contentGenerator.js';
|
||||
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
|
||||
@@ -35,6 +36,45 @@ const mockConfig = {
|
||||
getClientName: vi.fn().mockReturnValue(undefined),
|
||||
} as unknown as Config;
|
||||
|
||||
describe('getAuthTypeFromEnv', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('GEMINI_API_KEY', '');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('should detect LOGIN_WITH_GOOGLE when GOOGLE_GENAI_USE_GCA is true', () => {
|
||||
vi.stubEnv('GOOGLE_GENAI_USE_GCA', 'true');
|
||||
expect(getAuthTypeFromEnv()).toBe(AuthType.LOGIN_WITH_GOOGLE);
|
||||
});
|
||||
|
||||
it('should detect USE_VERTEX_AI when GOOGLE_GENAI_USE_VERTEXAI is true', () => {
|
||||
vi.stubEnv('GOOGLE_GENAI_USE_VERTEXAI', 'true');
|
||||
expect(getAuthTypeFromEnv()).toBe(AuthType.USE_VERTEX_AI);
|
||||
});
|
||||
|
||||
it('should detect GATEWAY when GOOGLE_GEMINI_BASE_URL is present', () => {
|
||||
vi.stubEnv('GOOGLE_GEMINI_BASE_URL', 'https://gateway.example.com');
|
||||
expect(getAuthTypeFromEnv()).toBe(AuthType.GATEWAY);
|
||||
});
|
||||
|
||||
it('should detect USE_GEMINI when GEMINI_API_KEY is present', () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', 'fake-key');
|
||||
expect(getAuthTypeFromEnv()).toBe(AuthType.USE_GEMINI);
|
||||
});
|
||||
|
||||
it('should detect COMPUTE_ADC when CLOUD_SHELL is true', () => {
|
||||
vi.stubEnv('CLOUD_SHELL', 'true');
|
||||
expect(getAuthTypeFromEnv()).toBe(AuthType.COMPUTE_ADC);
|
||||
});
|
||||
|
||||
it('should return undefined when no matching env variables are set', () => {
|
||||
expect(getAuthTypeFromEnv()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createContentGenerator', () => {
|
||||
beforeEach(() => {
|
||||
resetVersionCache();
|
||||
@@ -851,6 +891,40 @@ describe('createContentGenerator', () => {
|
||||
),
|
||||
).rejects.toThrow('Invalid custom base URL: not-a-url');
|
||||
});
|
||||
|
||||
it('should set empty x-goog-api-key header for GATEWAY auth when apiKey is empty string', async () => {
|
||||
const mockConfig = {
|
||||
getModel: vi.fn().mockReturnValue('gemini-pro'),
|
||||
getProxy: vi.fn().mockReturnValue(undefined),
|
||||
getUsageStatisticsEnabled: () => false,
|
||||
getClientName: vi.fn().mockReturnValue(undefined),
|
||||
} as unknown as Config;
|
||||
|
||||
const mockGenerator = {
|
||||
models: {},
|
||||
} as unknown as GoogleGenAI;
|
||||
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never);
|
||||
|
||||
await createContentGenerator(
|
||||
{
|
||||
apiKey: '',
|
||||
authType: AuthType.GATEWAY,
|
||||
baseUrl: 'https://gateway.test.local',
|
||||
},
|
||||
mockConfig,
|
||||
);
|
||||
|
||||
expect(GoogleGenAI).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
apiKey: '',
|
||||
httpOptions: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'x-goog-api-key': '',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createContentGeneratorConfig', () => {
|
||||
@@ -955,24 +1029,33 @@ describe('createContentGeneratorConfig', () => {
|
||||
expect(config.apiKey).toBeUndefined();
|
||||
expect(config.vertexai).toBeUndefined();
|
||||
});
|
||||
it('should configure for GATEWAY using dummy placeholder if GEMINI_API_KEY is set', async () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', 'env-gemini-key');
|
||||
it('should configure for GATEWAY using provided apiKey if available', async () => {
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.GATEWAY,
|
||||
'custom-gateway-key',
|
||||
);
|
||||
expect(config.apiKey).toBe('gateway-placeholder-key');
|
||||
expect(config.apiKey).toBe('custom-gateway-key');
|
||||
expect(config.vertexai).toBe(false);
|
||||
});
|
||||
|
||||
it('should configure for GATEWAY using dummy placeholder if GEMINI_API_KEY is not set', async () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', '');
|
||||
vi.mocked(loadApiKey).mockResolvedValue(null);
|
||||
it('should configure for GATEWAY using GEMINI_API_KEY from environment if set', async () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', 'env-gateway-key');
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.GATEWAY,
|
||||
);
|
||||
expect(config.apiKey).toBe('gateway-placeholder-key');
|
||||
expect(config.apiKey).toBe('env-gateway-key');
|
||||
expect(config.vertexai).toBe(false);
|
||||
});
|
||||
|
||||
it('should configure for GATEWAY using empty string if no apiKey is provided', async () => {
|
||||
vi.stubEnv('GEMINI_API_KEY', '');
|
||||
const config = await createContentGeneratorConfig(
|
||||
mockConfig,
|
||||
AuthType.GATEWAY,
|
||||
);
|
||||
expect(config.apiKey).toBe('');
|
||||
expect(config.vertexai).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,6 +80,9 @@ export function getAuthTypeFromEnv(): AuthType | undefined {
|
||||
if (process.env['GOOGLE_GENAI_USE_VERTEXAI'] === 'true') {
|
||||
return AuthType.USE_VERTEX_AI;
|
||||
}
|
||||
if (process.env['GOOGLE_GEMINI_BASE_URL']) {
|
||||
return AuthType.GATEWAY;
|
||||
}
|
||||
if (process.env['GEMINI_API_KEY']) {
|
||||
return AuthType.USE_GEMINI;
|
||||
}
|
||||
@@ -178,7 +181,8 @@ export async function createContentGeneratorConfig(
|
||||
}
|
||||
|
||||
if (authType === AuthType.GATEWAY) {
|
||||
contentGeneratorConfig.apiKey = apiKey || 'gateway-placeholder-key';
|
||||
contentGeneratorConfig.apiKey =
|
||||
apiKey || process.env['GEMINI_API_KEY'] || '';
|
||||
contentGeneratorConfig.vertexai = false;
|
||||
|
||||
return contentGeneratorConfig;
|
||||
@@ -313,6 +317,9 @@ export async function createContentGenerator(
|
||||
'x-gemini-api-privileged-user-id': `${installationId}`,
|
||||
};
|
||||
}
|
||||
if (config.authType === AuthType.GATEWAY && config.apiKey === '') {
|
||||
headers['x-goog-api-key'] = '';
|
||||
}
|
||||
let baseUrl = config.baseUrl;
|
||||
if (!baseUrl) {
|
||||
const envBaseUrl =
|
||||
@@ -337,7 +344,12 @@ export async function createContentGenerator(
|
||||
}
|
||||
|
||||
const googleGenAI = new GoogleGenAI({
|
||||
apiKey: config.apiKey === '' ? undefined : config.apiKey,
|
||||
apiKey:
|
||||
config.authType === AuthType.GATEWAY
|
||||
? config.apiKey
|
||||
: config.apiKey === ''
|
||||
? undefined
|
||||
: config.apiKey,
|
||||
vertexai: config.vertexai ?? config.authType === AuthType.USE_VERTEX_AI,
|
||||
httpOptions,
|
||||
...(apiVersionEnv && { apiVersion: apiVersionEnv }),
|
||||
|
||||
Reference in New Issue
Block a user