VSTO - How get account email address from Outlook.Store entity - c#

Some time ago to get Outlook accounts and account info (e.g. Email address, SMTP address) i was use Outlook.Accounts entity, but Outlook.Accounts caches data and doesn't support events like Add/Remove. Here I was offered to switch to Outlook.Stores (Outlook.Store) entity, but I don’t understand how I can get the Email address from Outlook.Store at least.

If the store is associated with any account configured in Outlook you can use the following code which iterates over all accounts configured and finds the required one where you may ask for an email address:
Outlook.Account GetAccountForFolder(Outlook.Folder folder)
{
// Obtain the store on which the folder resides.
Outlook.Store store = folder.Store;
// Enumerate the accounts defined for the session.
foreach (Outlook.Account account in Application.Session.Accounts)
{
// Match the DefaultStore.StoreID of the account
// with the Store.StoreID for the currect folder.
if (account.DeliveryStore.StoreID == store.StoreID)
{
// Return the account whose default delivery store
// matches the store of the given folder.
return account;
}
}
// No account matches, so return null.
return null;
}
The Account.SmtpAddress property returns a string representing the Simple Mail Transfer Protocol (SMTP) address for the Account. The purpose of SmtpAddress and Account.UserName is to provide an account-based context to determine identity. If the account does not have an SMTP address, SmtpAddress returns an empty string.

Generally, stores do not have an intrinsic identity - imagine a standalone PST store: there is no user identity associated with it. Or you can have multiple POP3/SMTP accounts delivering to the same PST store - you now have multiple identities associated with the PST store.
Or imagine having a PF store - it is accessible to multiple users without having its own identity.
Only Exchange stores have a notion of an owner. You can go from an Exchange store to an email account by looping through the Namespace.Accounts collection and comparing (using Namespace.CompareEntryIDs) the entry id of your store in question and the store exposed by the Account.DeliveryStore property.
If using Redemption is an option (I am its author), it exposes the Exchange mailbox owner directly through the RDOExchangeMailboxStore.Owner property (returns RDOAddressEntry object).

Related

How to integrate LDAP in WPF application that consumes WCF service

