forked from boos/BoosAPI
fix managed downstream deny-all semantics
This commit is contained in:
@@ -114,10 +114,10 @@ function resolveUpstreamPath(apiVersion: string, modelActionPath: string): strin
|
|||||||
return `/${normalizedVersion}/${normalizedAction}`;
|
return `/${normalizedVersion}/${normalizedAction}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasDownstreamModelRestrictions(policy: { supportedModels?: unknown; allowedRouteIds?: unknown }): boolean {
|
function hasDownstreamModelRestrictions(policy: { supportedModels?: unknown; allowedRouteIds?: unknown; denyAllWhenEmpty?: unknown }): boolean {
|
||||||
const supportedModels = Array.isArray(policy.supportedModels) ? policy.supportedModels : [];
|
const supportedModels = Array.isArray(policy.supportedModels) ? policy.supportedModels : [];
|
||||||
const allowedRouteIds = Array.isArray(policy.allowedRouteIds) ? policy.allowedRouteIds : [];
|
const allowedRouteIds = Array.isArray(policy.allowedRouteIds) ? policy.allowedRouteIds : [];
|
||||||
return supportedModels.length > 0 || allowedRouteIds.length > 0;
|
return supportedModels.length > 0 || allowedRouteIds.length > 0 || policy.denyAllWhenEmpty === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractGeminiListedModelName(item: unknown): string {
|
function extractGeminiListedModelName(item: unknown): string {
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { getProxyAuthContext } from '../../middleware/auth.js';
|
import { getProxyAuthContext } from '../../middleware/auth.js';
|
||||||
import { isModelAllowedByPolicyOrAllowedRoutes, recordManagedKeyCostUsage } from '../../services/downstreamApiKeyService.js';
|
import { isModelAllowedByPolicyOrAllowedRoutes, recordManagedKeyCostUsage } from '../../services/downstreamApiKeyService.js';
|
||||||
import {
|
import { EMPTY_DOWNSTREAM_ROUTING_POLICY, type DownstreamRoutingPolicy } from '../../services/downstreamPolicyTypes.js';
|
||||||
EMPTY_DOWNSTREAM_ROUTING_POLICY,
|
|
||||||
GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY,
|
|
||||||
type DownstreamRoutingPolicy,
|
|
||||||
} from '../../services/downstreamPolicyTypes.js';
|
|
||||||
|
|
||||||
export function getDownstreamRoutingPolicy(request: FastifyRequest): DownstreamRoutingPolicy {
|
export function getDownstreamRoutingPolicy(request: FastifyRequest): DownstreamRoutingPolicy {
|
||||||
const authContext = getProxyAuthContext(request);
|
const authContext = getProxyAuthContext(request);
|
||||||
if (!authContext) return EMPTY_DOWNSTREAM_ROUTING_POLICY;
|
if (!authContext) return EMPTY_DOWNSTREAM_ROUTING_POLICY;
|
||||||
if (authContext.keyId === null) return GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY;
|
|
||||||
return authContext.policy;
|
return authContext.policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +16,6 @@ export async function ensureModelAllowedForDownstreamKey(
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const authContext = getProxyAuthContext(request);
|
const authContext = getProxyAuthContext(request);
|
||||||
if (!authContext) return true;
|
if (!authContext) return true;
|
||||||
if (authContext.keyId === null) return true;
|
|
||||||
|
|
||||||
if (await isModelAllowedByPolicyOrAllowedRoutes(requestedModel, authContext.policy)) {
|
if (await isModelAllowedByPolicyOrAllowedRoutes(requestedModel, authContext.policy)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ describe('downstreamApiKeyService', () => {
|
|||||||
supportedModels: [],
|
supportedModels: [],
|
||||||
allowedRouteIds: [],
|
allowedRouteIds: [],
|
||||||
siteWeightMultipliers: {},
|
siteWeightMultipliers: {},
|
||||||
|
denyAllWhenEmpty: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(await service.isModelAllowedByPolicyOrAllowedRoutes('gpt-4o-mini', policy)).toBe(false);
|
expect(await service.isModelAllowedByPolicyOrAllowedRoutes('gpt-4o-mini', policy)).toBe(false);
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ export async function isModelAllowedByPolicyOrAllowedRoutes(model: string, polic
|
|||||||
const hasPatternRules = patterns.length > 0;
|
const hasPatternRules = patterns.length > 0;
|
||||||
const hasRouteRules = allowedRouteIds.length > 0;
|
const hasRouteRules = allowedRouteIds.length > 0;
|
||||||
|
|
||||||
if (!hasPatternRules && !hasRouteRules) return false;
|
if (!hasPatternRules && !hasRouteRules) return policy.denyAllWhenEmpty === true ? false : true;
|
||||||
|
|
||||||
if (hasPatternRules && patterns.some((pattern) => matchesDownstreamModelPattern(model, pattern))) {
|
if (hasPatternRules && patterns.some((pattern) => matchesDownstreamModelPattern(model, pattern))) {
|
||||||
return true;
|
return true;
|
||||||
@@ -278,6 +278,7 @@ export function toPolicyFromView(view: Pick<DownstreamApiKeyPolicyView, 'support
|
|||||||
supportedModels: normalizeSupportedModelsInput(view.supportedModels),
|
supportedModels: normalizeSupportedModelsInput(view.supportedModels),
|
||||||
allowedRouteIds: normalizeAllowedRouteIdsInput(view.allowedRouteIds),
|
allowedRouteIds: normalizeAllowedRouteIdsInput(view.allowedRouteIds),
|
||||||
siteWeightMultipliers: normalizeSiteWeightMultipliersInput(view.siteWeightMultipliers),
|
siteWeightMultipliers: normalizeSiteWeightMultipliersInput(view.siteWeightMultipliers),
|
||||||
|
denyAllWhenEmpty: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export interface DownstreamRoutingPolicy {
|
|||||||
supportedModels: string[];
|
supportedModels: string[];
|
||||||
allowedRouteIds: number[];
|
allowedRouteIds: number[];
|
||||||
siteWeightMultipliers: Record<number, number>;
|
siteWeightMultipliers: Record<number, number>;
|
||||||
|
denyAllWhenEmpty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EMPTY_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = {
|
export const EMPTY_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = {
|
||||||
@@ -9,9 +10,3 @@ export const EMPTY_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = {
|
|||||||
allowedRouteIds: [],
|
allowedRouteIds: [],
|
||||||
siteWeightMultipliers: {},
|
siteWeightMultipliers: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = {
|
|
||||||
supportedModels: ['*'],
|
|
||||||
allowedRouteIds: [],
|
|
||||||
siteWeightMultipliers: {},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ describe('TokenRouter downstream policy', () => {
|
|||||||
allowedRouteIds: [],
|
allowedRouteIds: [],
|
||||||
supportedModels: [],
|
supportedModels: [],
|
||||||
siteWeightMultipliers: {},
|
siteWeightMultipliers: {},
|
||||||
|
denyAllWhenEmpty: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(deniedPick).toBeNull();
|
expect(deniedPick).toBeNull();
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ function isModelAllowedByDownstreamPolicy(requestedModel: string, policy: Downst
|
|||||||
: [];
|
: [];
|
||||||
const hasSupportedPatterns = supportedPatterns.length > 0;
|
const hasSupportedPatterns = supportedPatterns.length > 0;
|
||||||
const hasAllowedRoutes = policy.allowedRouteIds.length > 0;
|
const hasAllowedRoutes = policy.allowedRouteIds.length > 0;
|
||||||
if (!hasSupportedPatterns && !hasAllowedRoutes) return false;
|
if (!hasSupportedPatterns && !hasAllowedRoutes) return policy.denyAllWhenEmpty === true ? false : true;
|
||||||
const matchedSupportedPattern = supportedPatterns.some((pattern) => matchesModelPattern(requestedModel, pattern));
|
const matchedSupportedPattern = supportedPatterns.some((pattern) => matchesModelPattern(requestedModel, pattern));
|
||||||
if (matchedSupportedPattern) return true;
|
if (matchedSupportedPattern) return true;
|
||||||
if (hasAllowedRoutes) return true;
|
if (hasAllowedRoutes) return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user