I am making an ASP.NET intranet website that uses Active Directory and I am trying to get all the groups a user belongs to, even sub groups etc ... So that means I had to make a recursive method to load all the groups in an ArrayList following the example given here : https://www.codeproject.com/Articles/18102/Howto-Almost-Everything-In-Active-Directory-via-C
Hence I have a method to recursively fill an ArrayList based on group membership:
public ArrayList AttributeValuesMultiString(string attributeName, string objectDn,
ArrayList valuesCollection, bool recursive)
{
DirectoryEntry ldapConnection = new DirectoryEntry(objectDn);
PropertyValueCollection valueCollection = ldapConnection.Properties[attributeName];
IEnumerator en = valueCollection.GetEnumerator();
while (en.MoveNext())
{
if (en.Current != null)
{
if (!valuesCollection.Contains(en.Current.ToString()))
{
valuesCollection.Add(en.Current.ToString());
if (recursive)
{
AttributeValuesMultiString(attributeName, "LDAP://" +
en.Current.ToString(), valuesCollection, true);
}
}
}
}
ldapConnection.Close();
ldapConnection.Dispose();
return valuesCollection;
}
That I call from this other method:
public ArrayList Groups(string userDn, bool recursive)
{
ArrayList groupMemberships = new ArrayList();
return AttributeValuesMultiString("memberOf", userDn, groupMemberships, recursive);
}
Using the recursive boolean I can build my group's arraylist, but the first method can be used for other multistring object loading. Now when I test this just by a simple call to the method
//adManager is an instance of the class containing the methods above
//groups is an ArrayList
//testChain is my distinguishedName
groups = adManager.Groups(testChain, true);
foreach (var g in groups)
Console.WriteLine(g.ToString());
I have the following exception:
System.Runtime.InteropServices.COMException : Unspecified error
And the point at which I have the exception is at the assignment:
PropertyValueCollection valueCollection = ldapConnection.Properties[attributeName];
I really don't see the problem, especially that this method was recommended on another SO thread, so I'm guessing it's functional
EDIT Seems that my problem comes from an authentication issue. I added impersonation in my test code but I still get an exception :
using (HostingEnvironment.Impersonate())
{
var domainContext = new PrincipalContext(ContextType.Domain, "radiofrance.rootad.inetrf");
var groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, IdentityType.Name, "Users");
if (groupPrincipal != null)
{
groups = adManager.Groups(testChain, true);
foreach (var g in groups)
Console.WriteLine(g.ToString());
}
else
Console.WriteLine("Fail");
}
Related
I am kind of scratching my head to resolve this issue where I have to remove all the Claims in one go. When I use the below code, I get this error Collection was modified; enumeration operation may not execute
Since, I am changing/modifying the value from the enumerator that I am currently looping through, therefore, I am getting this error. Any idea how can I resolve this issue. I have used the below code:
private static readonly string[] claimTypes=
{
ClaimTypes.Role,
ClaimTypes.UserSpecific.IdleSessionTimeout,
ClaimTypes.CustomerSpecific.ApplicationId,
ClaimTypes.CustomerSpecific.CustomerId,
ClaimTypes.CustomerSpecific.CustomerName,
ClaimTypes.CustomerSpecific.CurrencyCode,
ClaimTypes.CustomerSpecific.Locale,
ClaimTypes.CustomerSpecific.TimeZone,
ClaimTypes.CustomerSpecific.ReportingUnitId
};
public static void RemoveClaimsFor(this ClaimsPrincipal principal, params string[] claimTypes)
{
if (principal == null) throw new ArgumentNullException(nameof(principal));
foreach (var identity in principal.Identities)
{
foreach (var claim in identity.FindAll(claim => claimTypes.Any(type => type == claim.Type)))
{
identity.TryRemoveClaim(claim);
}
}
}
Since this was giving error so I thought of using the below code. but this also does not work.
public static void RemoveClaimsFor(this ClaimsPrincipal principal, params string[] claimTypes)
{
if (principal == null) throw new ArgumentNullException(nameof(principal));
var claimNamedList = principal.Identities.Select(x=>x.Claims.Select(y=>y.Type).ToList());
foreach (var identity in principal.Identities)
{
var claimNameList = identity.FindAll(claim => claimTypes.Any(type => type == claim.Type));
foreach (var name in claimNameList)
{
var aa = principal.Identities.Select(x => x.Claims.Where(b => b.Type == name.Type));
identity.TryRemoveClaim(name);
}
}
}
In order to solve this, you need to create a new collection that you enumerate:
public static void RemoveClaimsFor(this ClaimsPrincipal principal, params string[] claimTypes)
{
if (principal == null) throw new ArgumentNullException(nameof(principal));
foreach (var identity in principal.Identities)
{
var claimsToRemove = identity
.FindAll(claim => claimTypes.Any(type => type == claim.Type))
.ToArray();
foreach (var claim in claimsToRemove)
{
identity.TryRemoveClaim(claim);
}
}
}
By using ToArray (or ToListor another To* method), the results of the enumeration are stored in a new collection that you can enumerate over. When removing items, this does not affect the newly created collection so the error will be gone.
I am building a ASP.NET Core website, where the user signs in, and is able to save things like the theme of the page, which then triggers some js code:
var data = {
userName: userName,
key: "theme",
value: localStorage.getItem("theme")
}
var pendingRequest;
if (pendingRequest) {
pendingRequest.abort()
pendingRequest = null;
}
pendingRequest = $.post('/Account/setExtension', data, function (data) {
pendingRequest = null;
$('#settingsModal').modal('hide');
});
which calls the controller:
[HttpPost]
public void setExtension(string userName, string key, string value)
{
Dictionary<string, object> addData = new Dictionary<string, object>
{
{key, value }
};
GraphHelper.updateExtension(userName, "com.user.roamingSettings", addData).ConfigureAwait(false);
}
which calls the checks if the extension exists, and then decides to create it, or update the existing extension:
public static async Task<bool> extensionExistsAsync(string userName, string extensionName)
{
try
{
var graphClient = GetAuthenticatedClient();
// if extension doesnt exist
bool extensionFound = false;
var extensions = await graphClient.Users[userName].Extensions.Request().GetAsync();
foreach (var extension in extensions)
{
if (extension.Id == extensionName)
{
extensionFound = true;
}
}
return extensionFound;
}
catch (Exception e)
{
Debug.WriteLine(e.Message.ToString());
throw;
}
}
The problem is, the code just stops running on this line:
var extensions = await graphClient.Users[userName].Extensions.Request().GetAsync();
It doesn't throw or anything. Stepping through it line by line, it returns all the way to the assignment, and the output window is empty when it stops. Why is this? How can I get an extension by name, or get all extensions to see which ones exist, either by graph api, or calls?
When ever you use the below call
var extensions = await graphClient.Users[userName].Extensions.Request().GetAsync();
you will be getting UserExtensionsCollectionPage object which gives the list of extensions of a user.
This page doesn't have Id property, the extension objects that are present in this UserExtensionsCollectionPage object have it. So use the below code to print the id and type of the Extensions.
var extensions = await graphClient.Users["a1ee289f-4cab-4fc3-885f-d4cbefb48895"].Extensions.Request().GetAsync();
foreach(var data in extensions.CurrentPage)
{
Console.WriteLine("Id: " + data.Id + "Type: " + data.ODataType );
}
This will give you the data as below.
Hello I have this project on Entity Framework for an exam, I have 157 test to pass and I pass 155, the last 2 are very similar to others but I dont understand why fails, I put some code so I can explain better:
public void GetUsers_OnDeletedObject_Throws() {
Site.Delete();
Assert.That(() => Site.GetUsers(), Throws.TypeOf<InvalidOperationException>()); }
I defined the Delete() method to recorsively call Delete to resources in the site(like users), then remove the site from the context.
From now on any method in the site must throw InvalidOperationException because the site doesn't exist anymore, but in some way Site.GetUsers() find a site and dont throw.
The real mistery is if I try to insert other methods (written in same way) between the delete and the assert the extra method throw InvalidOperationException!
Site.Delete() code:
using (var ctx = new SiteContext(connectionString))
{
var site = ctx.Sites.Find(Name);
foreach (var a in GetAuctions()) ctx.Auctions.Remove(a);
foreach (var s in GetSessions()) ctx.Sessions.Remove(s);
foreach (var u in GetUsers()) ctx.Users.Remove(u);
ctx.Sites.Remove(site);
ctx.SaveChanges();
}
Part of GetUsers where should throw
public IEnumerable<IUser>GetUsers()
{
using (var ctx = new SiteContext(connectionString))
{
var site = ctx.Sites.SingleOrDefault(s => s.Name == Name);
AlreadyDeleted(site);
AlreadyDeleted() code:
public static void AlreadyDeleted(object o)
{
if (null == o) throw new InvalidOperationException();
}
I'm using Domain PrincipalContext to find users groups. And I got it. But when I'm trying to work with group collection I get System.OutOfMemoryException. All Principal objects are disposable. And there is using section in my code. I had tried to use Dispose() method and GC.Collect() But it does not help.
Here is code:
using (var ctx = new PrincipalContext(ContextType.Domain, _domain, _user, _password))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx,IdentityType.SamAccountName, sAMAccountName))
{
PrincipalSearchResult<Principal> userGroups = user.GetGroups();
using (userGroups)
{
foreach (Principal p in userGroups)
{
using (p)
{
result.Add(p.Guid == null ? Guid.Empty : (Guid)p.Guid);
}
}
}
}
}
foreach loop return exception. Even foreach is empty loop.
I find that the System.DirectoryServices.AccountManagement namespace (UserPrincipal, etc.) does waste a lot of memory. For example, every time you create a UserPrincipal or GroupPrincipal, it asks AD for every attribute that has a value - even if you only ever use one of them.
If the user is a member of many, many groups, that could be the cause, although I am still surprised. Maybe your computer just doesn't have the available memory to load all that.
You can do the same thing and use less memory (and probably less time) by using the System.DirectoryServices namespace directly (which is what the AccountManagement namespace uses in the background anyway).
Here is an example that will look at the memberOf attribute of the user to find the groups and pull the Guid. This does have some limitations, which I describe an article I wrote: Finding all of a user’s groups (this example is modified from one in that article). However, in most cases (especially if you only have one domain in your environment and no trusted domains) it'll be fine.
public static IEnumerable<Guid> GetUserMemberOf(DirectoryEntry de) {
var groups = new List<Guid>();
//retrieve only the memberOf attribute from the user
de.RefreshCache(new[] {"memberOf"});
while (true) {
var memberOf = de.Properties["memberOf"];
foreach (string group in memberOf) {
using (var groupDe = new DirectoryEntry($"LDAP://{group.Replace("/", "\\/")}") {
groupDe.RefreshCache(new[] {"objectGUID"});
groups.Add(new Guid((byte[]) groupDe.Properties["objectGUID"].Value));
}
}
//AD only gives us 1000 or 1500 at a time (depending on the server version)
//so if we've hit that, go see if there are more
if (memberOf.Count != 1500 && memberOf.Count != 1000) break;
try {
de.RefreshCache(new[] {$"memberOf;range={groups.Count}-*"});
} catch (COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) break; //no more results
throw;
}
}
return groups;
}
You need to feed it a DirectoryEntry object of the user. If you know the distinguishedName beforehand, you can use that (e.g. new DirectoryEntry($"LDAP://{distinguishedName}")). But if not, you can search for it:
var ds = new DirectorySearcher(
new DirectoryEntry($"LDAP://{_domain}"),
$"(&(objectClass=user)(sAMAccountName={sAMAccountName}))");
ds.PropertiesToLoad.Add("distinguishedName"); //add at least one attribute so it doesn't return everything
var result = ds.FindOne();
var userDe = result.GetDirectoryEntry();
I notice you are also passing the username and password to PrincipalContext. If that's needed here, the constructor for DirectoryEntry does accept a username and password, so you can update this code to include that every time you create a new DirectoryEntry.
I've build a simple intranet application using ASP.NET MVC4 that allows users inside network to view some resources. Application is build mostly in ExtJS and WebAPI controllers.
My authorization is based on Active Directory.
Debugging my code I noticed that I'm doing AD calls multiple times.
Here is my AD helper class:
public class AD
{
public static string CurrentUser = "";
public static string GetCurrentUserLogin()
{
return HttpContext.Current.User.Identity.Name.Split('\\')[1];
}
public static Employee GetCurrentUser()
{
var login = CurrentUser == "" ? GetCurrentUserLogin() : CurrentUser ;
return GetUser(login);
}
public static Employee GetCurrentUser(string login)
{
return GetUser(login);
}
public static Employee GetUser(Guid guid)
{
using (var entry = new DirectoryEntry("LDAP://wawa.firma.pl/OU=IT,DC=wawa,DC=firma,DC=pl", #"AD_client", "xyzabc"))
{
using (var searcher = new DirectorySearcher(entry))
{
byte[] bytes = guid.ToByteArray();
var sb = new StringBuilder();
foreach (byte b in bytes)
{
sb.Append(string.Format(#"\{0}", b.ToString("X2")));
}
searcher.Filter = String.Format("(&(objectClass=User)(objectCategory=Person)(objectguid={0}))", sb);
searcher.PropertiesToLoad.Add("description");
searcher.PropertiesToLoad.Add("objectguid");
searcher.PropertiesToLoad.Add("SN");
searcher.PropertiesToLoad.Add("givenName");
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("manager");
searcher.PropertiesToLoad.Add("mail");
searcher.PropertiesToLoad.Add("sAMAccountName");
searcher.PropertiesToLoad.Add("telephoneNumber");
searcher.PropertiesToLoad.Add("directReports");
searcher.SizeLimit = 1;
searcher.SearchScope = SearchScope.Subtree;
try
{
SearchResult result = searcher.FindOne();
if (result == null) return null;
var emp = ParseSearchResult(result);
emp.fullAccess = true;
return emp;
}
catch (Exception)
{
return null;
}
}
}
}
}
This works fine, but every time I must get current user id and I do call like this:
var user_id = AD.GetCurrentUser().Id;
My problem is that every time I request some property for user that is stored in AD i must do new AD call.
Ideally I would like to do simple call once (for current user) and store it, then every time I need something I will get it from local object.
I read some articles about session in WebAPI controllers, for example: http://stick2basic.wordpress.com/2013/04/18/how-to-access-session-in-web-api-controller-in-mvc4/, but I found also greate example of using Cache: https://github.com/filipw/AspNetWebApi-OutputCache
But Session is per User and Cache per Application.
What would be best way of storing ActiveDirectory results per User? How should I modify my AD class to hold those variables.
The easiest way would be to store result in session as so:
public static Employee GetUser(Guid guid)
{
var e = HttpContext.Current.Session[guid.ToString()] as Employee;
if (e == null)
{
//get user from AD
//store in session
}
return e;
}
This way I'll be able to query AD only once, but I won't be able to requery every n-minutes (cache has timeout).
How can I make this easier/better? Are there other ways to do this?
You need some memory cache. Have a look at: MemoryCache Class
Store the DirectoryEntry object in the cache with the path as key.