Add LDAP query builder feature with UI and API endpoints for managing LDAP attributes and filters.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/12f8d069-cc9a-4b87-88eb-6a7e3bcb8c9c.jpg
This commit is contained in:
alphaeusmote
2025-04-08 21:43:51 +00:00
parent a0b0c23f18
commit 98159c0d30
6 changed files with 2177 additions and 2 deletions
+741
View File
@@ -846,6 +846,747 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
/**
* @swagger
* /api/connections/{connectionId}/ldap-attributes:
* get:
* summary: Get LDAP attributes for a specific object class
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* - name: objectClass
* in: query
* required: true
* schema:
* type: string
* enum: [user, group, organizationalUnit, computer, domain]
* responses:
* 200:
* description: List of LDAP attributes
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/LdapAttribute'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.get("/api/connections/:connectionId/ldap-attributes", requireAuth, async (req: Request, res: Response, next: NextFunction) => {
try {
const connectionId = parseInt(req.params.connectionId);
const objectClass = req.query.objectClass as string;
if (!objectClass) {
return res.status(400).json({ message: "objectClass parameter is required" });
}
// Verify the connection exists
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const attributes = await storage.getLdapAttributes(connectionId, objectClass);
res.json(attributes);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ldap-attributes:
* post:
* summary: Create a new LDAP attribute
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - objectClass
* properties:
* name:
* type: string
* displayName:
* type: string
* description:
* type: string
* type:
* type: string
* multiValued:
* type: boolean
* objectClass:
* type: string
* enum: [user, group, organizationalUnit, computer, domain]
* isIndexed:
* type: boolean
* responses:
* 201:
* description: Attribute created successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapAttribute'
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
app.post("/api/connections/:connectionId/ldap-attributes", requireAdmin, async (req, res, next) => {
try {
const connectionId = parseInt(req.params.connectionId);
// Verify the connection exists
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const attribute = await storage.createLdapAttribute({
...req.body,
connectionId
});
res.status(201).json(attribute);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-attributes/{id}:
* put:
* summary: Update an LDAP attribute
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* displayName:
* type: string
* description:
* type: string
* type:
* type: string
* multiValued:
* type: boolean
* isIndexed:
* type: boolean
* responses:
* 200:
* description: Attribute updated successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapAttribute'
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.put("/api/ldap-attributes/:id", requireAdmin, async (req, res, next) => {
try {
const id = parseInt(req.params.id);
const updatedAttribute = await storage.updateLdapAttribute(id, req.body);
if (!updatedAttribute) {
return res.status(404).json({ message: "LDAP attribute not found" });
}
res.json(updatedAttribute);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-attributes/{id}:
* delete:
* summary: Delete an LDAP attribute
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Attribute deleted successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.delete("/api/ldap-attributes/:id", requireAdmin, async (req, res, next) => {
try {
const id = parseInt(req.params.id);
const deleted = await storage.deleteLdapAttribute(id);
if (!deleted) {
return res.status(404).json({ message: "LDAP attribute not found" });
}
res.json({ success: true });
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ldap-filters:
* get:
* summary: List LDAP filters for a connection
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: List of LDAP filters
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/LdapFilter'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
/**
* @swagger
* /api/connections/{connectionId}/test-filter:
* post:
* summary: Test an LDAP filter against a connection
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - ldapFilter
* - objectClass
* properties:
* ldapFilter:
* type: string
* objectClass:
* type: string
* enum: [user, group, organizationalUnit, computer, domain]
* responses:
* 200:
* description: Filter test results
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.post("/api/connections/:connectionId/test-filter", requireAuth, async (req: Request, res: Response, next: NextFunction) => {
try {
const connectionId = parseInt(req.params.connectionId);
const { ldapFilter, objectClass } = req.body;
if (!ldapFilter || !objectClass) {
return res.status(400).json({ message: "ldapFilter and objectClass are required" });
}
// Verify the connection exists
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
// Test the filter
const results = await storage.testLdapFilter(connectionId, ldapFilter, objectClass);
res.json(results);
} catch (error) {
next(error);
}
});
app.get("/api/connections/:connectionId/ldap-filters", requireAuth, async (req, res, next) => {
try {
const connectionId = parseInt(req.params.connectionId);
// Verify the connection exists
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const filters = await storage.listLdapFilters(connectionId);
res.json(filters);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ldap-filters:
* post:
* summary: Create a new LDAP filter
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - objectClass
* - filter
* - ldapFilter
* properties:
* name:
* type: string
* description:
* type: string
* objectClass:
* type: string
* enum: [user, group, organizationalUnit, computer, domain]
* filter:
* type: object
* ldapFilter:
* type: string
* responses:
* 201:
* description: Filter created successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapFilter'
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
app.post("/api/connections/:connectionId/ldap-filters", requireAuth, async (req, res, next) => {
try {
const connectionId = parseInt(req.params.connectionId);
// Verify the connection exists
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const filter = await storage.createLdapFilter({
...req.body,
connectionId,
createdBy: req.user.id,
modifiedBy: req.user.id
});
res.status(201).json(filter);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-filters/{id}:
* get:
* summary: Get a specific LDAP filter
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: LDAP filter details
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapFilter'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.get("/api/ldap-filters/:id", requireAuth, async (req, res, next) => {
try {
const id = parseInt(req.params.id);
const filter = await storage.getLdapFilter(id);
if (!filter) {
return res.status(404).json({ message: "LDAP filter not found" });
}
res.json(filter);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-filters/{id}:
* put:
* summary: Update an LDAP filter
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* description:
* type: string
* filter:
* type: object
* ldapFilter:
* type: string
* isActive:
* type: boolean
* responses:
* 200:
* description: Filter updated successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapFilter'
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.put("/api/ldap-filters/:id", requireAuth, async (req, res, next) => {
try {
const id = parseInt(req.params.id);
// Check if the filter exists
const existingFilter = await storage.getLdapFilter(id);
if (!existingFilter) {
return res.status(404).json({ message: "LDAP filter not found" });
}
// Update with the current user as modifier
const updatedFilter = await storage.updateLdapFilter(id, {
...req.body,
modifiedBy: req.user.id
});
res.json(updatedFilter);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-filters/{id}:
* delete:
* summary: Delete an LDAP filter
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Filter deleted successfully
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.delete("/api/ldap-filters/:id", requireAuth, async (req, res, next) => {
try {
const id = parseInt(req.params.id);
// Check if the filter exists and if the user is allowed to delete it
const filter = await storage.getLdapFilter(id);
if (!filter) {
return res.status(404).json({ message: "LDAP filter not found" });
}
// Only allow the creator or admins to delete
if (filter.createdBy !== req.user.id) {
const userRole = await storage.getRole(req.user.roleId!);
if (userRole?.name !== "admin") {
return res.status(403).json({ message: "You are not authorized to delete this filter" });
}
}
const deleted = await storage.deleteLdapFilter(id);
res.json({ success: deleted });
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-filters/{filterId}/revisions:
* get:
* summary: Get the revision history for an LDAP filter
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: filterId
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: List of filter revisions
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/LdapFilterRevision'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.get("/api/ldap-filters/:filterId/revisions", requireAuth, async (req, res, next) => {
try {
const filterId = parseInt(req.params.filterId);
// Check if the filter exists
const filter = await storage.getLdapFilter(filterId);
if (!filter) {
return res.status(404).json({ message: "LDAP filter not found" });
}
const revisions = await storage.getLdapFilterRevisions(filterId);
res.json(revisions);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/ldap-filters/{filterId}/revert/{revisionId}:
* post:
* summary: Revert a filter to a previous revision
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: filterId
* in: path
* required: true
* schema:
* type: integer
* - name: revisionId
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Filter reverted successfully
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/LdapFilter'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
* 404:
* $ref: '#/components/responses/NotFoundError'
*/
app.post("/api/ldap-filters/:filterId/revert/:revisionId", requireAuth, async (req, res, next) => {
try {
const filterId = parseInt(req.params.filterId);
const revisionId = parseInt(req.params.revisionId);
// Check if the filter exists
const filter = await storage.getLdapFilter(filterId);
if (!filter) {
return res.status(404).json({ message: "LDAP filter not found" });
}
// Check if the user has permission to modify this filter
if (filter.createdBy !== req.user.id) {
const userRole = await storage.getRole(req.user.roleId!);
if (userRole?.name !== "admin") {
return res.status(403).json({ message: "You are not authorized to modify this filter" });
}
}
// First update the modifiedBy to the current user
await storage.updateLdapFilter(filterId, { modifiedBy: req.user.id });
// Then perform the revert
const revertedFilter = await storage.revertLdapFilterToRevision(filterId, revisionId);
if (!revertedFilter) {
return res.status(404).json({ message: "Failed to revert filter or revision not found" });
}
res.json(revertedFilter);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/test-ldap-filter:
* post:
* summary: Test an LDAP filter against a connection
* tags: [LDAP Query Builder]
* security:
* - cookieAuth: []
* parameters:
* - name: connectionId
* in: path
* required: true
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - ldapFilter
* - objectClass
* properties:
* ldapFilter:
* type: string
* objectClass:
* type: string
* enum: [user, group, organizationalUnit, computer, domain]
* responses:
* 200:
* description: Test results
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* 400:
* $ref: '#/components/responses/BadRequestError'
* 401:
* $ref: '#/components/responses/UnauthorizedError'
*/
app.post("/api/connections/:connectionId/test-ldap-filter", requireAuth, async (req, res, next) => {
try {
const connectionId = parseInt(req.params.connectionId);
const { ldapFilter, objectClass } = req.body;
if (!ldapFilter || !objectClass) {
return res.status(400).json({ message: "ldapFilter and objectClass are required" });
}
// Verify the connection exists
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
const results = await storage.testLdapFilter(connectionId, ldapFilter, objectClass);
res.json(results);
} catch (error) {
next(error);
}
});
const httpServer = createServer(app);
return httpServer;
+352 -1
View File
@@ -4,8 +4,10 @@ import {
AdUser, InsertAdUser, AdGroup, InsertAdGroup,
AdOrgUnit, InsertAdOrgUnit, AdComputer, InsertAdComputer,
AdDomain, InsertAdDomain, Role, ApiQuery,
LdapFilter, InsertLdapFilter, LdapFilterRevision, InsertLdapFilterRevision,
LdapAttribute, InsertLdapAttribute,
users, apiTokens, ldapConnections, adUsers, adGroups, adOrgUnits, adComputers, adDomains,
roles
roles, ldapFilters, ldapFilterRevisions, ldapAttributes
} from "@shared/schema";
import session from "express-session";
import createMemoryStore from "memorystore";
@@ -57,6 +59,25 @@ export interface IStorage {
deleteLdapConnection(id: number): Promise<boolean>;
listLdapConnections(): Promise<LdapConnection[]>;
// LDAP Query Builder
getLdapAttributes(connectionId: number, objectClass: string): Promise<LdapAttribute[]>;
createLdapAttribute(attribute: InsertLdapAttribute): Promise<LdapAttribute>;
updateLdapAttribute(id: number, attribute: Partial<LdapAttribute>): Promise<LdapAttribute | undefined>;
deleteLdapAttribute(id: number): Promise<boolean>;
getLdapFilter(id: number): Promise<LdapFilter | undefined>;
createLdapFilter(filter: InsertLdapFilter): Promise<LdapFilter>;
updateLdapFilter(id: number, filter: Partial<LdapFilter>): Promise<LdapFilter | undefined>;
deleteLdapFilter(id: number): Promise<boolean>;
listLdapFilters(connectionId: number): Promise<LdapFilter[]>;
getLdapFilterRevisions(filterId: number): Promise<LdapFilterRevision[]>;
getLdapFilterRevision(id: number): Promise<LdapFilterRevision | undefined>;
createLdapFilterRevision(revision: InsertLdapFilterRevision): Promise<LdapFilterRevision>;
revertLdapFilterToRevision(filterId: number, revisionId: number): Promise<LdapFilter | undefined>;
testLdapFilter(connectionId: number, ldapFilter: string, objectClass: string): Promise<any[]>;
// AD Users
getAdUser(id: number): Promise<AdUser | undefined>;
createAdUser(user: InsertAdUser): Promise<AdUser>;
@@ -197,6 +218,336 @@ export class DatabaseStorage implements IStorage {
async listLdapConnections(): Promise<LdapConnection[]> {
return db.select().from(ldapConnections);
}
// LDAP Query Builder methods
async getLdapAttributes(connectionId: number, objectClass: string): Promise<LdapAttribute[]> {
const cacheKey = `ldapAttributes:${connectionId}:${objectClass}`;
// Try to get from cache first
const cachedData = await getCached<LdapAttribute[]>(cacheKey);
if (cachedData) {
debug(`Cache hit for ${cacheKey}`);
return cachedData;
}
const attributes = await db.select()
.from(ldapAttributes)
.where(
and(
eq(ldapAttributes.connectionId, connectionId),
eq(ldapAttributes.objectClass, objectClass)
)
);
// Cache the result
await setCached(cacheKey, attributes, CACHE_TTL.LONG);
return attributes;
}
async createLdapAttribute(attribute: InsertLdapAttribute): Promise<LdapAttribute> {
debug(`Creating LDAP attribute: ${JSON.stringify(attribute)}`);
const result = await db.insert(ldapAttributes).values(attribute).returning();
// Invalidate cache for the specific connection and object class
await invalidateCache(`ldapAttributes:${attribute.connectionId}:${attribute.objectClass}`);
return result[0];
}
async updateLdapAttribute(id: number, attribute: Partial<LdapAttribute>): Promise<LdapAttribute | undefined> {
debug(`Updating LDAP attribute ${id}: ${JSON.stringify(attribute)}`);
// Get the attribute first to get connectionId and objectClass for cache invalidation
const existingAttribute = await this.getLdapAttribute(id);
if (!existingAttribute) {
return undefined;
}
const result = await db.update(ldapAttributes)
.set({
...attribute,
updatedAt: new Date()
})
.where(eq(ldapAttributes.id, id))
.returning();
// Invalidate cache for the specific connection and object class
await invalidateCache(`ldapAttributes:${existingAttribute.connectionId}:${existingAttribute.objectClass}`);
return result.length > 0 ? result[0] : undefined;
}
async getLdapAttribute(id: number): Promise<LdapAttribute | undefined> {
const result = await db.select().from(ldapAttributes).where(eq(ldapAttributes.id, id));
return result.length > 0 ? result[0] : undefined;
}
async deleteLdapAttribute(id: number): Promise<boolean> {
debug(`Deleting LDAP attribute ${id}`);
// Get the attribute first to get connectionId and objectClass for cache invalidation
const existingAttribute = await this.getLdapAttribute(id);
if (!existingAttribute) {
return false;
}
const result = await db.delete(ldapAttributes)
.where(eq(ldapAttributes.id, id))
.returning({ id: ldapAttributes.id });
// Invalidate cache for the specific connection and object class
if (result.length > 0) {
await invalidateCache(`ldapAttributes:${existingAttribute.connectionId}:${existingAttribute.objectClass}`);
return true;
}
return false;
}
async getLdapFilter(id: number): Promise<LdapFilter | undefined> {
debug(`Getting LDAP filter ${id}`);
const result = await db.select().from(ldapFilters).where(eq(ldapFilters.id, id));
return result.length > 0 ? result[0] : undefined;
}
async createLdapFilter(filter: InsertLdapFilter): Promise<LdapFilter> {
debug(`Creating LDAP filter: ${JSON.stringify(filter)}`);
// Create the filter
const newFilter = await db.insert(ldapFilters).values({
...filter,
currentVersion: 1,
modifiedAt: new Date(),
}).returning();
// Also create the initial revision
await this.createLdapFilterRevision({
filterId: newFilter[0].id,
version: 1,
filter: filter.filter,
ldapFilter: filter.ldapFilter,
createdBy: filter.createdBy,
comment: "Initial version"
});
// Invalidate the list cache
await invalidateCache(`ldapFilters:${filter.connectionId}`);
return newFilter[0];
}
async updateLdapFilter(id: number, filter: Partial<LdapFilter>): Promise<LdapFilter | undefined> {
debug(`Updating LDAP filter ${id}: ${JSON.stringify(filter)}`);
// Get the current filter
const currentFilter = await this.getLdapFilter(id);
if (!currentFilter) {
return undefined;
}
// If the filter structure or LDAP filter is changing, increment the version
const currentVersion = currentFilter.currentVersion || 1;
const newVersion = (filter.filter || filter.ldapFilter)
? currentVersion + 1
: currentVersion;
// Update the filter
const result = await db.update(ldapFilters)
.set({
...filter,
currentVersion: newVersion,
modifiedAt: new Date()
})
.where(eq(ldapFilters.id, id))
.returning();
// If version incremented, create a new revision
if (newVersion > (currentFilter.currentVersion || 0) && filter.filter && filter.ldapFilter) {
await this.createLdapFilterRevision({
filterId: id,
version: newVersion,
filter: filter.filter,
ldapFilter: filter.ldapFilter,
createdBy: filter.modifiedBy,
comment: `Version ${newVersion}`
});
}
// Invalidate the cache
await invalidateCache(`ldapFilters:${currentFilter.connectionId}`);
await invalidateCache(`ldapFilter:${id}`);
return result.length > 0 ? result[0] : undefined;
}
async deleteLdapFilter(id: number): Promise<boolean> {
debug(`Deleting LDAP filter ${id}`);
// Get the filter first for connection ID
const filter = await this.getLdapFilter(id);
if (!filter) {
return false;
}
// Delete the filter (related revisions will be cascade deleted)
const result = await db.delete(ldapFilters)
.where(eq(ldapFilters.id, id))
.returning({ id: ldapFilters.id });
// Invalidate the cache
if (result.length > 0) {
await invalidateCache(`ldapFilters:${filter.connectionId}`);
await invalidateCache(`ldapFilter:${id}`);
await invalidateCache(`ldapFilterRevisions:${id}`);
return true;
}
return false;
}
async listLdapFilters(connectionId: number): Promise<LdapFilter[]> {
const cacheKey = `ldapFilters:${connectionId}`;
// Try to get from cache first
const cachedData = await getCached<LdapFilter[]>(cacheKey);
if (cachedData) {
debug(`Cache hit for ${cacheKey}`);
return cachedData;
}
const filters = await db.select()
.from(ldapFilters)
.where(eq(ldapFilters.connectionId, connectionId));
// Cache the result
await setCached(cacheKey, filters, CACHE_TTL.MEDIUM);
return filters;
}
async getLdapFilterRevisions(filterId: number): Promise<LdapFilterRevision[]> {
const cacheKey = `ldapFilterRevisions:${filterId}`;
// Try to get from cache first
const cachedData = await getCached<LdapFilterRevision[]>(cacheKey);
if (cachedData) {
debug(`Cache hit for ${cacheKey}`);
return cachedData;
}
const revisions = await db.select()
.from(ldapFilterRevisions)
.where(eq(ldapFilterRevisions.filterId, filterId))
.orderBy(ldapFilterRevisions.version);
// Cache the result
await setCached(cacheKey, revisions, CACHE_TTL.MEDIUM);
return revisions;
}
async getLdapFilterRevision(id: number): Promise<LdapFilterRevision | undefined> {
debug(`Getting LDAP filter revision ${id}`);
const result = await db.select().from(ldapFilterRevisions).where(eq(ldapFilterRevisions.id, id));
return result.length > 0 ? result[0] : undefined;
}
async createLdapFilterRevision(revision: InsertLdapFilterRevision): Promise<LdapFilterRevision> {
debug(`Creating LDAP filter revision: ${JSON.stringify(revision)}`);
const result = await db.insert(ldapFilterRevisions)
.values({
...revision,
createdAt: new Date()
})
.returning();
// Invalidate cache for the revisions list
await invalidateCache(`ldapFilterRevisions:${revision.filterId}`);
return result[0];
}
async revertLdapFilterToRevision(filterId: number, revisionId: number): Promise<LdapFilter | undefined> {
debug(`Reverting LDAP filter ${filterId} to revision ${revisionId}`);
// Get the current filter
const filter = await this.getLdapFilter(filterId);
if (!filter) {
return undefined;
}
// Get the target revision
const revision = await this.getLdapFilterRevision(revisionId);
if (!revision || revision.filterId !== filterId) {
return undefined;
}
// Create a new revision with the next version number
const currentVersion = filter.currentVersion || 1;
const newVersion = currentVersion + 1;
// Update the filter with the revision data
const result = await db.update(ldapFilters)
.set({
filter: revision.filter as any,
ldapFilter: revision.ldapFilter,
currentVersion: newVersion,
modifiedAt: new Date()
})
.where(eq(ldapFilters.id, filterId))
.returning();
// Create a new revision record
await this.createLdapFilterRevision({
filterId,
version: newVersion,
filter: revision.filter as any,
ldapFilter: revision.ldapFilter,
createdBy: filter.modifiedBy,
comment: `Reverted to revision ${revision.version}`
});
// Invalidate caches
await invalidateCache(`ldapFilters:${filter.connectionId}`);
await invalidateCache(`ldapFilter:${filterId}`);
return result.length > 0 ? result[0] : undefined;
}
async testLdapFilter(connectionId: number, ldapFilter: string, objectClass: string): Promise<any[]> {
debug(`Testing LDAP filter for connection ${connectionId}, object class ${objectClass}: ${ldapFilter}`);
// This is a placeholder. In a real implementation, this would connect to the LDAP server
// and execute the query. For now, we'll simulate it using our existing data.
let results: any[] = [];
switch (objectClass) {
case 'user':
results = await this.listAdUsers(connectionId);
break;
case 'group':
results = await this.listAdGroups(connectionId);
break;
case 'organizationalUnit':
results = await this.listAdOrgUnits(connectionId);
break;
case 'computer':
results = await this.listAdComputers(connectionId);
break;
case 'domain':
results = await this.listAdDomains(connectionId);
break;
default:
throw new Error(`Unsupported object class: ${objectClass}`);
}
// In a real implementation, we'd use the ldapFilter to filter the results
// Here we're just returning all items since we don't have a LDAP filter parser
// Adding log of the test
debug(`LDAP filter test returned ${results.length} results`);
return results;
}
// AD Users
async getAdUser(id: number): Promise<AdUser | undefined> {