Directory Seacher issue, how to get primary SMTP only? - c#

I have the below snippet from my authentication login page
How can I modify it so that it takes the Primary SMTP mail address from AD? Currently it appears that it takes the first one that it finds.
Is there a property to only get the primary mail address?
adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")";
adsSearcher.PropertiesToLoad.Add("mail");
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
bool bSucceeded = true;
string strAuthenticatedBy = "Active Directory";
string strError = "User has been authenticated by Active Directory.";
adsEntry.Close();
try
{
if (adsSearchResult.Properties["mail"] != null && adsSearchResult.Properties["mail"].Count > 0)
{
Session["email"] = adsSearchResult.Properties["mail"][0].ToString().ToLower();
}

The best way to do this is actually to retrieve the proxyAddresses attribute. Each entry in that attribute is prefixed with a protocol (e.g. SIP:, SMTP:, etc.). The proxy with the capitalized prefix (e.g. SMTP: vs smtp:) is the primary address for that protocol. The mail attribute /should/ be in sync but it's not always the case.

Related

Verfiy a SMTP address is associated with a mailbox

Is there any easy/elegant way to verify, that a SMTP address is associated with an Exchange mailbox via EWS?
Credentials of an administrative Exchange user are given.
The following quick and dirty solution works:
Create an ExchangeService with the SMTP address as Impersonated user
Try to make a method call
When the exception message is "The SMTP address has no mailbox associated with it.", the SMTP address is not associated with a mailbox
Problem:
Not that elegant to compare the exception message
Takes about 200-250ms per user (not parallel)
Another Problem:
It is possible, that there are contacts in the GAL, that don't have a mailbox.
So ResolveNames won't be helpful.
The easiest way is to use the ResolveName operation and search the Directory you just need to prefix the address with smtp: and Exchange will the search both the Primary and proxyaddressses eg
String EmailAddresstoCheck = "info#domain.com";
NameResolutionCollection ncCol = service.ResolveName(("SMTP:" + EmailAddresstoCheck), ResolveNameSearchLocation.DirectoryOnly, true);
if (ncCol.Count == 1)
{
if (ncCol[0].Contact != null)
{
if (EmailAddresstoCheck.ToLower() == ncCol[0].Mailbox.Address)
{
Console.WriteLine("Primary SMTP Address of " + ncCol[0].Contact.DisplayName);
}
else
{
Console.WriteLine("Proxy Address of " + ncCol[0].Contact.DisplayName);
Console.WriteLine("Primary SMTP Address : " + ncCol[0].Mailbox.Address);
}
}
}
Cheers
Glen

Using the TFS 2012 API, how do I get the email address of a user?

I am trying to get the email address of a particular user in TFS 2012 using the API. I have set the users Preferred Email address in the Profile section. I have done plenty of searching online and have the following code.
var userId = "myUserId";
var collection = new TfsTeamProjectCollection(tfsUri, tfsCerd);
var managementService = collection.GetService<IIdentityManagementService>();
var member =
managementService
.ReadIdentity(
IdentitySearchFactor.AccountName,
userId,
MembershipQuery.Direct,
ReadIdentityOptions.ExtendedProperties);
var emailAddress = member.GetAttribute("Mail", null)
This code is both a success and a failure. It is a success in that it successfully retrieves the specified user; however, the problem is that the Email attribute is blank. When I analyzed the member variable, I noticed the "Mail" attribute was listed there and it was empty. I then noticed there were two other attributes called "ConfirmedNotificationAddress" and "CustomNotificationAddress" that had my preferred email address correctly in there.
I am wondering why I can't seem to get the "Mail" variable to load properly with the preferred email address as I will need this code to work on a lot of peoples servers.
Try using Mail instead of Email for the attribute name - that works for me.
Also, if that doesn't work, check the results of member.GetProperties() - maybe that will give you the right name to use.
For me, GetProperty("Mail") also worked.
I bumped into the same problem, I found a work around by getting my users email address from AD using the following code.
public string GetUserEmail(string username)
{
using (var pctx = new PrincipalContext(ContextType.Domain))
{
using (UserPrincipal up = UserPrincipal.FindByIdentity(pctx, username))
{
return up != null && !string.IsNullOrEmpty(up.EmailAddress) ? up.EmailAddress : string.Empty;
}
}
}
But then I found that it would throw an exception when my user was not in my domain. So this code helped me have an a second source. If I didn't find in AD i would go and use the IdentityManagementService.
public TeamFoundationIdentity GetUserByAccountName(string account)
{
var ims = _tfServer.GetService<IIdentityManagementService>();
return ims.ReadIdentity(IdentitySearchFactor.DisplayName, account, MembershipQuery.Expanded, ReadIdentityOptions.ExtendedProperties);
}
Then I would simply use this execution.
var ownerMail = GetUserEmail(checkinEvent.Resource.CheckedInBy.DisplayName);
if (string.IsNullOrEmpty(ownerMail))
{
ownerMail = GetUserByAccountName(checkinEvent.Resource.CheckedInBy.DisplayName).GetProperty("Mail").ToString();
}

Why does open OpenLDAP require a cn=username?

I'm connecting to OpenLDAP with C#, and when I pass in my username and password, I have to pass them into my LdapConnection object as cn=Username, Password. If I just pass in username and password my call to Bind fails. Why do I have to do that? Is something misconfigured on my OpenLDAP server?
It's just a byproduct of the implementation. Novell's eDirectory solution takes a very similar approach, and I use the same Novell.Directory.Ldap code to handle bind requests to both eDirectory and OpenLDAP. Now obviously, the users themselves shouldn't have to enter their entire CN when authorizing - we can just issue a search for them, based of thier UID :
//Setup the initial bind for the admin user
var lc = new LdapConnection();
lc.SecureSocketLayer = SSL;
lc.UserDefinedServerCertValidationDelegate += delegate { return true; };
lc.Connect(ServerName, Port);
lc.Constraints.TimeLimit = Timeout;
lc.Bind(AdminUsername, AdminPassword);
Now I just filter for the user, and bind using their distinguished name, or full container name (CN) :
//Ex. (uid=jsmith)
string filter = config.LdapAuth.LdapFilter.Replace("{{uid}}", username);
//Find the user we're trying to authorize
var lsc = lc.Search(config.LdapAuth.LdapDomain, LdapConnection.SCOPE_SUB, filter, null, false);
if (lsc.hasMore())
{
LdapEntry nextEntry = lsc.next();
//Check the Entries DN so we can properly bind
lc.Bind(nextEntry.DN, Password);
}
This was the most widely used approach I could find, and it's worked quite well so far.

Determine if mailbox exists using Exchange Web Services API

Using the Exchange Web Services API, is it possible to determine whether a mailbox/e-mail address such as someone#mydomain.com exists within an organization?
If so, which is the simplest way to do this and is it possible without the use of impersonation?
Case: A Windows Service regularly sends e-mails to people within the organization. It does not have any explicit knowledge about their e-mail adresses. It only knows their username and assumes that their e-mail address is username#mydomain.com. This is true for all users except for a few that do not have mailboxes. In these cases, it should not attempt to send the e-mail in the first place.
Solution:
As suggested by mathieu: look for user and e-mail address in Active Directory instead. This function gets the job done:
using System.DirectoryServices.AccountManagement;
// ...
public static bool TryGetUserEmailAddress(string userName, out string email)
{
using (PrincipalContext domainContext =
new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
using (UserPrincipal user =
UserPrincipal.FindByIdentity(domainContext, userName))
{
if (user != null && !string.IsNullOrWhiteSpace(user.EmailAddress))
{
email = user.EmailAddress;
return true;
}
}
email = null;
return false; // user not found or no e-mail address specified
}
Determining if an user has a mailbox with EWS only could be more complicated than expected, especially without impersonation.
If you're in an Active Directory domain, you should rely on the DirectoryEntry information to determine the mailbox of an user, and send email accordingly. If you got your user login, it's really easy to get the associated DirectoryEntry.
there is an easy way to do it by checking the user availability like the following code.
I tried this and it is working for me.
I am not sure about other cases when availability result returns error but for sure when the email is not right it does
to define your exchange service refer to this: https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/get-started-with-ews-managed-api-client-applications
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);//You version
service.Credentials = new WebCredentials("user1#contoso.com", "password");
service.AutodiscoverUrl("user1#contoso.com", RedirectionUrlValidationCallback);
string email = "TEST#YOUR.COM";
// Get User Availability after 6 months
AttendeeInfo attendee = new AttendeeInfo(email);
var attnds = new List<AttendeeInfo>();
attnds.Add(attendee);
var freeTime = service.GetUserAvailability(attnds, new
TimeWindow(DateTime.Now.AddMonths(6), DateTime.Now.AddMonths(6).AddDays(1)), AvailabilityData.FreeBusyAndSuggestions);
//if you receive result with error then there is a big possibility that the email is not right
if(freetimes.AttendeesAvailability.OverallResult == ServiceResult.Error)
{
return false;
}
return true;

Validating a user's credentials remotely

I currently use LogonUser() to authenticate my user's username and password on my local domain at the office and it works great for what i need it to do.
Since I developed the app I now need to make it work over my VPN. It seems LogonUser() will not work with REMOTELY validating credentials. Or will it? Is it possible to use LogonUser() to validate a user's credentials on a REMOTE domain account?
I have read in some places that using LOGON32_LOGON_NEW_CREDENTIALS for the 4th param (login type) and LOGON32_PROVIDER_WINNT50 for the 5th param (provider) would do the trick. But every time I try that I ALWAYS get success... I can supply a bogas user and pass and it will work every time :(.
Ideas?
Edit - Added Notes
Tried to use this function but I kept getting the exception telling me the user/pass was bad.
public bool Win2kCredentialsIsValid(string domain, string username, string password)
{
string adPath = "LDAP://" + domain + "/rootDSE";
DirectoryEntry adRoot = new DirectoryEntry(adPath, domain + "\\" + username, password, AuthenticationTypes.ReadonlyServer);
try
{
object o = adRoot.Properties["defaultNamingContext"];
}
catch
{
return false;
}
return true;
}
--
Edit - Added More Notes
OK so I tried yet another example just to get it to work and started down this path, and there are a few things to note...
MyServerHostName is exactly that, my server's hostname. EX: 'Server01'.
My domain name in this example is 'MyDomain.local'
So that makes my FQN for the server 'Server01.MyDomain.local'
I tried to make this work and got the following error...
The supplied context type does not match the server contacted. The server type is Domain.
This errored out at : var context = new PrincipalContext(ContextType.ApplicationDirectory, "MyServerHostName:389", "DC=MyDomain,DC=local"))
private bool CheckADCredentials()
{
bool bResults;
using (var context = new PrincipalContext(ContextType.ApplicationDirectory,
"MyServerHostName:389",
"DC=MyDomain,DC=local"))
{
var username = "firstname.lastname";
var email = "firstname.lastname#MyServerHostName";
var password = "123456";
var user = new UserPrincipal(context)
{
Name = username,
EmailAddress = email
};
user.SetPassword(password);
user.Save();
if (context.ValidateCredentials(username, password, ContextOptions.SimpleBind))
{
bResults = true;
}
else
{
bResults = false;
}
user.Dispose();
}
return bResults;
}
I ended up going with a different solution. Instead of trying to validate a user's account on a domain that my PC was not connected to I ended up caching my domain credentials in the database and just built a salted MD5 type encrypt function so it would make it hard .. er.. for someone to crack it. ;)
Now I just validate against cached credentials in the database when working remotely... It just required the user to first login on the domain but then the user can use it remotely day and night. ;)
Thanks!

Categories

Resources