Check Active directory Group membership - spec#

How do i go about iterating a group to find out if a given user is a member of a group?
I know i can use IsInRole on WindowsPrincipal object but for some reason it don't always work for me, it doesn't error out or throw exception but just return false.
i have put together following code from web, can some help me improve it in terms of reliability, it hasn't gave any wrong result in 3 weeks of testing.
Side notes: 1: I don't have access to AD username and password hence using GC. 2: Groups can be created in any domain but with in same forest. 3: Group can have users from various domains as well as groups.
thanks
KA
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
static extern int CheckTokenMembership(int TokenHandle, byte[] PSID, out bool IsMember);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
static extern bool IsValidSid(byte[] PSID);
private bool Authenticate(XmlNodeList XmlNodeGroups)
{
bool result = false;
try
{
Dictionary<string, List<string>> Groups = GetGroups(XmlNodeGroups);
//search global catalog and get SID of the group
Byte[] sid = null;
foreach (string groupName in Groups.Keys)
{
using (DirectoryEntry entry = new DirectoryEntry("GC:"))
{
IEnumerator ie = entry.Children.GetEnumerator();
ie.MoveNext();
using (DirectorySearcher ds = new DirectorySearcher((DirectoryEntry)ie.Current))
{
ds.Filter = string.Format("(&(|(sAMAccountName={0}))(objectClass=group))", groupName);
using (SearchResultCollection resColl = ds.FindAll())
{
if (resColl.Count > 0)
{
ResultPropertyCollection resultPropColl = resColl[0].Properties;
sid = (byte[])resultPropColl["objectsid"][0];
if (sid == null || !IsValidSid(sid))
{
// log message and continue to next group continue;
}
}
else
{
// log message and continue to next group continue;
}
}
bool bIsMember = false;
if (CheckTokenMembership(0, sid, out bIsMember) == 0)
{
// log message and initiate fall back....... use Legacy
result = CheckMemberOf(XmlNodeGroups, _CurrentIdentity);
break;
}
else
{
result = bIsMember ? true : false;
if (result)
{
// debug message break;
}
else
{
// debug message
}
}
}
}
}
}
catch (Exception ex)
{
// log exception message and initiate fall back....... use Legacy
result = CheckMemberOf(XmlNodeGroups, _CurrentIdentity);
}
return result;
}</code>

Are you on .NET 3.5 ? If so, check out the MSDN magazine article Managing Directory Security Principals in the .NET Framework 3.5. It shows just how much easier things have become when it comes to users and groups in AD.
As for your requirement - you could
find the group in question
enumerate all its members
find if your given user is a member in that group
and all this can be done quite easily with the help of the System.DirectoryServices.AccountManagement namespace:
// establish a context - define a domain (NetBIOS style name),
// or use the current one, when not specifying a specific domain
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find the group in question
GroupPrincipal theGroup = GroupPrincipal.FindByIdentity(ctx, "nameOfGroup");
// recursively enumerate the members of the group; making the search
// recursive will also enumerate the members of any nested groups
PrincipalSearchResult<Principal> result = theGroup.GetMembers(true);
// find the user in the list of group members
UserPrincipal user = (result.FirstOrDefault(p => p.DisplayName == "Some Name") as UserPrincipal);
// if found --> user is member of this group, either directly or recursively
if(user != null)
{
// do something with the user
}

I tried to use your code snippet above for the 3.5 framework and this line my compiler says it's incorrect:
// find the user in the list of group members
UserPrincipal user = (result.FirstOrDefault(p => p.DisplayName == adUser) as UserPrincipal);
Specifically the result.FirstOfDefault, it says that's not a valid option.
Thanks!

Related

How to programmatically get list of users and groups that have access to a file or folder in SharePoint

