"Domain Users" group is empty when I use DirectoryServices "member" property - vb.net

I'm using the following code to get the members of a group on my domain:
Dim de As New DirectoryEntry("LDAP://" & GroupDN)
For Each user As String In CType(de.Properties("member"), IEnumerable)
GroupCollection.Add(Username, Username)
Next
My problem is that when GroupDN (the distinguishedname of the group) is "CN=Domain Users,CN=Users,DC=Mydomain,DC=local", the For...Each loop doesn't execute, and when I check the Properties statement manually, it's got a count of zero. This seems to work for every other group in my domain, but the "Domain Users" group should contain everybody, and it appears to contain nobody.
I've checked, and the group lists everybody correctly in my Windows AD tools. Is there something obvious that I'm missing here? On a side note, is there a better way to get all the members of a group?

Unless you change the primary group id of a user, the user is not stored in the member attribute of the Domain Users group, rather it uses the fact that the primary group id is set to the Domain Users RID to determine membership in Domain Users. The normal case is that the Domain Users member attribute is empty; it would require that you make some changes to the default Active Directory implementation for this to not be the case.
The Domain Users group uses a
"computed" mechanism based on the
"primary group ID" of the user to
determine membership and does not
typically store members as
multi-valued linked attributes. If the
primary group of the user is changed,
their membership in the Domain Users
group is written to the linked
attribute for the group and is no
longer calculated. This was true for
Windows 2000 and has not changed for
Windows Server 2003.
Reference

The accepted answer is absolutely correct. By default every (user) object has 513 set in the property primarygroupid, which is the fixed "tail" of the Domain Users sid. But: this can be changed and every other group can be configured there, so we can't rely on that.
Here is an example method how to get the group members anyway (regardless if the default is kept or changed). I call a method like this after any query for members of active directory groups. In this example I get an array of distinguished names as result. But all other properties are possible, just add them to dSearcher.PropertiesToLoad.Add(...) and modify the result.
I know, this is a question about VB, I hope its easy to port it.
using System.DirectoryServices;
using System.Security.Principal;
public static string[] GetMembersDnByPrimaryGroupId(string domainName, SecurityIdentifier sidOfGroupToGetMembersByPrimaryGroupId)
{
// In a single domain environement the domain name is probably not needed, but
// we expect a multy domain environement
if (string.IsNullOrWhiteSpace(domainName) || sidOfGroupToGetMembersByPrimaryGroupId == null)
{
throw new ArgumentNullException($"Neither domainName nor sid may be null / blank: DomainName: { domainName }; sid: { sidOfGroupToGetMembersByPrimaryGroupId }");
//<----------
}
List<string> membersDnResult = new List<string>();
// Get the last segment of the group sid, this is what is stored in the "primaryGroupId"
string groupSidTail = sidOfGroupToGetMembersByPrimaryGroupId.Value.Split('-').Last();
string path = $"LDAP://{ domainName }";
DirectoryEntry dEntry = new DirectoryEntry(path);
SearchResultCollection adSearchResult = null;
DirectorySearcher dSearcher = new DirectorySearcher(dEntry);
// For this example we need just the distinguished name but you can add
// here the property / properties you want
dSearcher.PropertiesToLoad.Add("distinguishedName");
// set the filter to primarygroupid
dSearcher.Filter = $"(&(primarygroupid={ groupSidTail }))";
// May die thousand deaths, therefore exception handling is needed.
// My exception handling is outside of this method, you may want
// to add it here
adSearchResult = dSearcher.FindAll();
// Get the domains sid and check if the domain part of the wanted sid
// fits the domain sid (necesarry in multy domain environments)
byte[] domainSidBytes = (byte[])dEntry.Properties["objectSid"].Value;
SecurityIdentifier domainSid = new SecurityIdentifier(domainSidBytes, 0);
if (sidOfGroupToGetMembersByPrimaryGroupId.AccountDomainSid != domainSid)
{
throw new ArgumentException($"Domain sid of the wanted group { sidOfGroupToGetMembersByPrimaryGroupId.AccountDomainSid } does not fit the sid { domainSid } of the searched through domain \"{ domainName }\"");
//<----------
}
// We found entries by the primarygroupid
if (adSearchResult.Count > 0)
{
foreach (SearchResult forMemberByPrimaryGroupId in adSearchResult)
{
// Every AD object has a distinguishedName, therefore we acess "[0]"
// wihtout any further checking
string dn = forMemberByPrimaryGroupId.Properties["distinguishedName"][0].ToString();
membersDnResult.Add(dn);
}
}
return membersDnResult.ToArray();
}

Related

