WEB API + Cache all users from AD on application_start - asp.net

I have a WEB API which perform 2 functions
return current login user details
Also enable asyn search for any user.
In order to save performance hit on first call, i want to get all users and save them in cache on application_start. I have enabled IIS warm up, so everything should be in cache before first request
DirectoryEntry entry = new DirectoryEntry(LdapPath, activeDirectoryUserid, activeDirectoryPassword);
DirectorySearcher search = new DirectorySearcher(entry)
{
Filter = "(&(&(objectClass = user)(objectClass = person)))"
};
search.PropertiesToLoad.Add(AppConstants.AD.Name);
search.PropertiesToLoad.Add(AppConstants.AD.SamAccountName);
search.PropertiesToLoad.Add(AppConstants.AD.ThumbnailPhoto);
search.PropertiesToLoad.Add(AppConstants.AD.FirstName);
search.PropertiesToLoad.Add(AppConstants.AD.Manager);
search.PropertiesToLoad.Add(AppConstants.AD.DistinguishedName);
search.CacheResults = false;
SearchResultCollection resultset = search.FindAll();

Try using System.DirectoryServices.AccountManagement namespace.
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "yourdomain.com"))
{
UserPrincipal allUsers = new UserPrincipal(ctx);
PrincipalSearcher ps = new PrincipalSearcher(allUsers);
foreach (UserPrincipal user in ps.FindAll())
{
//USER will contain the necessary information you need
string name = user.DisplayName;
}
}

Related

How can I create an ASP.NET Identity user that can sign in?

I have an ASP.NET web application that has internal individual user accounts. I want to create those user accounts from an external program. So I have a program that references Microsoft.AspNet.Identity. In it I create users:
var userStore = new UserStore<IdentityUser>();
var um = new UserManager<IdentityUser>(userStore);
//...
var newUser = new IdentityUser(userName);
newUser.Email = userName;
var result = um.Create(newUser, "P#ssword1");
However, when I run the first application and try to sign in, SignInManager.PasswordSignInAsync always returns SignInStatus.Failure. (I'm quite sure the username and password are correct; I'm copying the username from the database and copying the password from the code.) Shouldn't it work? Am I missing something?
It turns out the culprit was IdentityUser. I don't know why (I'm guessing securitystamp has something to do with it), but you need to make ApplicationUsers. So I included a reference to the web application so I could make an ApplicationUser instead of an IdentityUser:
var userStore = new UserStore<ApplicationUser>(new ApplicationDbContext());
var um = new UserManager<ApplicationUser>(userStore);
//...
var newUser = new ApplicationUser();
newUser.Email = user.UserName;
newUser.UserName = user.UserName;
var result = um.Create(newUser, "P#ssword1");

Asp.net Identity Email Verifcation Token Not Recognized

We are using Microsoft's Identity Framework v2.0 in a web forms application. All is working well. We decided we want to add email verification as part of the new account set up process. If we validate the token after it is created in the same page, we are successful. But if we try to validate the token in a different page, it fails. The process is very simple:
Admin creates a new account by providing user's email and name. (we do not support self registration).
User clicks link he gets in email to validate the email was received.
Here is the code to create the email verification token:
var manager = new UserManager();
var user = new ApplicationUser() { UserName = EmailAddress.Text, Email = EmailAddress.Text, FirstName = FirstName.Text, LastName = LastName.Text };
IdentityResult result = manager.Create(user);
var provider = new DpapiDataProtectionProvider();
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"))
{
TokenLifespan = TimeSpan.FromHours(24)
};
var strToken = manager.GenerateEmailConfirmationToken(user.Id);
//IdentityResult validToken = manager.ConfirmEmail(user.Id, strToken);
strToken = HttpUtility.UrlEncode(strToken.ToString());
NOTE: If we uncomment the line beginning //IdentityResult validToken..., then it succeeds.
Here is the code on the VerifyEmail page:
string userid = Request.QueryString["id"].ToString();
string tokenReceived = Request.QueryString["token"].ToString();
//tokenReceived = HttpUtility.UrlDecode(tokenReceived);
ApplicationUser User = new ApplicationUser();
var manager = new UserManager();
User = manager.FindById(userid);
var provider = new DpapiDataProtectionProvider();
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"))
{
TokenLifespan = TimeSpan.FromHours(24)
};
IdentityResult validToken = manager.ConfirmEmail(User.Id, tokenReceived);
The validToken line does not succeed in this file. I have validated that the strings User.Id and tokenReceived match EXACTLY in both file, so there is no URL corruption going on. (That is why I commented out the UrlDecode since it seems to be decoded by the browser automatically - when I try to decode, it is not 100% the same as the string before encoding).
So I am certain we are calling the same method (ConfirmEmail) and that the two parameters that are passed are exactly the same strings. I am also aware that a token can only be validated once, so I am not trying to re-use them after once validating them.
Any ideas would be welcome.
I think the problem in DpapiDataProtectionProvider - If you use the same instance of this class in creating and validating the token, it'll work fine.
Any reason you are not getting UserManager from Owin Context as per VC2013 template?

How can i use an already authenticated session for google calendar?

