What is the meaning and purpose of Active Directory Primary Group? - active-directory

I recently heard of the Primary Group concept in Active Directory but it so far it's quite unclear that is its purpose and how this really works.
As part of the same question, how do I get the Primary Group using LDAP ? It seems that this is not listed as a memberOf attribute.

I've been using this code to get the primary group of a user in Active Directory:
private string GetPrimaryGroup(DirectoryEntry aEntry, DirectoryEntry aDomainEntry)
{
int primaryGroupID = (int)aEntry.Properties["primaryGroupID"].Value;
byte[] objectSid = (byte[])aEntry.Properties["objectSid"].Value;
StringBuilder escapedGroupSid = new StringBuilder();
// Copy over everything but the last four bytes(sub-authority)
// Doing so gives us the RID of the domain
for(uint i = 0; i < objectSid.Length - 4; i++)
{
escapedGroupSid.AppendFormat("\\{0:x2}", objectSid[i]);
}
// Add the primaryGroupID to the escape string to build the SID of the primaryGroup
for(uint i = 0; i < 4; i++)
{
escapedGroupSid.AppendFormat("\\{0:x2}", (primaryGroupID & 0xFF));
primaryGroupID >>= 8;
}
// Search the directory for a group with this SID
DirectorySearcher searcher = new DirectorySearcher();
if(aDomainEntry != null)
{
searcher.SearchRoot = aDomainEntry;
}
searcher.Filter = "(&(objectCategory=Group)(objectSID=" + escapedGroupSid.ToString() + "))";
searcher.PropertiesToLoad.Add("distinguishedName");
return searcher.FindOne().Properties["distinguishedName"][0].ToString();
}
The primary group is a group that every new user is added to by default - usually Domain Users.

Related

Spring ldap unlocking an account

I am trying to unlock user account using spring ldap and getting the error message
""Malformed 'LockoutTime' attribute value" exception.
My code looks like below
public boolean unlockAccount(Name dn) {
ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("lockoutTime", 0));
ldapTemplate.modifyAttributes(dn, new ModificationItem[] {item});
return true;
}
I am using Windows server 2016 and Spring ldap 2.3.2.
Is 'lockoutTime' the correct attribute to unlock an account ?
Is there anything else I am missing ?
In LDAP if you type the wrong password for more than 5 times, the account gets locked. If you want to unlock the user you have to delete an operational attribute name as pwdAccountLockedTime.
public String unlockUser(Users pvo) {
System.out.println("this is pvo" + pvo);
Name dn = buildDn(pvo);
DirContextOperations context = ldapTemplate.lookupContext(dn);
ModificationItem[] modificationItems;
modificationItems = new ModificationItem[1];
modificationItems[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
new BasicAttribute("pwdAccountLockedTime"));
ldapTemplate.modifyAttributes(dn, modificationItems);
return "Account Unlocked";
}
build Dn for your LDAP and use the above code then the user gets unlocked.
String[] attrIDs = new String[] { "lockoutTime", "sAMAccountName",
"distinguishedName","pwdLastSet", "accountExpires", "userAccountControl",
"IsAccountLocked" };
ctls.setReturningAttributes(attrIDs);
ctls.setSearchScope(2);
String filter = "(&(objectClass=user)(objectCategory=Person)(sAMAccountName=" +
samaccountname+ "))";
NamingEnumeration<SearchResult> answer = ctx.search(adManagedOU, filter,ctls);
while (answer.hasMore()) {
SearchResult rs = answer.next();
Attributes attrs = rs.getAttributes();
distinguishedName = rs.getNameInNamespace();
String[] lockouttime = null;
String lockOutValue=attrs.get("lockoutTime");
if (lockOutValue != null)
{
lockouttime = attrs.get("lockoutTime").toString().split(":");
if (Long.valueOf(lockouttime[1].trim()) > 0) {
ModificationItem[] mods1 = new ModificationItem[] {
new ModificationItem(2, new BasicAttribute("lockoutTime", "0") )
};
((DirContext) ctls).modifyAttributes(distinguishedName, mods1);
}
else
{
LOGGER.info(username + " Account Not Locked");
}
The only values that may be set on lockouttime is to set the value to "0" which will effectively un-lock the account.
To learn more on Microsoft Active Directory Lockouts.
Setting the value to a String instead of an int makes this work, at least with AWS Simple AD.
ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("lockoutTime", "0"));
ldapTemplate.modifyAttributes(dn, new ModificationItem[] {item});

Fastest way to record all DocIds and FileNames from dtSearch in SQL database

I am using dtSearch on combination with a SQL database and would like to maintain a table that includes all DocIds and their related FileNames. From there, I will add a column with my foreign key to allow me to combine text and database searches.
I have code to simply return all the records in the index and add them one by one to the DB. This, however, takes FOREVER, and doesn't address the issue of how to simply append new records as they are added to the index. But just in case it helps:
MyDatabaseContext db = new StateScapeEntities();
IndexJob ij = new dtSearch.Engine.IndexJob();
ij.IndexPath = #"d:\myindex";
IndexInfo indexInfo = dtSearch.Engine.IndexJob.GetIndexInfo(#"d:\myindex");
bool jobDone = ij.Execute();
SearchResults sr = new SearchResults();
uint n = indexInfo.DocCount;
for (int i = 1; i <= n; i++)
{
sr.AddDoc(ij.IndexPath, i, null);
}
for (int i = 1; i <= n; i++)
{
sr.GetNthDoc(i - 1);
//IndexDocument is defined elsewhere
IndexDocument id = new IndexDocument();
id.DocId = sr.CurrentItem.DocId;
id.FilePath = sr.CurrentItem.Filename;
if (id.FilePath != null)
{
db.IndexDocuments.Add(id);
db.SaveChanges();
}
}
To keep the DocId in the index you must use the flag dtsIndexKeepExistingDocIds in the IndexJob
You can also look the dtSearch Text Retrieval Engine Programmer's Reference when the DocID is changed
When a document is added to an index, it is assigned a DocId, and DocIds are always numbered sequentially.
When a document is reindexed, the old DocId is cancelled and a new DocId is assigned.
When an index is compressed, all DocIds in the index are renumbered to remove the cancelled DocIds unless the dtsIndexKeepExistingDocIds flag is set in IndexJob.
When an index is merged into another index, DocIds in the target index are never changed. The documents merged into the target index will all be assigned new, sequentially-numbered DocIds, unless (a) the dtsIndexKeepExistingDocIds flag is set in IndexJob and (b) the indexes have non-overlapping ranges of doc ids.
To improve your speed you can search for the word "xfirstword" and get all documents in an index.
You can also look to the faq How to retrieve all documents in an index
So, I used part of user2172986's response, but combined it with some additional code to get the solution to my question. I did indeed have to set the dtsKeepExistingDocIds flag in my index update routine.
From there, I only wanted to add the newly created DocIds to my SQL database. For that, I used the following code:
string indexPath = #"d:\myindex";
using (IndexJob ij = new dtSearch.Engine.IndexJob())
{
//make sure the updated index doesn't change DocIds
ij.IndexingFlags = IndexingFlags.dtsIndexKeepExistingDocIds;
ij.IndexPath = indexPath;
ij.ActionAdd = true;
ij.FoldersToIndex.Add( indexPath + "<+>");
ij.IncludeFilters.Add( "*");
bool jobDone = ij.Execute();
}
//create a DataTable to hold results
DataTable newIndexDoc = MakeTempIndexDocTable(); //this is a custom method not included in this example; just creates a DataTable with the appropriate columns
//connect to the DB;
MyDataBase db = new MyDataBase(); //again, custom code not included - link to EntityFramework entity
//get the last DocId in the DB?
int lastDbDocId = db.IndexDocuments.OrderByDescending(i => i.DocId).FirstOrDefault().DocId;
//get the last DocId in the Index
IndexInfo indexInfo = dtSearch.Engine.IndexJob.GetIndexInfo(indexPath);
uint latestIndexDocId = indexInfo.LastDocId;
//create a searchFilter
dtSearch.Engine.SearchFilter sf = new SearchFilter();
int indexId = sf.AddIndex(indexPath);
//only select new records (from one greater than the last DocId in the DB to the last DocId in the index itself
sf.SelectItems(indexId, lastDbDocId + 1, int.Parse(latestIndexDocId.ToString()), true);
using (SearchJob sj = new dtSearch.Engine.SearchJob())
{
sj.SetFilter(sf);
//return every document in the specified range (using xfirstword)
sj.Request = "xfirstword";
// Specify the path to the index to search here
sj.IndexesToSearch.Add(indexPath);
//additional flags and limits redacted for clarity
sj.Execute();
// Store the error message in the status
//redacted for clarity
SearchResults results = sj.Results;
int startIdx = 0;
int endIdx = results.Count;
if (startIdx==endIdx)
return;
for (int i = startIdx; i < endIdx; i++)
{
results.GetNthDoc(i);
IndexDocument id = new IndexDocument();
id.DocId = results.CurrentItem.DocId;
id.FileName= results.CurrentItem.Filename;
if (id.FileName!= null)
{
DataRow row = newIndexDoc.NewRow();
row["DocId"] = id.DocId;
row["FileName"] = id.FileName;
newIndexDoc.Rows.Add(row);
}
}
newIndexDoc.AcceptChanges();
//SqlBulkCopy
using (SqlConnection connection =
new SqlConnection(db.Database.Connection.ConnectionString))
{
connection.Open();
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
bulkCopy.DestinationTableName =
"dbo.IndexDocument";
try
{
// Write from the source to the destination.
bulkCopy.WriteToServer(newIndexDoc);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
newIndexDoc.Clear();
db.UpdateIndexDocument();
}
Here is my new solution with AddDoc method from the SearchResults interface:
First get the StartingDocID and the LastDocID from the IndexInfo and walk the loop like this:
function GetFilename(paDocID: Integer): String;
var
lCOMSearchResults: ISearchResults;
lSearchResults_Count: Integer;
begin
if Assigned(prCOMServer) then
begin
lCOMSearchResults := prCOMServer.NewSearchResults as ISearchResults;
lCOMSearchResults.AddDoc(GetIndexPath(prIndexContent), paDocID, 0);
lSearchResults_Count := lCOMSearchResults.Count;
if lSearchResults_Count = 1 then
begin
lCOMSearchResults.GetNthDoc(0);
Result := lCOMSearchResults.DocDetailItem['_Filename'];
end;
end;
end

Retrieve All Members of Large AD Groups

Working with an Microsoft Active Directory and Unboundid SDK and there is a group with >29k members.
I am trying to utilize the range values to get all the groups, but can not determine when the end has been reached.
I am using this method: (Updated to working code)
public static List<String> getAttributeRangeBasedSearch(LDAPConnection ldc, String basedn, String filter, int step, String return_attribute) throws LDAPException
{
List<String> allValues = new ArrayList<String>();
// initialize counter to total the group members and range values
int allvalues = 0;
int start = 0;
// int step = 1000;
int finish = step - 1;
boolean finallyFinished = false;
String range;
// loop through the query until we have all the results
while (!finallyFinished)
{
range = start + "-" + finish;
String currentRange = return_attribute + ";Range=" + range;
String range_returnedAtts[] = { currentRange };
SearchRequest searchRequest = new SearchRequest(basedn, SearchScope.BASE, filter, range_returnedAtts);
List<SearchResultEntry> rangedEntries = ldc.search(searchRequest).getSearchEntries();
for (Iterator<SearchResultEntry> iterator = rangedEntries.iterator(); iterator.hasNext();)
{
SearchResultEntry searchResultEntry = iterator.next();
Collection<Attribute> allAttribute = searchResultEntry.getAttributes();
for (Iterator<Attribute> attributeIterator = allAttribute.iterator(); attributeIterator.hasNext();)
{
Attribute attribute = attributeIterator.next();
log.debug("---> " + allvalues + ": " + attribute.getName());
if (attribute.getName().endsWith("*"))
{
currentRange = attribute.getName();
finallyFinished = true;
}
String[] attributeBatch = searchResultEntry.getAttributeValues(currentRange);
for (int i = 0; i < attributeBatch.length; i++)
{
allValues.add(attributeBatch[i]);
log.debug("-- " + allvalues++ + " " + attribute.getName() + ":" + attributeBatch[i]);
}
}
}// for SearchResultEntry
start = start + step;
finish = finish + step;
}// finallyFinished
return allValues;
}
Any ideas?
Thanks
-jim
I got things working, but the process is very difficult and currently I am using a hard coded value for the step as this could be dynamically changed formt he default of 1,500 to a hard coded limit of 5,000.
I have not been able to determine the value dynamically. Appears, maybe, that if it is not defined at: CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,forest root then is must be at defaults, which the default, also varies based on which version of Microsoft Active Directory is being used.
There is also described in MSDN about some sort of control that might help, but no information on how it could be used. Anyone ever use this?
LDAP policies are specified using the lDAPAdminLimits attribute.
The lDAPAdminLimits attribute of a queryPolicy object is a multivalued string where each string value encodes a name-value pair. In the encoding, the name and value are separated by an "=". For example, the encoding of the name "MaxActiveQueries" with value "0" is "MaxActiveQueries=0". Each name is the name of an LDAP policy, and the value is a value of that policy.
There can be multiple queryPolicy objects in a AD Forest. A DC determines the queryPolicy object that contains its policies according to the following logic:
If the queryPolicyObject attribute is present on the DC's nTDSDSA object, the DC uses the queryPolicy object referenced by it.
Otherwise, if the queryPolicyObject attribute is present on the nTDSSiteSettings object for the Active Directory Site to which the DC belongs, the DC uses the queryPolicy object referenced by the Active Directory Site.
Otherwise, the DC uses the queryPolicy object whose DN is "CN=Default Query Policy,CN=Query-Policies" relative to the nTDSService object (for example, "CN=Default Query Policy, CN=Query-Policies, CN=Directory Service, CN=Windows NT, CN=Services" relative to the root of the config NC).
This one can retrieve and store in textfile any number of users. Moreover it will not finish in infinite loop if group is empty
$myGroup = [string]$args[0];
$myGroup = $myGroup.replace(" ",",");
$group = [adsi]("LDAP://$($myGroup)");
$from = 0
$all = $false
$members = #()
while (! $all) {
trap{$script:all = $True;continue}
$to = $from + 999
$DS = New-Object DirectoryServices.DirectorySearcher($Group,"(objectClass=*)","member;range=$from-$to",'Base')
$members += $ds.findall() | foreach {$_.properties | foreach {$_.item($_.PropertyNames -like 'member;*')}}
if($from -gt $members.count){
break;
}
$from += 1000
}
$currentExecuting = (Get-Item $MyInvocation.MyCommand.Path)
$group.sAMAccountName
$members | measure-object
$members > "$($currentExecuting.Directory)\$($group.sAMAccountName).txt"
usage:
getADGroupMembers.ps1 CN=groupName,OU=myOrgUnit,DC=contoso,DC=com
Here is a very good code example where you can get all members of a group by ranges. It handles the case when you're on the last range too. You can also transform this method in a paginated request. Have a look. It helped me.
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://CN=My Distribution List,OU=Distribution Lists,DC=Fabrikam,DC=com");
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(objectClass=*)";
uint rangeStep = 1000;
uint rangeLow = 0;
uint rangeHigh = rangeLow + (rangeStep - 1);
bool lastQuery = false;
bool quitLoop = false;
do
{
string attributeWithRange;
if(!lastQuery)
{
attributeWithRange = String.Format("member;range={0}-{1}", rangeLow, rangeHigh);
}
else
{
attributeWithRange = String.Format("member;range={0}-*", rangeLow);
}
searcher.PropertiesToLoad.Clear();
searcher.PropertiesToLoad.Add(attributeWithRange);
SearchResult results = searcher.FindOne();
foreach(string res in results.Properties.PropertyNames)
{
System.Diagnostics.Debug.WriteLine(res.ToString());
}
if(results.Properties.Contains(attributeWithRange))
{
foreach(object obj in results.Properties[attributeWithRange])
{
Console.WriteLine(obj.GetType());
if(obj.GetType().Equals(typeof(System.String)))
{
}
else if (obj.GetType().Equals(typeof(System.Int32)))
{
}
Console.WriteLine(obj.ToString());
}
if(lastQuery)
{
quitLoop = true;
}
}
else
{
lastQuery = true;
}
if(!lastQuery)
{
rangeLow = rangeHigh + 1;
rangeHigh = rangeLow + (rangeStep - 1);
}
}
while(!quitLoop);
}
catch(Exception ex)
{
// Handle exception ex.
}
Source: http://systemmanager.ru/adam-sdk.en/netds/enumerating_members_in_a_large_group.htm

Is my LDAP syntax wrong in search filter

This is my first attempt in trying to query our LDAP server for AD info. When I am trying to query the LDAP server here is what I'm trying to retrieve:
I am trying to retrieve all active employees with a countlimit of 500 records whose displayname starts with "sav", has an email address and has a userAccountControl attribute of 512. The problem I'm encountering is that I'm only getting back 8 records total. I should literally be getting back at least 10 records.
I did a separate search on the 2 records that were NOT retrieved in my search and each had an email address and a userAccountControl value of 512. So I'm not sure why those 2 records were missing.
I'm sure I've done something wrong in my syntax but I cannot find what it is. Any HELP/DIRECTION would be appreciated. Thank you.
After googling I've defined the SEARCH FILTER as:
String searchFilter = "(&(objectClass=user)(displayname="+displayname+"*"+")(mail=*)(userAccountControl=512))";
Please see my complete method below:
public List<String> getAutocompleteEmpRecordsList(String displayname, LdapContext ctx) {
List<String> activeEmpAttributes = new ArrayList<String>();
Attributes attrs = null;
int count = 0;
int empEmailAddrLen = 0;
try {
SearchControls constraints = new SearchControls();
constraints.setCountLimit(500);
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
String[] attrIDs = {"displayname", "mail", "userAccountControl"};
constraints.setReturningAttributes(attrIDs);
String searchFilter = "(&(objectClass=user)(displayname="+displayname+"*"+")(mail=*)(userAccountControl=512))";
NamingEnumeration answer = ctx.search("OU=Standard,OU=Users,DC=xxx,DC=org", searchFilter, constraints);
if (answer != null) {
while (answer.hasMore()) {
attrs = ((SearchResult) answer.next()).getAttributes();
if (attrs.get("displayname") != null) {
int empNameLen = attrs.get("displayname").toString().length();
activeEmpAttributes.add(attrs.get("displayname").toString().substring(13, empNameLen));
}
count++;
ctx.close();
}
}
else {
throw new Exception("Invalid User");
}
System.out.println("activeEmpAttributes: " + activeEmpAttributes);
System.out.println("count: " + activeEmpAttributes.size());
} catch (Exception ex) {
ex.printStackTrace();
}
return activeEmpAttributes;
}
You may be confusing displayname attribute and cn attribute.
On Windows server you've got a command line tool called LDIDIFDE.EXE which can allow you to test your filter.
ldifde -f datas.ldf -d "OU=Standard,OU=THR Users,DC=txhealth,DC=org" -r "(&(objectClass=user)(displayname=sav*)(mail=*)(userAccountControl=512))"
ldifde -f datas.ldf -d "OU=Standard,OU=THR Users,DC=txhealth,DC=org" -r "(&(objectClass=user)(cn=sav*)(mail=*)(userAccountControl=512))"
In the User and computer MMC you can also test your filter.
Start User and computer Active-Directory :
Right buton on registered request :
Choose personalize search, you've got an helper tab for common attributes :
You can choose personalized tab for technical attributes
You can test en copy the resulting LDAP filter (you don't need the double (& one is enough):
Can you post your userAccountControl, displayName, and mail values for the two excluded users?
FWIW the medial search on displayName would run alot faster if you add a tuple index to it.
I downloaded a free AD tool to view all in AD that I needed and it showed me that the data was not the problem but I was just not hitting all the OU's that I needed because there is NOT just 1 OU where all our users are stored.
Consequently, after googling some more I found a page on the Oracle site regarding LDAP and I changed my LDAPContext to DirContext for my connection to do searches within the directory as well as using this context's REFERRAL and set the value to "follow" to avoid the PartialSearchException.
I thought I'd post my findings just in case some other newbie ran into the same issue.
If you see a downside to the changes I made please let me know. Regards.
Here is my corrected code:
public List<String> getAutocompleteEmpRecordsList(String displayname, DirContext ctx) {
List<String> activeEmpAttributes = new ArrayList<String>();
Attributes attrs = null;
int count = 0;
int empEmailAddrLen = 0;
try {
SearchControls constraints = new SearchControls();
constraints.setCountLimit(500);
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
String[] attrIDs = {"displayname", "mail", "userAccountControl"};
constraints.setReturningAttributes(attrIDs);
String searchFilter = "(&(objectClass=user)(displayname="+displayname.trim()+"*"+")(mail=*)(userAccountControl=512))";
NamingEnumeration answer = ctx.search("DC=xxx,DC=org", searchFilter, constraints);
if (answer != null) {
while (answer.hasMore()) {
attrs = ((SearchResult) answer.next()).getAttributes();
if (attrs.get("displayname") != null) {
int empNameLen = attrs.get("displayname").toString().length();
activeEmpAttributes.add(attrs.get("displayname").toString().substring(13, empNameLen));
}
count++;
ctx.close();
}
}
else {
throw new Exception("Invalid User");
}
System.out.println("activeEmpAttributes: " + activeEmpAttributes);
System.out.println("count: " + activeEmpAttributes.size());
} catch (Exception ex) {
ex.printStackTrace();
}
return activeEmpAttributes;
}
Thanks anyway.

Odd result by using DirectorySearcher

I am using DirectorySearcher to get all AD users' display name from company AD server, we have around 100k records and most of results are correct.
But we got near 100 users' display name are "$CimsUserVersion2", it's really a odd result, I checked in outlook which also sync display name from AD, the name is correct
Have u facing same issue?
Thanks a lot
using (var de = new DirectoryEntry("LDAP://" + domain))
{
using (var search = new DirectorySearcher(de))
{
search.Filter = "CN=" + userName;
var results = search.FindAll();
string temp = results[0].Properties["displayname"][0].ToString();
if (string.IsNullOrEmpty(temp))
{
return string.Empty;
}
else
{
return temp;
}
}
}
Not sure if that's the problem - but I think you'd need to tell your searcher that you want the displayName attribute to be loaded:
using (var de = new DirectoryEntry("LDAP://" + domain))
{
using (var search = new DirectorySearcher(de))
{
search.Filter = "CN=" + userName;
search.PropertiesToLoad.Add("displayName"); // specify "displayname" to be returned from search
var results = search.FindAll();
string temp = results[0].Properties["displayname"][0].ToString();
if (string.IsNullOrEmpty(temp))
{
return string.Empty;
}
else
{
return temp;
}
}
}
Don't you automate the provisioning of UNIX users and groups into Microsoft Active Directory with "Centrify DirectControl".
This tool uses a simple object model to manage the UNIX-specific properties for users, groups, computers, and zones, as well as UNIX NIS services.
As far as I understand it uses Active-Directory attributes to register some special informations.
UserVersion is map to displayName :
UserVersion determines compatibility between a user profile object and the Centrify DirectControl Administrator Console. The only valid value for this attribute is $CimsUserVersion2.
For example:
displayName: $CimsUserVersion2

Resources