mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-11 03:09:37 +00:00
Add LDAP query builder feature with UI and API endpoints.
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
buildLdapFilter,
|
||||
LdapQueryBuilder
|
||||
} from "./ldap-filter-builder";
|
||||
import { connectToLdap, searchLdap } from "./ldap";
|
||||
import { connectToLdap, searchLdap, getLdapAvailableAttributes } from "./ldap";
|
||||
import { IStorage } from "./storage";
|
||||
import { InsertLdapQuery, LdapQuery, InsertLdapQueryVersion } from "@shared/schema";
|
||||
|
||||
@@ -506,6 +506,87 @@ export function registerLdapQueryBuilderRoutes(router: Router, storage: IStorage
|
||||
* 404:
|
||||
* description: Connection not found
|
||||
*/
|
||||
/**
|
||||
* @swagger
|
||||
* /ldap-queries/attributes:
|
||||
* get:
|
||||
* summary: Get available LDAP attributes
|
||||
* description: Retrieve a list of available attributes for the specified object type from an LDAP connection
|
||||
* tags: [LDAP Query Builder]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: connectionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: LDAP Connection ID
|
||||
* - in: query
|
||||
* name: targetObject
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [users, groups, computers, ous]
|
||||
* description: The type of object to get attributes for
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of available attributes
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: Invalid request parameters
|
||||
* 404:
|
||||
* description: Connection not found
|
||||
*/
|
||||
router.get("/ldap-queries/attributes", hasPermission("read:ldap_objects"), async (req, res) => {
|
||||
try {
|
||||
const connectionId = parseInt(req.query.connectionId as string);
|
||||
const targetObject = req.query.targetObject as "users" | "groups" | "computers" | "ous";
|
||||
|
||||
if (isNaN(connectionId)) {
|
||||
return res.status(400).json({ error: "Invalid connection ID" });
|
||||
}
|
||||
|
||||
if (!targetObject || !["users", "groups", "computers", "ous"].includes(targetObject)) {
|
||||
return res.status(400).json({ error: "Invalid target object type" });
|
||||
}
|
||||
|
||||
// Get the connection
|
||||
const connection = await storage.getLdapConnection(connectionId);
|
||||
if (!connection) {
|
||||
return res.status(404).json({ error: "LDAP connection not found" });
|
||||
}
|
||||
|
||||
// Connect to LDAP
|
||||
try {
|
||||
const client = await connectToLdap(connection);
|
||||
|
||||
// Get available attributes
|
||||
const attributes = await getLdapAvailableAttributes(client, targetObject);
|
||||
|
||||
// Close the connection
|
||||
client.destroy();
|
||||
|
||||
// Return the attributes
|
||||
res.json(attributes);
|
||||
} catch (error) {
|
||||
console.error("Error connecting to LDAP:", error);
|
||||
return res.status(500).json({
|
||||
error: "Failed to connect to LDAP server",
|
||||
details: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error getting LDAP attributes:", error);
|
||||
res.status(500).json({ error: "Failed to retrieve LDAP attributes" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/ldap-queries/test", hasPermission("read:ldap_objects"), async (req, res) => {
|
||||
try {
|
||||
const { connectionId, targetObject, filter, limit = 100, properties = [] } = req.body;
|
||||
|
||||
+128
@@ -128,4 +128,132 @@ export async function getLdapDomainInfo(client: ldapjs.Client): Promise<Record<s
|
||||
debug("Failed to get LDAP domain info:", error);
|
||||
throw new Error(`Failed to get LDAP domain info: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available attributes for a specific object type from LDAP schema
|
||||
* This function retrieves attributes that are commonly used for the specified object type
|
||||
*/
|
||||
export async function getLdapAvailableAttributes(
|
||||
client: ldapjs.Client,
|
||||
targetObject: "users" | "groups" | "computers" | "ous"
|
||||
): Promise<string[]> {
|
||||
debug(`Getting available attributes for ${targetObject}`);
|
||||
|
||||
// Define common attributes for each object type
|
||||
const commonAttributes = [
|
||||
"cn", "name", "displayName", "description", "objectClass", "objectCategory",
|
||||
"distinguishedName", "whenCreated", "whenChanged", "objectGUID", "objectSid"
|
||||
];
|
||||
|
||||
// Object type specific attributes
|
||||
const specificAttributes: Record<string, string[]> = {
|
||||
users: [
|
||||
"sAMAccountName", "userPrincipalName", "givenName", "sn", "mail",
|
||||
"telephoneNumber", "mobile", "title", "department", "company",
|
||||
"manager", "employeeID", "employeeNumber", "memberOf", "userAccountControl",
|
||||
"pwdLastSet", "lastLogon", "lastLogonTimestamp", "accountExpires", "badPwdCount",
|
||||
"logonCount", "homeDirectory", "homeDrive", "scriptPath", "profilePath",
|
||||
"lockoutTime", "country", "st", "l", "streetAddress", "postalCode", "otherTelephone"
|
||||
],
|
||||
groups: [
|
||||
"sAMAccountName", "groupType", "member", "memberOf", "managedBy", "msDS-PrincipalName",
|
||||
"mail", "info", "groupCategory", "adminCount", "proxyAddresses"
|
||||
],
|
||||
computers: [
|
||||
"sAMAccountName", "operatingSystem", "operatingSystemVersion", "operatingSystemServicePack",
|
||||
"dNSHostName", "servicePrincipalName", "lastLogonTimestamp", "pwdLastSet",
|
||||
"userAccountControl", "managedBy", "location", "serialNumber", "msDS-SupportedEncryptionTypes",
|
||||
"networkAddress", "primaryGroupID"
|
||||
],
|
||||
ous: [
|
||||
"ou", "name", "description", "distinguishedName", "managedBy", "gPLink", "gPOptions",
|
||||
"msDS-Approx-Immed-Subordinates", "streetAddress", "l", "st", "postalCode", "c"
|
||||
]
|
||||
};
|
||||
|
||||
try {
|
||||
// Try to dynamically retrieve schema info for more attributes
|
||||
// This gets attributes from the schema for the specific object class
|
||||
let dynamicAttributes: string[] = [];
|
||||
let objectClass: string;
|
||||
|
||||
switch (targetObject) {
|
||||
case "users":
|
||||
objectClass = "user";
|
||||
break;
|
||||
case "groups":
|
||||
objectClass = "group";
|
||||
break;
|
||||
case "computers":
|
||||
objectClass = "computer";
|
||||
break;
|
||||
case "ous":
|
||||
objectClass = "organizationalUnit";
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
// Attempt to query the schema - we need to try different base DNs since the schema location varies
|
||||
let schemaResults: Record<string, any>[] = [];
|
||||
|
||||
// Try common locations for the schema (most AD servers will use one of these)
|
||||
const schemaLocations = [
|
||||
"CN=Schema,CN=Configuration,DC=domain,DC=com", // Generic example
|
||||
"CN=Schema,CN=Configuration,DC=ad,DC=example,DC=com",
|
||||
"CN=Schema,CN=Configuration,DC=example,DC=com",
|
||||
"CN=Schema,CN=Configuration",
|
||||
"CN=Schema",
|
||||
];
|
||||
|
||||
let schemaFound = false;
|
||||
for (const schemaLocation of schemaLocations) {
|
||||
try {
|
||||
schemaResults = await searchLdap(client, {
|
||||
base: schemaLocation,
|
||||
filter: `(&(objectClass=attributeSchema)(|(attributeSyntax=2.5.5.8)(attributeSyntax=2.5.5.9)(attributeSyntax=2.5.5.12)))`,
|
||||
scope: "sub",
|
||||
attributes: ["lDAPDisplayName", "attributeSyntax", "isSingleValued"],
|
||||
limit: 500
|
||||
});
|
||||
if (schemaResults.length > 0) {
|
||||
schemaFound = true;
|
||||
debug(`Found schema at ${schemaLocation} with ${schemaResults.length} attributes`);
|
||||
break;
|
||||
}
|
||||
} catch (locationError) {
|
||||
debug(`Schema not found at ${schemaLocation}: ${locationError instanceof Error ? locationError.message : String(locationError)}`);
|
||||
// Continue trying other locations
|
||||
}
|
||||
}
|
||||
|
||||
if (!schemaFound) {
|
||||
debug("Could not find schema in any of the standard locations");
|
||||
}
|
||||
|
||||
dynamicAttributes = schemaResults.map(attr => attr.lDAPDisplayName);
|
||||
debug(`Retrieved ${dynamicAttributes.length} attributes from schema`);
|
||||
} catch (schemaError) {
|
||||
debug("Failed to retrieve attributes from schema, using predefined list:", schemaError);
|
||||
// Continue with the predefined attributes
|
||||
}
|
||||
|
||||
// Combine common and specific attributes, then add dynamic attributes
|
||||
let allAttributes = [...commonAttributes, ...specificAttributes[targetObject]];
|
||||
|
||||
// Add any dynamic attributes that aren't already in our list
|
||||
dynamicAttributes.forEach(attr => {
|
||||
if (!allAttributes.includes(attr)) {
|
||||
allAttributes.push(attr);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort alphabetically
|
||||
return allAttributes.sort();
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
debug("Failed to get LDAP available attributes:", error);
|
||||
// Return a default set of attributes instead of throwing
|
||||
return [...commonAttributes, ...specificAttributes[targetObject]].sort();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user