LDAP_MATCHING_RULE_IN_CHAIN not working with default AD groups - Domain Users

In my program, I need to fetch all the AD groups for a user.
The current version of my program uses System.DirectoryServices.AccountManagement.UserPrincipal.GetAuthorizationGroups.
This works good until we had a new customer with a much largeer AD. There it is really slow. (Up to 60 seconds)
Now I've been looking around, and saw the posts that the AccountManagement is easy to use, but slow.
I also found that LDAP_MATCHING_RULE_IN_CHAIN should also fetch all the nested groups that a user is member of. And is more performant.
From this link.
But I'm having an issue with the default groups that in AD exists.
For example the group "Domain Users" is not returned by the function.
They also have a group "BDOC" that as member have "Domain Users". That group is also not returned.
Trough the GetAuthorizationGroups it is returned correct.
I'm using following code to fetch the groups by user.
VB.NET:
Dim strFilter As String = String.Format("(member:1.2.840.113556.1.4.1941:={0})", oUserPrincipal.DistinguishedName)
Dim objSearcher As New DirectoryServices.DirectorySearcher("LDAP://" & oLDAPAuthenticationDetail.Domain & If(Not String.IsNullOrWhiteSpace(oLDAPAuthenticationDetail.Container), oLDAPAuthenticationDetail.Container, String.Empty))
objSearcher.PageSize = 1000
objSearcher.Filter = strFilter
objSearcher.SearchScope = DirectoryServices.SearchScope.Subtree
objSearcher.PropertiesToLoad.Add(sPropGuid)
objSearcher.PropertiesToLoad.Add(sPropDisplayName)
Dim colResults As DirectoryServices.SearchResultCollection = objSearcher.FindAll()
Afterwards I was testing with the script from the link, if it was possible to fetch all the users from the Domain Users group, by changing the "member" to "memberOf" in the filter.
When I put the Domain Admins group in the filter, it shows the admins correct.
When I put the Domain Users group in the filter, it returns nothing.
Powershell:
$userdn = 'CN=Domain Users,CN=Users,DC=acbenelux,DC=local'
$strFilter = "(memberOf:1.2.840.113556.1.4.1941:=$userdn)"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://rootDSE")
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = "LDAP://$($objDomain.rootDomainNamingContext)"
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Base"
$colProplist = "name"
foreach ($i in $colPropList)
{
$objSearcher.PropertiesToLoad.Add($i) > $nul
}
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults)
{
$objItem = $objResult.Properties
$objItem.name
}
I don't know what I'm doing wrong. Or is it maybe just not possible to fetch the "default groups" with that filter?
What is a good alternative then?
The default group is odd. It is not stored in memberOf, or even in the member attribute of the group. That's why your search won't find it. The default group is determined by the primaryGroupId of the user. That attribute stores the RID (the last part of the SID) of the group. It's kind of dumb, I know :)
I actually wrote an article about the 3 (yes 3) different ways someone can be a member of a group: What makes a member a member?
I also wrote an article about getting all of the groups a single user belongs to, and how to account for all 3 ways: Finding all of a user’s groups
For example, here is the C# code I put in that article about how to find the name of the primary group for a user (given a DirectoryEntry). It shouldn't be too hard to translate that to VB.NET:
private static string GetUserPrimaryGroup(DirectoryEntry de) {
de.RefreshCache(new[] {"primaryGroupID", "objectSid"});
//Get the user's SID as a string
var sid = new SecurityIdentifier((byte[])de.Properties["objectSid"].Value, 0).ToString();
//Replace the RID portion of the user's SID with the primaryGroupId
//so we're left with the group's SID
sid = sid.Remove(sid.LastIndexOf("-", StringComparison.Ordinal) + 1);
sid = sid + de.Properties["primaryGroupId"].Value;
//Find the group by its SID
var group = new DirectoryEntry($"LDAP://<SID={sid}>");
group.RefreshCache(new [] {"cn"});
return group.Properties["cn"].Value as string;
}
You are right that the AccountManagement namespace makes things easy, but it really does have terrible performance sometimes. I never use it anymore. I find that DirectoryEntry/DirectorySearcher gives you much more control of how often your code makes calls out to AD.
I have been meaning to write an article about writing high performance code with DirectoryEntry, but I haven't gotten around to it yet.
Update: So if you need the nested groups for the user, including membership through the primary group, then you can find the primary group first, then do an LDAP_MATCHING_RULE_IN_CHAIN search for groups that have both the user and the primary group as members:
(|(member:1.2.840.113556.1.4.1941:={userDN})(member:1.2.840.113556.1.4.1941:={primaryGroupDN}))
Update: If you want to include Authenticated Users in your search (edit the DC portion of the distinguishedName):
(|(member:1.2.840.113556.1.4.1941:=CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=domain,DC=com)(member:1.2.840.113556.1.4.1941:={userDN})(member:1.2.840.113556.1.4.1941:={primaryGroupDN})
Note that you can also use the tokenGroups attribute of a user to find out all of the authentication groups for a user. The tokenGroups attribute is a constructed attribute, so you only get it if you specifically ask for it (with DirectoryEntry.RefreshCache() or, in a search, add it to DirectorySearcher.PropertiesToLoad.
The caveat is that tokenGroups is a list of SIDs, not distinguishedName, but you can bind directly to an object using the SID with the LDAP://<SID={sid}> syntax.

How to Fetch custom column value from Sharepoint 2010 user group

I have created an user group in SP 2010 and i have added one custom column to it, from the list settings.
How to get the custom column value in web part?
EDIT:
My custom column is District. I want to return that column value in visual web part application.
To return group users i use this code
List<SPUser> users = SPContext.Current.Web.SiteGroups["PDO Owners"].Users.ToList();
I assume you mean you created a custom property for the user profile as in my opinion you cannot add extra columns to user groups. You can get the values through the ProfileManager object doing something like this:
//GET THE USER PROFILE MANAGER
SPServiceContext sc = SPServiceContext.GetContext(site);
UserProfileManager userProfileManager = new UserProfileManager(sc);
//GET A PROFILE FOR A USER
UserProfile profile = userProfileManager.GetUserProfile("i:0#.f|fbamembershipprovider|myfbauser");
string propertyvalue = profile["propertyinternalname"].Value.ToString();
Depending on the type of field, you will have to use something else than ToString (eg for a managed metadata field i think you should use TaxonomyFieldValue, etc...)

Ensuring Unique Email Address in Documents

I want to create or update a UserAccount entity with an EmailAddress property, whilst ensuring the EmailAddress is unique. UserAccount has it's own Id property of type long, this is just so I can track each UserAccount entity, as using the email address as the document id would present issues if the user wishes to change their email address.
I've heard I could create a new collection called UniqueEmailAddresses that could be collection of empty (or near empty?) documents and the email address would be the actual document id.
Is this correct? If I update both collections in one transaction, is this the best way to ensure that 2 documents with the same email addresses don't end up in UserAccount collection? Does adding this extra collection with the user's email address as the id, cause any performance issues when dealing with millions of users? Is it possible to create an empty document? Am I going down the right path?
EDIT
These are the available bundles, which bundle do I need to add to get the unique constraint features?
EDIT 2
I've added the Raven.Bundles.UniqueConstraints DLL to the folder ~/App_Data/RavenDB/Plugins (I'm using the embedded server) and changed my Document Store Provider to:
protected override IDocumentStore CreateInstance(IContext context)
{
EmbeddableDocumentStore documentStore = new EmbeddableDocumentStore()
{
DataDirectory = #"~\App_Data\RavenDB",
UseEmbeddedHttpServer = true,
Configuration = { Port = 8181, PluginsDirectory = #"~\App_Data\RavenDB\Plugins" }
};
documentStore.RegisterListener(new UniqueConstraintsStoreListener());
documentStore.Initialize();
return documentStore;
}
But it still doesn't show in the available bundles list and my call to checkResult.ConstraintsAreFree() wrongly returns true when I'm testing by breaking the constraint.
RavenDB has the notion of the unique constraints bundles that does all of it for you.
See the docs:
http://ravendb.net/docs/article-page/3.0/csharp/client-api/bundles/how-to-work-with-unique-constraints-bundle

typo3 extbase permissions in extensions

I have written one extension for making service order.
The issue I am facing here is,
There are FE users belong to three FE user groups namely "client", "Admin" and "Employee".
Here the client can make order and he should be able to see only his orders.
And the admin can see all orders done by different clients.
And the employee should be able to see only some clients orders.
Currently I made a order table with N:1 relation with FE user table. So every order should relate with any one client.
So in controller, I am checking the login user and using custom query in repository, I am accessing order related to loggedin client (FE user)
In file OrdersController.php
public function listAction() {
$orders = $this->ordersRepository->orderForLoginUsr();
$this->view->assign('orders', $orders);
}
In file OrdersRepository.php
public function orderForLoginUsr(){
$loggedInUserId = $GLOBALS ['TSFE']->fe_user->user['uid'];
$query = $this->createQuery();
$query->matching(
$query->equals('user', $loggedInUserId)
);
$query->setOrderings(array('crdate' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING));
return $query->execute();
}
But here my question is how to make admin user able to see all the orders done by all clients?
I have to write different template and action that calling function findAll() ?
$orders = $this->ordersRepository->findAll();
And how to set for usergroup Employee ?
Thanks in Advance
I think that the easiest way is to actually implement 3 actions with 3 different plugins, something like: listClientAction, listAdminAction and listEmployeeAction
In each of those action, you implement a method in your repository that fetch the right list of order with the good ordering:
orderForLoginClient(), orderForLoginEmployee(), orderForLoginAdmin()
What does the trick actually is that there will be 3 plugins on your page, one for each action. In each instance of your plugin, you set the access for the right be_group.
Don't forget to add the actions and plugin in the localconf and ext_table files.
I hope it will help!
Olivier
If your view is almost the same for client, admin, employee you should simply add a method like getOrderWithPermissionsForUser($currentUser);
In the method itself you should check for the usergroup and call different queries on your Repo.
If your view is different from usergroup to usergroup, you should use different templates with partials for the same parts.
If the data of the views is the same, just change the template for each usergroup in the action. If not use different actions.
Here is a helper method for easily changing your templatefile.
/**
* This method can change the used template file in an action method.
*
* #param string $templateName Something like "List" or "Foldername/Actionname".
* #param string $templateExtension Default is "html", but for other output types this may be changed as well.
* #param string $controllerName Optionally uses another subfolder of the Templates/ directory
* By default, the current controller name is used. Example value: "JobOffer"
* #param \TYPO3\CMS\Fluid\View\AbstractTemplateView $viewObject The view to set this template to. Default is $this->view
*/
protected function changeTemplateFile($templateName, $templateExtension = 'html', $controllerName = null, AbstractTemplateView $viewObject = null)
{
if (is_null($viewObject)) {
$viewObject = $this->view;
}
if (is_null($controllerName)) {
$controllerName = $this->getControllerContext()->getRequest()->getControllerName();
}
$templatePathAndFilename = $this->getTemplateRootpathForView($controllerName . '/' . $templateName . '.' . $templateExtension);
$viewObject->setTemplatePathAndFilename($templatePathAndFilename);
}

Make openam/opensso return role name instead of role universal id

I'm using OpenAM 9.5.2 for authenticating users on an application. The authentication works well but I'm having issues to get user memberships from final application.
I've defined the group "somegroup" in openam and added my user to this group. Now in my application, I want to test if authenticated users is member of this group. If I'm testing it with:
request.isUserInRole("somegroup");
I get false result. Actually, I have to test
request.isUserInRole("id=somegroup,ou=group,dc=opensso,dc=java,dc=net");
in order to get a true response.
I know that it's possible to define a privileged attribute mapping list in the sso agent configuration to map id=somegroup,ou=group,dc=opensso,dc=java,dc=net on somegroup, but it's not suitable in my situation since roles and groups are stored in an external database. It's not convenient to define role in database and mapping in sso agent conf.
So my question : is there a way to make openam use the "short" (i.e. somegroup) group name instead of its long universal id ?
This is not an answer, just one remark.
I've performed some researches in openam sources and it seems to confirm that the role name stored in repository is replaced by universalId when openam build the request. This is performed in com.sun.identity.idm.server.IdRepoJAXRPCObjectImpl.java class:
public Set getMemberships_idrepo(String token, String type, String name,
String membershipType, String amOrgName,
String amsdkDN
) throws RemoteException, IdRepoException, SSOException {
SSOToken ssoToken = getSSOToken(token);
Set results = new HashSet();
IdType idtype = IdUtils.getType(type);
IdType mtype = IdUtils.getType(membershipType);
Set idSet = idServices.getMemberships(ssoToken, idtype, name, mtype, amOrgName, amsdkDN);
if (idSet != null) {
Iterator it = idSet.iterator();
while (it.hasNext()) {
AMIdentity id = (AMIdentity) it.next();
results.add(IdUtils.getUniversalId(id));
}
}
return results;
}
To my knowledge this is not possible currently with out of box code. In case you have limited amount of groups, then privileged attribute mapping could be a way to go, but if not, then the issue gets more complicated.
You could try to change the AmRealm implementation (authenticateInternal method) to match your requirements and hook the new class into the container-specific ServiceResolver class (like http://sources.forgerock.org/browse/openam/trunk/opensso/products/j2eeagents/tomcat/v6/source/com/sun/identity/agents/tomcat/v6/AmTomcatAgentServiceResolver.java?r=700&r=700&r=700 )
You can also create a JIRA issue about providing a config property to put membership information into roles in non-UUID format.