mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
- Fix ObjectId comparison in getListAgentsHandler using .equals() method instead of strict equality
- Add findPubliclyAccessibleResources function to PermissionService for bulk public resource queries - Add hasPublicPermission function to PermissionService for individual resource public permission checks - Update getAgentHandler to use hasPublicPermission for accurate individual agent public status - Replace instanceProjectId-based global checks with isPublic property from backend in client code - Add isPublic property to Agent type definition - Add NODE_TLS_REJECT_UNAUTHORIZED debug setting to VS Code launch config
This commit is contained in:
parent
5979efd607
commit
c9aa10d3d5
7 changed files with 102 additions and 12 deletions
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
|
@ -8,7 +8,8 @@
|
||||||
"skipFiles": ["<node_internals>/**"],
|
"skipFiles": ["<node_internals>/**"],
|
||||||
"program": "${workspaceFolder}/api/server/index.js",
|
"program": "${workspaceFolder}/api/server/index.js",
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_ENV": "production"
|
"NODE_ENV": "production",
|
||||||
|
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||||
},
|
},
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"envFile": "${workspaceFolder}/.env"
|
"envFile": "${workspaceFolder}/.env"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,12 @@ const {
|
||||||
deleteAgent,
|
deleteAgent,
|
||||||
getListAgentsByAccess,
|
getListAgentsByAccess,
|
||||||
} = require('~/models/Agent');
|
} = require('~/models/Agent');
|
||||||
const { grantPermission, findAccessibleResources } = require('~/server/services/PermissionService');
|
const {
|
||||||
|
grantPermission,
|
||||||
|
findAccessibleResources,
|
||||||
|
findPubliclyAccessibleResources,
|
||||||
|
hasPublicPermission,
|
||||||
|
} = require('~/server/services/PermissionService');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { updateAgentProjects, revertAgentVersion } = require('~/models/Agent');
|
const { updateAgentProjects, revertAgentVersion } = require('~/models/Agent');
|
||||||
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
||||||
|
|
@ -134,6 +139,14 @@ const getAgentHandler = async (req, res, expandProperties = false) => {
|
||||||
// @deprecated - isCollaborative replaced by ACL permissions
|
// @deprecated - isCollaborative replaced by ACL permissions
|
||||||
agent.isCollaborative = !!agent.isCollaborative;
|
agent.isCollaborative = !!agent.isCollaborative;
|
||||||
|
|
||||||
|
// Check if agent is public
|
||||||
|
const isPublic = await hasPublicPermission({
|
||||||
|
resourceType: 'agent',
|
||||||
|
resourceId: agent._id,
|
||||||
|
requiredPermissions: PermissionBits.VIEW,
|
||||||
|
});
|
||||||
|
agent.isPublic = isPublic;
|
||||||
|
|
||||||
if (agent.author !== author) {
|
if (agent.author !== author) {
|
||||||
delete agent.author;
|
delete agent.author;
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +165,7 @@ const getAgentHandler = async (req, res, expandProperties = false) => {
|
||||||
projectIds: agent.projectIds,
|
projectIds: agent.projectIds,
|
||||||
// @deprecated - isCollaborative replaced by ACL permissions
|
// @deprecated - isCollaborative replaced by ACL permissions
|
||||||
isCollaborative: agent.isCollaborative,
|
isCollaborative: agent.isCollaborative,
|
||||||
|
isPublic: agent.isPublic,
|
||||||
version: agent.version,
|
version: agent.version,
|
||||||
// Safe metadata
|
// Safe metadata
|
||||||
createdAt: agent.createdAt,
|
createdAt: agent.createdAt,
|
||||||
|
|
@ -392,14 +406,23 @@ const getListAgentsHandler = async (req, res) => {
|
||||||
resourceType: 'agent',
|
resourceType: 'agent',
|
||||||
requiredPermissions: PermissionBits.VIEW,
|
requiredPermissions: PermissionBits.VIEW,
|
||||||
});
|
});
|
||||||
|
const publiclyAccessibleIds = await findPubliclyAccessibleResources({
|
||||||
|
resourceType: 'agent',
|
||||||
|
requiredPermissions: PermissionBits.VIEW,
|
||||||
|
});
|
||||||
// Use the new ACL-aware function
|
// Use the new ACL-aware function
|
||||||
const data = await getListAgentsByAccess({
|
const data = await getListAgentsByAccess({
|
||||||
accessibleIds,
|
accessibleIds,
|
||||||
otherParams: {}, // Can add query params here if needed
|
otherParams: {}, // Can add query params here if needed
|
||||||
|
|
||||||
});
|
});
|
||||||
|
if (data?.data?.length) {
|
||||||
|
data.data = data.data.map((agent) => {
|
||||||
|
if (publiclyAccessibleIds.some(id => id.equals(agent._id))) {
|
||||||
|
agent.isPublic = true;
|
||||||
|
}
|
||||||
|
return agent;
|
||||||
|
});
|
||||||
|
}
|
||||||
return res.json(data);
|
return res.json(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[/Agents] Error listing Agents', error);
|
logger.error('[/Agents] Error listing Agents', error);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ const {
|
||||||
findAccessibleResources: findAccessibleResourcesACL,
|
findAccessibleResources: findAccessibleResourcesACL,
|
||||||
hasPermission,
|
hasPermission,
|
||||||
getEffectivePermissions: getEffectivePermissionsACL,
|
getEffectivePermissions: getEffectivePermissionsACL,
|
||||||
|
findEntriesByPrincipalsAndResource,
|
||||||
} = require('~/models');
|
} = require('~/models');
|
||||||
const { AclEntry, AccessRole, Group } = require('~/db/models');
|
const { AclEntry, AccessRole, Group } = require('~/db/models');
|
||||||
|
|
||||||
|
|
@ -178,6 +179,37 @@ const findAccessibleResources = async ({ userId, resourceType, requiredPermissio
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all publicly accessible resources of a specific type
|
||||||
|
* @param {Object} params - Parameters for finding publicly accessible resources
|
||||||
|
* @param {string} params.resourceType - Type of resource (e.g., 'agent')
|
||||||
|
* @param {number} params.requiredPermissions - The minimum permission bits required (e.g., 1 for VIEW, 3 for VIEW+EDIT)
|
||||||
|
* @returns {Promise<Array>} Array of resource IDs
|
||||||
|
*/
|
||||||
|
const findPubliclyAccessibleResources = async ({ resourceType, requiredPermissions }) => {
|
||||||
|
try {
|
||||||
|
if (typeof requiredPermissions !== 'number' || requiredPermissions < 1) {
|
||||||
|
throw new Error('requiredPermissions must be a positive number');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all public ACL entries where the public principal has at least the required permission bits
|
||||||
|
const entries = await AclEntry.find({
|
||||||
|
principalType: 'public',
|
||||||
|
resourceType,
|
||||||
|
permBits: { $bitsAllSet: requiredPermissions },
|
||||||
|
}).distinct('resourceId');
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[PermissionService.findPubliclyAccessibleResources] Error: ${error.message}`);
|
||||||
|
// Re-throw validation errors
|
||||||
|
if (error.message.includes('requiredPermissions must be')) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available roles for a resource type
|
* Get available roles for a resource type
|
||||||
* @param {Object} params - Parameters for getting available roles
|
* @param {Object} params - Parameters for getting available roles
|
||||||
|
|
@ -407,6 +439,41 @@ const syncUserEntraGroupMemberships = async (user, accessToken, session = null)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if public has a specific permission on a resource
|
||||||
|
* @param {Object} params - Parameters for checking public permission
|
||||||
|
* @param {string} params.resourceType - Type of resource (e.g., 'agent')
|
||||||
|
* @param {string|mongoose.Types.ObjectId} params.resourceId - The ID of the resource
|
||||||
|
* @param {number} params.requiredPermissions - The permission bits required (e.g., 1 for VIEW, 3 for VIEW+EDIT)
|
||||||
|
* @returns {Promise<boolean>} Whether public has the required permission bits
|
||||||
|
*/
|
||||||
|
const hasPublicPermission = async ({ resourceType, resourceId, requiredPermissions }) => {
|
||||||
|
try {
|
||||||
|
if (typeof requiredPermissions !== 'number' || requiredPermissions < 1) {
|
||||||
|
throw new Error('requiredPermissions must be a positive number');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use public principal to check permissions
|
||||||
|
const publicPrincipal = [{ principalType: 'public' }];
|
||||||
|
|
||||||
|
const entries = await findEntriesByPrincipalsAndResource(
|
||||||
|
publicPrincipal,
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if any entry has the required permission bits
|
||||||
|
return entries.some(entry => (entry.permBits & requiredPermissions) === requiredPermissions);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[PermissionService.hasPublicPermission] Error: ${error.message}`);
|
||||||
|
// Re-throw validation errors
|
||||||
|
if (error.message.includes('requiredPermissions must be')) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bulk update permissions for a resource (grant, update, revoke)
|
* Bulk update permissions for a resource (grant, update, revoke)
|
||||||
* Efficiently handles multiple permission changes in a single transaction
|
* Efficiently handles multiple permission changes in a single transaction
|
||||||
|
|
@ -615,6 +682,8 @@ module.exports = {
|
||||||
checkPermission,
|
checkPermission,
|
||||||
getEffectivePermissions,
|
getEffectivePermissions,
|
||||||
findAccessibleResources,
|
findAccessibleResources,
|
||||||
|
findPubliclyAccessibleResources,
|
||||||
|
hasPublicPermission,
|
||||||
getAvailableRoles,
|
getAvailableRoles,
|
||||||
bulkUpdateResourcePermissions,
|
bulkUpdateResourcePermissions,
|
||||||
ensurePrincipalExists,
|
ensurePrincipalExists,
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,7 @@ export default function AgentSelect({
|
||||||
|
|
||||||
const resetAgentForm = useCallback(
|
const resetAgentForm = useCallback(
|
||||||
(fullAgent: Agent) => {
|
(fullAgent: Agent) => {
|
||||||
const { instanceProjectId } = startupConfig ?? {};
|
const isGlobal = fullAgent.isPublic ?? false;
|
||||||
const isGlobal =
|
|
||||||
(instanceProjectId != null && fullAgent.projectIds?.includes(instanceProjectId)) ?? false;
|
|
||||||
const update = {
|
const update = {
|
||||||
...fullAgent,
|
...fullAgent,
|
||||||
provider: createProviderOption(fullAgent.provider),
|
provider: createProviderOption(fullAgent.provider),
|
||||||
|
|
|
||||||
|
|
@ -125,8 +125,7 @@ export const useEndpoints = ({
|
||||||
if (ep === EModelEndpoint.agents && agents.length > 0) {
|
if (ep === EModelEndpoint.agents && agents.length > 0) {
|
||||||
result.models = agents.map((agent) => ({
|
result.models = agents.map((agent) => ({
|
||||||
name: agent.id,
|
name: agent.id,
|
||||||
isGlobal:
|
isGlobal: agent.isPublic ?? false,
|
||||||
(instanceProjectId != null && agent.projectIds?.includes(instanceProjectId)) ?? false,
|
|
||||||
}));
|
}));
|
||||||
result.agentNames = agents.reduce((acc, agent) => {
|
result.agentNames = agents.reduce((acc, agent) => {
|
||||||
acc[agent.id] = agent.name || '';
|
acc[agent.id] = agent.name || '';
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,7 @@ export const processAgentOption = ({
|
||||||
fileMap?: Record<string, TFile | undefined>;
|
fileMap?: Record<string, TFile | undefined>;
|
||||||
instanceProjectId?: string;
|
instanceProjectId?: string;
|
||||||
}): TAgentOption => {
|
}): TAgentOption => {
|
||||||
const isGlobal =
|
const isGlobal = _agent?.isPublic ?? false;
|
||||||
(instanceProjectId != null && _agent?.projectIds?.includes(instanceProjectId)) ?? false;
|
|
||||||
const agent: TAgentOption = {
|
const agent: TAgentOption = {
|
||||||
...(_agent ?? ({} as Agent)),
|
...(_agent ?? ({} as Agent)),
|
||||||
label: _agent?.name ?? '',
|
label: _agent?.name ?? '',
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ export type Agent = {
|
||||||
hide_sequential_outputs?: boolean;
|
hide_sequential_outputs?: boolean;
|
||||||
artifacts?: ArtifactModes;
|
artifacts?: ArtifactModes;
|
||||||
recursion_limit?: number;
|
recursion_limit?: number;
|
||||||
|
isPublic?: boolean;
|
||||||
version?: number;
|
version?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue