mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-08 01:43:40 +00:00
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:
@@ -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
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user