I am trying to find a way to get the list of AD users and AD Groups that have access to a folder or file that has broken inheritance. I don't need to know how to find the inheritance broken, I have gotten that part but I am having issues finding each user or group that has access. I do not want to see what users are within the AD group, I just want to see the name of the group that is accessing the folder. The use case behind this is that we do not want secured folders shared to single users. All of this must be controlled by AD groups only (site owners do not have access to add users to a secure folder). Also need to find out if there are any files within the folder that do not inherit from the folder and are also shared to individual users instead of AD groups (hope this makes sense). Here is what I have so far and it works to a point, but for some reason it is returning users that have access to the site elsewhere and also users who have limited access that I have to clean up later.
Here's the code I have written so far that sort of works. It takes in the item object of the file or folder and a reference to a string. It scans the access and then builds a list of users separated by semicolon and returns true if one of the spuser objects is a user and not a group:
/// <summary>
/// Provides list of users\groups that have access to a List Item.
/// </summary>
/// <param name="spListItem">Item to check access of</param>
/// <returns>semi colon delimited list of users\groups with access in a referenced list and boolean value indicating if a direct user exists</returns>
public bool GetListItemUserAccess(SPListItem spListItem, ref string accountsWithAccess)
{
//string accountsWithAccess = string.Empty;
bool IsFirstIteration = true;
bool domainUserExits = false;
SPRoleAssignmentCollection spItemRoles = spListItem.RoleAssignments;
SPRoleDefinitionCollection rolesInWeb = spListItem.Web.RoleDefinitions;
foreach(SPRoleAssignment spRole in spItemRoles)
{
SPPrincipal spPrincipal = spRole.Member;
//cast as SPGroup or SPUser to determine if is a SPGroup or User
if((spPrincipal as SPGroup) != null)
{
SPGroup spGroup = spPrincipal as SPGroup;
SPUserCollection usersInGroup = spGroup.Users;
//report on each user in group
foreach(SPUser spUser in usersInGroup)
{
//check to see if it is a user group
if(!spUser.IsDomainGroup)
{
domainUserExits = true;
}
//add to list for report.
if(IsFirstIteration)
{
IsFirstIteration = false;
}
else
{
accountsWithAccess += ";";
}
//depending on the account type sometimes the Login name has the credentials and sometimes it has
//a UID
if (spUser.LoginName.ToLower().Contains("<company name>"))
{
accountsWithAccess += this.ParseUserIDFromClaim(spUser.LoginName);
}
else
{
accountsWithAccess += this.ParseUserIDFromClaim(spUser.Name);
}
}
}
else if((spPrincipal as SPUser) != null)
{
//check to see if the user has limited access only (we don't report on this as this occurs when user has access to something in site)
if(!spListItem.DoesUserHavePermissions(spPrincipal as SPUser, SPBasePermissions.ViewListItems))
{
continue;
}
//check to see if it is a user group
if (!(spPrincipal as SPUser).IsDomainGroup)
{
domainUserExits = true;
}
//add to list for report.
if(IsFirstIteration)
{
IsFirstIteration = false;
}
else
{
accountsWithAccess += ";";
}
//depending on the account type sometimes the Login name has the credentials and sometimes it has
//a UID
if (spPrincipal.LoginName.ToLower().Contains("<company name>"))
{
accountsWithAccess += this.ParseUserIDFromClaim(spPrincipal.LoginName);
}
else
{
accountsWithAccess += this.ParseUserIDFromClaim(spPrincipal.Name);
}
}
}
return domainUserExits;
}
So the problem is that the code is returning both the users or groups that have access to the folder, but it is also returning back other users that have limited access to the item because they have access elsewhere within the site.
I finally corrected the issue by inserting the following code:
if (spRole.RoleDefinitionBindings.Count > 1 || !spRole.RoleDefinitionBindings.Xml.ToString().Contains("Limited Access"))
{
//Process accounts
}
What this is doing is if the user has more than one roles bound to them for the list item or the one they have is not Limited Access, it will process the account. Otherwise, it is one of these "phantom accesses" that don't actually have direct access granted to the list item

How to add a new group in Active Directory using LDAP in C#