I've implemented simple Google+ authentication on my MVC5 app and I'd like to access their google calendar. How do I do this using the MVC identity system and my already authenticated user?
Dim authGOps = New GooglePlusAuthenticationOptions() With {
.Caption = "Google+",
.ClientId = "MYCLIENTRID",
.ClientSecret = "MYCLIENTSECRET",
.Provider = New GooglePlusAuthenticationProvider() With {
.OnAuthenticated = Async Function(context)
context.Identity.AddClaim(New Claim(GooglePlusAccessTokenClaimType, context.AccessToken))
End Function
}
}
authGOps.Scope.Add("https://www.googleapis.com/auth/calendar")
app.UseGooglePlusAuthentication(authGOps)
Getting the calendar service:
Dim calendarService = New CalendarService(New Google.Apis.Services.BaseClientService.Initializer() With {
WHAT GOES HERE TO AUTHENTICATE USING MY OLD AUTH CEDENTIALS?
}
So I as well would love to use the Service as it's documented almost everywhere, but I found a workaround to at least getting the data and not having to login again.
Make sure to Nuget Json.Net to deserialize and strongly type. Otherwise you'll get a Json string to manage.
It's in C#, but I'm sure the translation won't be too difficult. Hope it helps!
var claimsIdentity = User.Identity as ClaimsIdentity;
var claims = claimsIdentity.Claims;
var accessTokenClaim = claims.FirstOrDefault(x => x.Type == GooglePlusAccessTokenClaimType);
if (accessTokenClaim != null)
{
string calendarUrl = "https://www.googleapis.com/calendar/v3/users/me/calendarList?access_token=" + Uri.EscapeDataString(accessTokenClaim.Value);
using(var client = new HttpClient())
{
var response = await client.GetAsync(calendarUrl);
var calendarList = JsonConvert.DeserializeObject<CalendarList>(await response.Content.ReadAsStringAsync());
}
}

System.DirectoryServices - The server is not operational

I get an error by a website, on which I use Windows Authentication.
Strange things:
Only occurs if user is not yet saved into database (new unknown user)
Appears only on live system, everything fine on local development environment
This is what I get in a logging mail:
Source : System.DirectoryServices
Message: The server is not operational.
Trace:
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at System.DirectoryServices.DirectorySearcher.FindOne()
at Smarthouse.Labs.DataAccess.UserListManager.SaveUser(String windowsUserName)
This is how I implement DirectorySearch:
private void SaveUser(string windowsUserName)
{
string[] domainAndUser = windowsUserName.Split('\\');
string domain = domainAndUser[0];
string username = domainAndUser[1];
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain);
DirectorySearcher search = new DirectorySearcher(entry);
try
{
// Bind to the native AdsObject to force authentication.
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("mail");
SearchResult result = search.FindOne();
if (result == null)
{
throw new Exception("No results found in Windows authentication.");
}
User userToSave = new User();
userToSave.FirstName = (String) result.Properties["givenName"][0];
userToSave.LastName = (String) result.Properties["sn"][0];
userToSave.Email = (String) result.Properties["mail"][0];
userToSave.Username = windowsUserName;
userToSave.Guid = Guid.NewGuid();
SaveUser(userToSave);
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message, ex);
}
finally
{
//Dispose service and search to prevent leek in memory
entry.Dispose();
search.Dispose();
}
}
If more code examples are needed just tell me.
Your problem is that you're using a "plain" domain name to bind - this won't work in LDAP. Actually, if you try to bind to LDAP://MyDomain, what you're really doing is trying to bind to the server called MyDomain.
You need a valid LDAP bind string - something like LDAP://dc=yourdomain,dc=local or something.
To find out what your default LDAP binding context is, use this code snippet:
DirectoryEntry deRoot = new DirectoryEntry("LDAP://RootDSE");
if (deRoot != null)
{
string defaultNamingContext = deRoot.Properties["defaultNamingContext"].Value.ToString();
}
Once you have that string - use that as your bind string to your LDAP server.
And if you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context -- no domain name needed, uses default domain
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, username);
if(user != null)
{
// do something here....
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
You can use bind strings in the format LDAP://mydomain.com:389. I kept getting "Access is Denied" when trying to use the format LDAP://DC=mydomain,DC=com. Once I switched to the LDAP://mydomain.com:389 format, and bound using the AuthenticationTypes.ServerBind flag when constructing my DirectoryEntry, it worked great. This was in Azure App Service.
To add to marc_s's answer above, I needed to search multiple domains.
So for each Domain I did the following:
DirectoryEntry deRoot = new DirectoryEntry("LDAP://" +"DomainName"+ "/RootDSE");
string defaultNamingContext = "LDAP://" + deRoot.Properties["defaultNamingContext"].Value.ToString();
DirectoryEntry mySearchRoot = new DirectoryEntry(defaultNamingContext);
DirectorySearcher myDirectorySearcher = new DirectorySearcher(mySearchRoot);
Similar Error Happened to me (though it happened all the time and not in specific cases like pointed out here) because of a wrong Active Directory connection string. i used the corp instead the prod one .
Use something that works for another app in your organization if exists.

Active directory authentication

I'm using the code below to authenticate a user in Active Directory, but the password is sending in clear text. How can I hash my password and then send it to Active Directory?
DirectoryEntry entry = new DirectoryEntry(path, username, pwd);
try
{
//Bind to the native AdsObject to force authentication.
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (null == result)
{
return false;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (string)result.Properties["cn"][0];
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message);
}
return true;
If you are using .NET 3.5, then I'd strongly recommend switching to using the System.DirectoryServices.AccountManagement namespace (read all about it: Managing Directory Security Principals in the .NET Framework 3.5).
Lots of things are a lot easier in S.DS.AM - like authenticating users:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
ctx.ValidateCredentials("test", "test", ContextOptions.SecureSocketLayer);
The only way to do this securely is by specifying the ContextOptions.SecureSocketLayer option to enforce using an SSL protected connection.
If you cannot move to .NET 3.5 and S.DS.AM, you need to check out the AuthenticationTypes that you can define in the fourth overloaded constructor of DirectoryEntry:
DirectoryEntry entry =
new DirectoryEntry(path, username, pwd,
AuthenticationTypes.SecureSocketsLayer);
There's no other way to do this, I'm afraid - I don't think there's any way for you on the client-side to hash a password the same way Windwos Server / Active Directory do it, and pass in that hashed value...

Resources