Check active directory group membership recursively - c#-4.0

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);
}

Related

Searching managed navigation termset

Is there a way to search/find a specific managed navigation term by guid using server object model
This code
var termGuid = new Guid(fieldValue.TermGuid);
var navTermSet = TaxonomyNavigation.GetTermSetForWeb(SPContext.Current.Web, StandardNavigationProviderNames.GlobalNavigationTaxonomyProvider, true);
if (navTermSet == null) return String.Empty;
var navTerm = navTermSet.Terms.Where(c => c.Id == termGuid).FirstOrDefault();
if (navTerm == null) return String.Empty;
return navTerm.GetWebRelativeFriendlyUrl();
only works if the term is in the first level, if its deeper down in hierarchy it doesn't find it and I'm not seeing any other mechanism that gets me a flat list of terms
It looks like a severe limitation with API
When you're looking for a certain term and you don't know how deep this resides and wether it's a parent or child, this becomes more complicated. If the API has no direct way of getting all the terms, regardless wether they're child or parent, I guess your best option is to list them all yourself by looping all terms + children and then find them by Guid.
Below code can be used to list all terms. The code is taken and shortened for your ease from this article.
List<Term> allTerms = new List<Term>();
void BuildTermList()
{
SPSite thisSite = SPContext.Current.Site;
TaxonomySession session = new TaxonomySession (thisSite);
foreach (TermStore termStore in session.TermStores)
{
foreach (Group group in termStore.Groups)
{
foreach (TermSet termSet in group.TermSets)
{
foreach (Term term in termSet.Terms)
{
AddTermSet(term);
}
}
}
}
}
void AddTermSet(Term term)
{
allItems.Add(term);
foreach (Term t in term.Terms)
AddTermSet(t);
}
Now that you have the list you can query it. Hope this helps.

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.

How do I load BDC data from a BDC URL?

I am doing this in SharePoint 2010, but wouldn't be surprised if the problem exists in SharePoint 2007 and has the same solution.
I have a runtime security trimmer on my BDC data. I was expecting the security trimmer to give me URLs based off of the "default" profile URL defined in the model. Unfortunately, that is not the case. It gives me a URL like:
bdc3://amms_amms/default/00000000%252d0000%252d0000%252d0000%252d000000000000/1971/amms/1973?s_id=ibqaaaaaaaaa=&s_ce=07nk0004080g10003o03vvf
I need to get the properties of this object (really just the primary key value). Any idea how I do this with the BDC object model? The following link seems to provide some help, but I haven't seen anything that consumes the URL above.
http://msdn.microsoft.com/en-us/library/ee556400.aspx
Update: I see that SharePoint 2007 has an AccessChecker (http://msdn.microsoft.com/en-us/library/aa981124.aspx) and 2010 likely has this as well (can't find good documentation for 2010 on this). We can't easily have security descriptors in the database, but the AccessChecker method might suffice.
Digging a little further I see that Microsoft.Office.Server.Search.Connector.BDC.BdcSecurityTrimmer is what is likely what is used by the AccessChecker in SharePoint 2010. It appears that this does a query to the DB per URL. Seems inefficient even if it does do it on multiple threads (which the 2007 documentation claims to do). I think I would prefer to batch up the information into a single web service call, but am on the fence...
OK, here's a simplification of my previous answer. It appears that you can totally avoid reflection:
using Microsoft.BusinessData.Runtime;
using Microsoft.Office.Server.Search.Connector;
using Microsoft.Office.Server.Search.Query;
private string[] GetIds(IList<string> documentCrawlUrls)
{
string[] ids = new String[documentCrawlUrls.Count];
for (int i = 0; i < documentCrawlUrls.Count; i++)
{
try
{
string url = documentCrawlUrls[i];
string id = new Microsoft.Office.Server.Search.Connector.UriParser(new Uri(url)).QueryStringParameters["s_id"];
ids[i] = Identity.Deserialize(id).GetIdentifierValues()[0].ToString();
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("Error: " + ex.Message);
}
}
return ids;
}
Note that I tried avoiding using the UriParser from Microsoft.Office.Server.Search.Connector using code like:
string id = HttpUtility.ParseQueryString(new Uri(url).Query)["s_id"];
ids[i] = Identity.Deserialize(id.ToUpper()).GetIdentifierValues()[0].ToString();
Unfortunately, this worked for some Id's and not others. I decided not to investigate any further and just use the special UriParser. In one example the ids I was looking for were "5,20,21,7,8,6,14,19,17,18,4" but this second approach gave me "5,20,21,24581,8,24580,24588,24593,17,24592,4". That messed me up for a few minutes since the first 3 were correct.
I'm not sure it is the best approach, but I got this to work by using Reflector to reverse engineer Microsoft.Office.Server.Search.Connector.BDC.BdcSecurityTrimmer. I only needed the identity value so that simplified this a little.
Below is my code that takes an array of documentCrawlUrls provided to the security trimmer and translates them into an array of primary keys as defined in my BDC model file. Once I have those I can determine the security trimming using more custom .NET code.
In CheckAccess() of my security trimmer (ISecurityTrimmer2) I have:
String[] ids = GetIds(documentCrawlUrls);
Then I have the following private method:
private string[] GetIds(IList<string> documentCrawlUrls)
{
string[] ids = new String[documentCrawlUrls.Count];
for (int i = 0; i < documentCrawlUrls.Count; i++)
{
try
{
string url = documentCrawlUrls[i];
Identity identity = null;
IEntity entity = null;
ILobSystemInstance lsi = null;
ParseUri(url, out entity, out identity, out lsi);
if (identity != null)
{
object[] values = identity.GetIdentifierValues();
if (values.Length > 0)
{
ids[i] = values[0].ToString();
}
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("Error: " + ex.Message);
}
}
return ids;
}
I didn't want to rewrite the SPBdcUri class and it is internal, so I cheat with reflection. I currently only use one of the out parameters so I can improve on efficiency. I may re-write the portions of SPBdcUri that I need instead of resorting to reflection.
private void ParseUri(string crawlUri, out IEntity entity, out Identity identity, out ILobSystemInstance lsi)
{
//SPBdcUri uri = new SPBdcUri(new Uri(crawlUri));
AssemblyName assemblyName = new AssemblyName("Microsoft.Office.Server.Search.Connector, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
Assembly assembly = Assembly.Load(assemblyName);
Type spBdcUriType = assembly.GetType("Microsoft.Office.Server.Search.Connector.BDC.SPBDC.SPBdcUri");
object uri = Activator.CreateInstance(spBdcUriType,
BindingFlags.NonPublic | BindingFlags.Instance,
null, new object[] { new Uri(crawlUri) }, System.Globalization.CultureInfo.CurrentCulture);
//uri.DoOverrideBDCThrottlingLimits = false;
spBdcUriType.InvokeMember("DoOverrideBDCThrottlingLimits",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
null, uri, new object[] { false });
//entity = uri.Entity;
object entityObj = spBdcUriType.InvokeMember("Entity",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty,
null, uri, null);
entity = (IEntity)entityObj;
//identity = uri.Identity;
object identityObj = spBdcUriType.InvokeMember("Identity",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty,
null, uri, null);
identity = (Identity)identityObj;
//lsi = uri.LobSystemInstance;
object lsiObj = spBdcUriType.InvokeMember("LobSystemInstance",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty,
null, uri, null);
lsi = (ILobSystemInstance)lsiObj;
}
Oh, here's my "using" statements:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.BusinessData.MetadataModel.Collections;
using Microsoft.BusinessData.MetadataModel;
using Microsoft.BusinessData.Runtime;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.BusinessData.SharedService;
using Microsoft.Office.Server.Search.Query;

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.

Check Active directory Group membership

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!

Resources