I have scenerio to create new groups in Active Directory using LDAP and C#.
Please provide the suggestions
This article on CodeProject is a really good starting point:
Howto: (Almost) Everything In Active Directory via C#
To create a group, you need to:
bind to a container where you want to create the group inside of
create the group and define some properties
Code:
public void Create(string ouPath, string name)
{
if (!DirectoryEntry.Exists("LDAP://CN=" + name + "," + ouPath))
{
try
{
// bind to the container, e.g. LDAP://cn=Users,dc=...
DirectoryEntry entry = new DirectoryEntry("LDAP://" + ouPath);
// create group entry
DirectoryEntry group = entry.Children.Add("CN=" + name, "group");
// set properties
group.Properties["sAmAccountName"].Value = name;
// save group
group.CommitChanges();
}
catch (Exception e)
{
Console.WriteLine(e.Message.ToString());
}
}
else { Console.WriteLine(path + " already exists"); }
}
Some addition info on setting the Group Scope and Group Type, the enums are:
public enum GroupType : uint
{
GLOBAL = 0x2,
DOMAIN_LOCAL = 0x4,
UNIVERSAL = 0x8,
SECURITY = 0x80000000
}
SECURITY ( Shortened from ADS_GROUP_TYPE_SECURITY_ENABLED ) is combined with the first 3 enums to give you the 6 possible options, without it a group will be a Distribution group.
The values are set as an int, which with the security flag goes into negatives, so unchecked() needs to be used.
Alternatively you could create an enum for the combined values.
GLOBAL | SECURITY = 0x80000002 = -2147483646
DOMAIN_LOCAL | SECURITY = 0x80000004 = -2147483644
UNIVERSAL | SECURITY = 0x80000008 = -2147483640
The value is stored in the 'groupType' property:
var groupType = unchecked((int)(GroupType.UNIVERSAL | GroupType.SECURITY));
group.Properties["groupType"].Value = groupType;
group.CommitChanges();
Take a look at this link: http://msdn.microsoft.com/en-us/library/ms180903(v=vs.80).aspx
I think you might be looking for this part of the code:
// Bind to the domain that this user is currently connected to.
DirectoryEntry dom = new DirectoryEntry();
// Find the container (in this case, the Consulting organizational unit) that you
// wish to add the new group to.
DirectoryEntry ou = dom.Children.Find("OU=Consulting");
// Add the new group Practice Managers.
DirectoryEntry group = ou.Children.Add("CN=Practice Managers", "group");
// Set the samAccountName for the new group.
group.Properties["samAccountName"].Value = "pracmans";
// Commit the new group to the directory.
group.CommitChanges();
I just got through solving this problem for a .NET Core 2.0 app - here is an updated solution for those using .NET Core 2.0+.
This utilizes the NuGet package System.DirectoryServices.Protocols:
try
{
string adminUsername = "myAdminUser";
string namingContext = "CN=Test123,DC=MyCompany,DC=com";
string hostNameAndSSLPort = "192.168.123.123:636";
string adminuser = $"CN={adminUsername},{namingContext}";
string adminpass = "password";
using (LdapConnection connection = new LdapConnection(hostNameAndSSLPort))
{
LdapSessionOptions options = connection.SessionOptions;
options.ProtocolVersion = 3;
options.SecureSocketLayer = true;
connection.AuthType = AuthType.Basic;
NetworkCredential credential = new NetworkCredential(adminuser, adminpass);
connection.Credential = credential;
connection.Bind();
string rolesContext = $"CN=Roles,{namingContext}";
string nameOfNewGroup = "MyGroup";
string groupDN = $"CN={nameOfNewGroup},{rolesContext}";
string dirClassType = "group";
AddRequest addRequest = new AddRequest(groupDN, dirClassType);
AddResponse addResponse = (AddResponse)connection.SendRequest(addRequest);
Console.WriteLine($"A {dirClassType} with a dn of\n {groupDN} was added successfully. The server response was {addResponse.ResultCode}");
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Also there are a lot of great code examples in this sample project provided by Microsoft.

Check active directory group membership recursively

So I have a question regarding recursive groups in active directory. I have a little method that checks if a user id is in a group or not. Works great. Found out today that it doesn't check recursive group membership and I'm not too sure how (or if) there is a way to do that. Here's what I have so far for non-recursive:
public static bool CheckGroupMembership(string userID, string groupName, string Domain)
{
bool isMember = false;
PrincipalContext ADDomain = new PrincipalContext(ContextType.Domain, Domain);
UserPrincipal user = UserPrincipal.FindByIdentity(ADDomain, userID);
if (user.IsMemberOf(ADDomain, IdentityType.Name, groupName.Trim()))
{
isMember = true;
}
return isMember;
}
I've seen some things about a directory searcher or something but I'm somewhat new to working directly with AD and while I understand the concepts, some other things are still a little lost on me.
Thanks!
You can also check by using the recursive option of GroupPrincipal.GetMembers.
public static bool CheckGroupMembership(string userID, string groupName, string Domain) {
bool isMember = false;
PrincipalContext ADDomain = new PrincipalContext(ContextType.Domain, Domain);
UserPrincipal user = UserPrincipal.FindByIdentity(ADDomain, userID);
GroupPrincipal group = GroupPrincipal.FindByIdentity(ADDomain, groupName);
if ((user != null) && (group != null)) {
isMember = group.GetMembers(true).Contains(user);
}
return isMember;
}
Here is a solution using System.DirectoryServices.AccountManagement Namespace. It's a kind of recursive solution. In Find Recursive Group Membership (Active Directory) using C#, I give a recursive solution that also works with distribution groups.
/* Retreiving a principal context
*/
Console.WriteLine("Retreiving a principal context");
PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "WM2008R2ENT:389", "dc=dom,dc=fr", "jpb", "PWD");
/* Look for all the groups a user belongs to
*/
UserPrincipal aUser = UserPrincipal.FindByIdentity(domainContext, "user1");
PrincipalSearchResult<Principal> a = aUser.GetAuthorizationGroups();
foreach (GroupPrincipal gTmp in a)
{
Console.WriteLine(gTmp.Name);
}

Is it possible to allow users of a SharePoint blog to comment, but not create posts?

By exploring the permissions, there seems to be no distinction between a comment and a blog post.
I can set users as contributers which allows them to comment and means their posts require apporval. But I would like to stop them from being able to create a post even as draft.
Many thanks
I've cracked it!
On the main page there is an option to manage posts. Within that page there is an option to set permissions. Perfect.
A blog within a My Site is created using the My Site Blogs feature (863DA2AC-3873-4930-8498-752886210911). Inside the feature receiver is the following code that modifies the Comments list by setting edit access to only their own, breaks role inheritance, and grants contribute access to the Visitors group:
int num2;
SPList list3;
SPRoleDefinition byType;
SPRoleAssignment assignment;
UserProfileManager manager;
string str2;
string[] strArray2;
int num3;
string str = parent.RootWeb.AllProperties["vti_associatevisitorgroup"] as string;
SPGroup principal = null;
if (!string.IsNullOrEmpty(str))
{
num2 = int.Parse(str, CultureInfo.InvariantCulture);
principal = parent.RootWeb.SiteGroups.GetByID(num2);
}
list3 = GetList(web, SPListTemplateType.Comments);
list3.WriteSecurity = 2;
byType = web.RoleDefinitions.GetByType(SPRoleType.Contributor);
list3.BreakRoleInheritance(true);
web.AllowUnsafeUpdates = true;
if (principal == null)
{
manager = new UserProfileManager(ServerContext.GetContext(parent));
strArray2 = manager.PersonalSiteReaders.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
num3 = 0;
while (num3 < strArray2.Length)
{
str2 = strArray2[num3];
try
{
SPRoleAssignment roleAssignment = new SPRoleAssignment(str2, null, null, null);
roleAssignment.RoleDefinitionBindings.Add(byType);
list3.RoleAssignments.Add(roleAssignment);
}
catch (Exception exception)
{
ULS.SendTraceTag(ULSTagID.tag_7otc, ULSCat.msoulscat_SPS_UserProfiles, ULSTraceLevel.Medium, "Ignored one invalid user for the personal site reader (%s): %s.", new object[] { str2, exception });
}
num3++;
}
}
else
{
assignment = new SPRoleAssignment(principal);
assignment.RoleDefinitionBindings.Add(byType);
list3.RoleAssignments.Add(assignment);
}
list3.Update();
ULS.SendTraceTag(ULSTagID.tag_6y3j, ULSCat.msoulscat_SPS_UserProfiles, ULSTraceLevel.Medium, "Successfully activated MySite Blog Feature");
I would probably write a custom Feature Receiver that does something similar. However, if I only needed a single blog in the site collection and a URL of Blog was acceptable, I might try creating the blog by activating the My Site Blogs feature.

any way to get sharepoint groups with a given site url

I'm getting a stuck. Suppose that I create 5 sites. For each one, I create a few sharepoint groups. So, I create a dropdownlist control to bind 5 sites and when I click on any site there, I will get sharepoint groups created on it. But I always see the dropdownlist used to bind these groups still never change. I mean it only binds a few default groups in sahrepoint everytime. The new created groups is not.
And I have confusion like this
web.AssociatedGroups
web.Groups
web.SiteGroups
which one we will use this case ? Please guide me
Here my snippet code
private void BindSPGroupsToDropDownList(DropDownList ddl, string siteUrl)
{
ddl.Items.Clear();
try
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(siteUrl))
{
using (SPWeb web = site.OpenWeb())
{
//web.SiteGroups
foreach (SPGroup spGroup in web.Groups)
{
ddl.Items.Add(new ListItem(spGroup.Name.Trim(), spGroup.ID.ToString()));
}
}
}
});
}
}
thanks in advance
You don't show how you add groups, so it is hard to see why you can't list them.
Some considerations:
groups are scoped at the site collection level, i.e. there is no notion of groups at the web (SPWeb) level; only at the SPSite level
I wrote code a couple of years ago for managing groups, so I don't remember the difference between the different properties. Looking at my code, I used SPWeb.SiteGroups to test if a group exists and to add a new group.
Here is my - slightly anonymized - code:
public static void CreateSiteGroup(SPSite site, string strGroupName, string strGroupDesc, string strGroupOwner) {
if (site == null) {
string message = GetMessagePrefix() + " Site is null";
XxxLog.Error(XxLogEventId.Common, message, XxxLogCategory.CommonBusiness);
} else if (String.IsNullOrEmpty(strGroupName)) {
string message = GetMessagePrefix() + " The group name is empty";
XxxLog.Error(XxxLogEventId.Common, message, XxxLogCategory.CommonBusiness);
} else {
try {
using (SPWeb rootWeb = site.RootWeb) {
SPMember owner;
if (String.IsNullOrEmpty(strGroupOwner)) {
owner = rootWeb.CurrentUser;
} else {
if (!ContainsGroup(site, strGroupOwner)) {
string message = GetMessagePrefix() + " Can not find owner group name: " + strGroupOwner;
XxxLog.Error(XxxLogEventId.Common, message, XxxLogCategory.CommonBusiness);
return;
} else {
owner = rootWeb.SiteGroups[strGroupOwner];
}
}
if (!ContainsGroup(site, strGroupName)) {
rootWeb.SiteGroups.Add(strGroupName,
owner,
null, // no default user
strGroupDesc);
} else {
string message = GetMessagePrefix() + " The group " + strGroupName + " was already present";
XxxLog.Info(message, XxxLogCategory.CommonBusiness);
}
}
} catch (Exception e) {
string message = GetMessagePrefix() + " Cannot create " + strGroupName + " group";
XxxLog.Error(XxxLogEventId.Common, message,e, XxxLogCategory.CommonBusiness);
}
}
}
public static Boolean ContainsGroup(SPSite site, string name) {
SPGroup group = null;
using (SPWeb rootWeb = site.RootWeb) {
foreach (SPGroup g in rootWeb.SiteGroups) {
if (g.Name.ToUpper().Equals(name.ToUpper())) {
group = g;
break;
}
}
}
return (group != null);
}

Resources