I will start by describing how my application works today without LDAP.
I have WPF application that consumes WCF services (authentication windows or UserName depends on users choice). This services allows communication with database.
I display to user a "Login screen" in order to allow him set her "user name" and "password" then application connects to service and consumes function that checks if UserName and Password exist in database. (see img below)
Now I need also to integrate LDAP for authenticating user accounts against their existing systems instead of having to create another login account.
I'm bit ignorant about LDAP and confused about many things. Please excuse the possible use of wrong terminology.
I googled but I still don't have answers of many questions.
1- What is the relation between users that exist in my database table "User" and profiles that I should be created in LDAP ?
2- What is the check I should do to allow user come from LDAP to access to my application and use all functionnalities of my service ?
3- Should I have service type "LDAP" like other authentications types I have today in my application ("Windows" and "UserName") ?
4- If I want to update my application architecture described in picture above where should I add LDAP ?
First I am going to answer your questions one by one,
The user on LDAP is the same on DB, you can hold LDAP's Username and it's domain in your Users Table,
but the profile on the LDAP may vary with your profile table, but it can be fetched from LDAP address.
It's enough to check username and password over LDAP, just need to hold LDAP addresses in a Table (example ExternalPath) and make a relation between User and ExternalPath tables. LDAP address is contains some specifications.
Yes, you have to have a separate mechanism for identifying LDAP Users which I will explain more further.
This is not hard if everything be atomic and designed right, in further steps you may see it is easy.
So let me tell about my experience in LDAP and Authenticate users on LDAP and DB and our architecture.
I was implemented a WCF service named Auth.svc, this service contains a method named AuthenticateAndAuthorizeUser this is transparent for user which came from LDAP or anywhere.
I hope you get the clue and architecture to Authenticate user over LDAP and DB in below steps:
1- First I have a table named Users which hold users info and one more field named ExternalPath as foreign key, if it is null specify UserName is in DB wit it's password otherwise it is came from UserDirectory.
2- In second step you have to hold LDAP address (in my case LDAP addresses are in ExternalPath table), all LDAP addresses are on port 389 commonly.
3- Implementing authenticate User, if is not found(with Username and Password) then check it's ExternalPath to verify over LDAP address.
4- The DB schema should be something like below screenshot.
As you can see ExternalPath field specify user is from LDAP or not.
5- In presentation layer I am defining LDAP servers like below screenshot also
6- In the other side while adding new user in system you can define LDAP for user in my case I am listing LDAP titles in a DropDown in adding User form (if admin select LDAP address then don't need to get password and save it in DB), as I mentioned just need to hold LDAP username not password.
7- But last thing is authenticating user on LDAP and DB.
So the authenticate method is something like:
User userLogin = User.Login<User>(username, password, ConnectionString, LogFile);
if (userLogin != null)
return InitiateToken(userLogin, sourceApp, sourceAddress, userIpAddress);
else//Check it's LDAP path
{
User user = new User(ConnectionString, LogFile).GetUser(username);
if (user != null && user.ExternalPath != null)
{
LDAPSpecification spec = new LDAPSpecification
{
UserName = username,
Password = password,
Path = user.ExternalPath.Path,
Domain = user.ExternalPath.Domain
};
bool isAthenticatedOnLDAP = LDAPAuthenticateUser(spec);
}
}
If userLogin does not exist in DB by entered UserName and Password then we should authenticate it over related LDAP address.
In else block find User from Users table and get it's ExternalPath if this field was not null means User is on LDAP.
8- The LDAPAuthenticateUser method is :
public bool LDAPAuthenticateUser(LDAPSpecification spec)
{
string pathDomain = string.Format("LDAP://{0}", spec.Path);
if (!string.IsNullOrEmpty(spec.Domain))
pathDomain += string.Format("/{0}", spec.Domain);
DirectoryEntry entry = new DirectoryEntry(pathDomain, spec.UserName, spec.Password, AuthenticationTypes.Secure);
try
{
//Bind to the native AdsObject to force authentication.
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + spec.UserName + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (null == result)
{
return false;
}
}
catch (Exception ex)
{
Logging.Log(LoggingMode.Error, "Error authenticating user on LDAP , PATH:{0} , UserName:{1}, EXP:{2}", pathDomain, spec.UserName, ex.ToString());
return false;
}
return true;
}
If exception raised in LDAPAuthenticateUser means User does not exist in User Directory.
The authentication code accepts a domain, a user name, a password, and a path to the tree in the Active Directory.
The above code uses the LDAP directory provider the authenticate method calls LDAPAuthenticateUser and passes in the credentials that are collected from the user. Then, a DirectoryEntry object is created with the path to the directory tree, the user name, and the password. The DirectoryEntry object tries to force the AdsObject binding by obtaining the NativeObject property. If this succeeds, the CN attribute for the user is obtained by creating a DirectorySearcher object and by filtering on the SAMAccountName. After the user is authenticated and exception not happened method returns true means user find on given LDAP address.
To see more info about Lightweight Directory Access Protocol and authenticate over it THIS Link can be useful which tells about specification more.
Hope will help you.

EWS: Mailbox address from a Calendar FolderId, is it possible?

I have stored Calendar FolderIds in order report on calendar events which a user has access to. I'm using Exchange Web Services via c# (using Microsoft.Exchange.WebServices)
These calendars can be associated with the authenticated user's mailbox, a shared mailbox, an impersonated|delegated mailbox, or a public mailbox. And now I'd like to go from the FolderId to the mailbox address.
Ideally there'd be a function to
string address = getMailboxAddress(new FolderId("AAJk...AA="));
If you have the FolderId what you have is the EWS version of this https://msdn.microsoft.com/en-us/library/ee217297(v=exchg.80).aspx which means with the data you have doesn't contain the Email Address. You would be better at the time you store the CalendarId store the Email address its associated with.
You can try using ConvertId with a generic non resolvable email Address this should return the EmailAddress the folder belongs (won't work for Public Folder) to eg
AlternateId aiAlternateid = new AlternateId(IdFormat.EwsId, SharedFoder.Id.UniqueId, "mailbox#domain.com");
AlternateIdBase aiResponse = service.ConvertId(aiAlternateid, IdFormat.EwsId);
Console.WriteLine(((AlternateId)aiResponse).Mailbox);
Cheers
Glen

Register and use User's email domain

In my current application i want to add a new feature that customers will be able to register their own email domain server if they have any. Currently i am sending all emails using my own application domain.
I want to know how we can accomplish that using ASP.NET WebApi & C# that user will allow to validate and register their own email domains instead of using the default one.
Do we need user's DKIM and SPF records ?
If the user provides the credentials to their own email service
Then you don't need to look at DKIM and SPF records. The user should do that himself.
You will have to collect from the user:
email service type (smtp, mandrill, postmarkapp...)
email service host name (if smtp)
credentials or api key
and maybe service-specific options
If you use your own service to send emails
Then the user will have to add a DKIM entry and update the SPF entry on their domain name. You will have to provide the values to your user depending on your email service.
You can take inspiration from all those services that allow the same kind of feature (mailchimp, mandrill, postmarkapp, zoho...).
Check for DNS email records
In both cases, you can use a DNS client to verify the DNS records are OK. You will be able to tell the user whether records are missing.
Here a sample code to start checking the SPF record with ARSoft.Tools.Net.
var client = new DnsClient(servers, 10000);
var spfRecords = client.Resolve(item.Name, RecordType.Txt);
if (result.AnswerRecords == null || result.AnswerRecords.Count == 0 || result.ReturnCode == ReturnCode.NxDomain)
{
// spf record is missing
}
else if (result.AnswerRecords.Count == 1)
{
// check the record value with SpfRecord.TryParse(item.ActualValue, out spf)
}
else
{
// too many spf records
}

What to store in database to identify Active Directory and local account users

I am writing a permissions system for my WPF application that needs to work with both Active Directory and local machine accounts; the system may or may not be connected to a network.
The user can log into the application with either an AD login or a local machine login. The application stores data in an SQL Server Express data base and each row will have an "owner".
The application will only show rows that are "owned" by the logged in user.
Per this question, the recommended data to store to identify a user is the LDAP objectGUID which I can retrieve with the code below and store in a uniqueidentifier column.
using System.DirectoryServices.AccountManangement;
using System.Security.Principal;
public class ActiveDirectoryHelper
{
public static Guid? GetObjectGuidFromIdentity(WindowsIdentity identity)
{
string domainName = identity.Name.Split('\\')[0];
PrincipalContext pc = null;
if (domainName == Environment.MachineName)
{
pc = new PrincipalContext(ContextType.Machine);
}
else
{
pc = new PrincipalContext(ContextType.Domain, domainName);
}
UserPrincipal user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, identity.Name);
return user.Guid;
}
}
However, UserPrincipal.Guid is null for ContextType.MachineName.
Is there a single piece of information that I can store that can refer to either an AD account or a local account ?
Or do I need to add another column to specify the directory type (AD/SAM) and use another column to store a different identifier (SID) ?
As you discovered, local accounts do not have a GUID. Instead, they just have a SID.
You could opt to use the SID for identifying the user instead so you only have one identifier. The upshot of the GUID is that in cases where customers restructure their Active Directory forest, the user's GUID will stay static while the SID will change.

DirectoryEntry results in The server is unwilling to process the request

I am writing a small app that integrates with AD and will be listing users and allowing members of the site to edit certain fields of the users. These fields will be, first name, last name, display name and email.
When I run the following code I get an exception that says
The server is unwilling to process the request.
Please note that result is not null and contains the correct active directory user that I want to edit. Also note that result is of type SearchResult. Also note that the user I am using to connect to AD is the Administrative user.
DirectoryEntry entryToUpdate = result.GetDirectoryEntry();
entryToUpdate.Properties["cn"].Value = user.Name;
entryToUpdate.Properties["mail"].Value = user.Email;
entryToUpdate.Properties["sn"].Value = user.Surname;
entryToUpdate.Properties["displayName"].Value = user.DisplayName;
string username = user.Email.Substring(0, user.Email.IndexOf("#"));
entryToUpdate.Properties["sAMAccountName"].Value = username;
entryToUpdate.CommitChanges();
Any Ideas?
I believe
entryToUpdate.Properties["cn"].Value = user.Name;
is your Problem. Use givenName as first Name. "cn" is managed by the System.
Also:
If using Exchange, the Mail attribute is managed by it.
Consider the ramifications of modifing samAccountName. Users may not know how to log in, seperate Systems with pseudo SSO may not recognize them, the Login Name does not match the local Profile Name, etc.

Categories