My company is under Office 365.
My goal is to retrieve Outlook's suggested contacts of the user in my asp .net MVC app (the contacts displayed in the autocomplete list).
The website is configured for automatic logon with Windows Authentication and I don't want to ask the user his credentials.
I have to tried to retrieve the suggested contacts by using Exchange Web Service but I only succeed to retrieve "real" contacts by using this code :
public List<Contact> GetContacts()
{
ContactsFolder.Bind(this._service, WellKnownFolderName.Contacts);
ItemView itemView = new ItemView(1000);
itemView.PropertySet = new PropertySet(BasePropertySet.IdOnly, new PropertyDefinitionBase[4]
{
(PropertyDefinitionBase) ContactSchema.DisplayName,
(PropertyDefinitionBase) ContactSchema.Surname,
(PropertyDefinitionBase) ContactSchema.GivenName,
(PropertyDefinitionBase) ContactSchema.EmailAddress1
});
FindItemsResults<Item> items = this._service.FindItems(WellKnownFolderName.Contacts, (ViewBase) itemView);
List<Contact> list = new List<Contact>();
foreach (Item obj in items)
{
if (obj is Contact)
list.Add(obj as Contact);
}
return list;
}
Then, I tried by using the People Api of Office 365 REST API but I don't know how to make the call without asking for the user login/password. This is a sample of a try (if I don't use the proxy, I receive an HTTP 407 Error) :
public async Task Try()
{
var proxy = WebRequest.GetSystemWebProxy();
proxy.Credentials = new NetworkCredential("foo", "1234");
// Now create a client handler which uses that proxy
HttpClient client = null;
HttpClientHandler httpClientHandler = new HttpClientHandler()
{
Proxy = proxy,
PreAuthenticate = true,
UseDefaultCredentials = false,
Credentials = new NetworkCredential("foo#foo.com", "1234")
};
var httpClient = new HttpClient(httpClientHandler);
var result = await httpClient.GetAsync("https://outlook.office.com/api/beta/me/people");
var stringContent = await result.Content.ReadAsStringAsync();
}
About the Suggested Contact problem
What I am thinking is that you are not looking in the proper folder. From what I have seen by googling the suggested contacts are not in the Contacts directory but in Suggested Contacts. In your EWS sample you are looking in Contacts...
See this discussion. Look also at this guy post, he manages to have access to the Suggested Contacts folder with EWS and powershell so there is no doubt this is feasible with C# and EWS .NET SDK. My advice is to continue trying with your sample 1.
About the Authentication problem
Let me emphasize the fact that your requests should be authorized to access both Exchange Web Services (code sample 1) or the outlook REST API (code sample 2).
In sample 1 we do not see how the _service field is instantiated but I bet there is a piece of code that looks more or less the lines below so your allowed to request EWS.
ExchangeService service = new ExchangeService();
service.Credentials = new OAuthCredentials(token);
service.Url = new Uri(ewsUrl);
The token can be probably reuse for the Outlook REST API, try to set it in the bearer in the httpClient
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Your Oauth token");
Now your request should be authorized but you still have your proxy problem. I bet that this happen only within your organisation because your IT set up a proxy. You probably won't need it in production. You can use a debug statement to make it work while your developing locally.
#if DEBUG
IWebProxy webProxy = System.Net.WebRequest.DefaultWebProxy;
if (webProxy!=null && !webProxy.IsBypassed(new Uri(endpoint)))
{
client.Proxy = webProxy;
}
#endif
I never found the "Suggested Contacts" folder of the post.
I ended by using the folder "AllContacts" that seems to do the job.
public List<Contact> GetSuggestedContacts()
{
// Instantiate the item view with the number of items to retrieve from the Contacts folder.
ItemView view = new ItemView(1000);
// To keep the request smaller, request only the display name property.
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties, ContactSchema.DisplayName, ContactSchema.Surname, ContactSchema.GivenName, ContactSchema.EmailAddress1);
// Retrieve the RecipientCache folder in the Contacts folder that have the properties that you selected.
var contactFolders = _service.FindFolders(new FolderId(WellKnownFolderName.Root), new FolderView(500));
var folder = contactFolders.Folders.SingleOrDefault(x => x.DisplayName == "AllContacts");
if(folder == null) return new List<Contact>();
//Cast Item in Contact and filtered only real adresses
var cacheContacts = folder.FindItems(view).Items.OfType<Contact>().Where(x => x.EmailAddresses.Contains(0) && x.EmailAddresses[0].Address != null && x.EmailAddresses[0].Address.Contains('#')).ToList();
return cacheContacts;
}
I also found the service ResolveName of Exchange that I could have use for the autocomplete.
Related
I am currently working out the Microsoft Graph tutorial with C# .Net Core, and in the process I came across the following C#-method for Subscription:
[HttpGet]
public async Task<ActionResult<string>> Get()
{
var graphServiceClient = GetGraphClient();
var sub = new Microsoft.Graph.Subscription();
sub.ChangeType = "updated";
sub.NotificationUrl = config.Ngrok + "/api/notifications";
sub.Resource = "/users";
sub.ExpirationDateTime = DateTime.UtcNow.AddMinutes(15);
sub.ClientState = "SecretClientState";
var newSubscription = await graphServiceClient
.Subscriptions
.Request()
.AddAsync(sub);
Subscriptions[newSubscription.Id] = newSubscription;
if (subscriptionTimer == null)
{
subscriptionTimer = new Timer(CheckSubscriptions, null, 5000, 15000);
}
return $"Subscribed. Id: {newSubscription.Id}, Expiration: {newSubscription.ExpirationDateTime}";
}
and wanted to know how I can change it for sharepoint lists instead of users.
If I change it to /sites/{site-id} or similar it does not work. (see sub.Resource)
Github-Link: MS Repo
Microsoft Graph API uses a webhook mechanism to deliver change notifications to clients. Using the Microsoft Graph API, an app can subscribe to changes for list under a SharePoint site.
Resource Path - Changes to content within the list:
/sites/{id}/lists/{id}
For details round how to subscribe to and handle incoming notifications, see Set up notifications for changes in user data
Also make sure you check necessary permissions needed here.
I found the solution myself with the sub.Resource: /sites/{site-id}/lists/{list-id}
I'm struggling with a simple Task, that gets new E-Mails in specific Folders in Exchange Online, sets "Processed"-Category and then stores the E-Mail.
Firstly, I create App permissions like that:
var app = ConfidentialClientApplicationBuilder.Create(_appConfig.ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic,
_appConfig.Tenant)
.WithClientSecret(_appConfig.ClientSecret)
.Build();
AuthenticationResult authResultresult = null;
var ewsScopes = new[] {"https://outlook.office.com/.default"};
authResultresult = await app.AcquireTokenForClient(ewsScopes)
.ExecuteAsync();
then I create Exchange-Client and use created Oauth-Token to authorize:
var result = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
result.KeepAlive = false;
result.DateTimePrecision = DateTimePrecision.Milliseconds;
result.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
result.UseDefaultCredentials = false;
var authResultresult = await CreateAppPermissions(_appConfig);
result.Credentials = new OAuthCredentials(authResultresult.AccessToken);
after that I impersonate SMTP-User with my mainSMTP account
result.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, _appConfig.SMTPMailAccount);
after that I use this Code to retrieve an Email using known Id, add for it new Category and update the item like this:
var itemsToStore = result.BindToItems(new []{newItemId}, props);
foreach (var itemToStore in itemsToStore)
{
itemToStore.Item.Categories.Add("Processed");
itemToStore.Item.Update(ConflictResolutionMode.AlwaysOverwrite, true);
}
This code has previously produced “Access is denied. Check credentials and try again., Cannot save changes made to an item to store." - Exception on Item.Update. After a research I have found this :
Office 365 API ErrorAccessDenied (Access is denied. Check credentials and try again.)
and followed the proposed solution by removing "Have full access to a users mailbox"- checkbox flag.
After that I'm getting 401 unauthorized, when I'm calling BindToItems.
Was it a step backwards to remove the checkbox?
RESOLVED:
Found the solution for 401:
since I'm using EWS a.k.a. older API called Exchange Web Services it was a mistake to remove the checkbox.
the reason for “Access is denied. Check credentials and try again., Cannot save changes made to an item to store." was that impersonated user didn't have rights to change someones elses emails
recently I have noticed a lot of users being registered to my site and they all use disposable emails, temporary email addresses or fake emails to register on the site...
What I have done to partially prevent that is I added mailgun email verification which tells me whether an email is valid or not , disposable one or not...
This filtered out like 95% of the fake emails and users, however there is still a small portion of them which go "unnoticed". What I have figured out to do are following things via code:
public static bool IsValidEmail(string email)
{
RestClient client = new RestClient();
client.BaseUrl = new Uri("https://api.mailgun.net/v3");
client.Authenticator = new HttpBasicAuthenticator("api", "");
RestRequest request = new RestRequest();
request.Resource = "/address/validate";
request.AddParameter("address", email);
var res = new JavaScriptSerializer().Deserialize<RootObject>(client.Execute(request).Content);
if (res.is_disposable_address || res.is_valid==false)
return false;
return true;
}
This is one step via mailgun that I described previously... And second method to prevent spam/fake users is that I have set up manually a table in my DB with blacklisted domains to which registration won't be allowed... And the logic for that is:
public static bool IsValid(string email)
{
using (var ctx = new myEntities())
{
var blacklistedProviders = ctx.BlacklistedDomains.ToList();
foreach (var item in blacklistedProviders)
{
if (email.ToLower().Contains(item.DomainName.ToLower()))
return false;
}
}
return true;
}
However I don't think this will prevent them from doing more malicious stuff on the website...
I have also added google recaptcha to harden the process of registration for spammers, that does the job well too....
So my questions here are:
Are there any other ways that I can validate the email to see whether it is disposable or invalid email
What other way could I implement to prevent users from creating fake accounts?
Can someone help me out with this?
I was able to succeed via a package I found called EAGetMail. Unfortunately, I realized soon after that they have a token system and this is not a free approach.
There are a couple other choices available, like using Outlook Mail REST API, and MimeKit, but I'm lost on how to achieve my end result because no "start to finish" code is available on either of these references that demonstrates how to parse an Inbox for an account.
I've started to write this with the help of Mimekit, but am not sure if this is the proper way at all.
I must imagine it looks something like:
using (var client = new SmtpClient ())
{
client.Connect("outlook.office365.com", 587);
client.Authenticate("myemail#office365account.com", "mypassword");
var message = MimeMessage.Load(stream);
}
I don't know how to setup the stream mentioned above, and I don't know if it's possible to do this with Mimekit and Office 365.
I'm open to seeing a solution for this in any other approach that's not through EAGetMail. If anyone has a lightweight solution ranging from actual establishing a connection, to pulling messages from the inbox, would be great to see!
I've got it using EWS (Exchange Web Services). Here's my code:
private static bool RedirectionUrlValidationCallback(string redirectionUrl)
{
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https")
{
result = true;
}
return result;
}
static void Main(string[] args)
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials("email#myemail.com", "myPassword");
service.AutodiscoverUrl("email#myemail.com", RedirectionUrlValidationCallback);
//creates an object that will represent the desired mailbox
Mailbox mb = new Mailbox(#"email#myemail.com");
//creates a folder object that will point to inbox folder
FolderId fid = new FolderId(WellKnownFolderName.Inbox, mb);
//this will bind the mailbox you're looking for using your service instance
Folder inbox = Folder.Bind(service, fid);
//load items from mailbox inbox folder
if (inbox != null)
{
FindItemsResults<Item> items = inbox.FindItems(new ItemView(100));
foreach (var item in items)
{
item.Load();
Console.WriteLine("Subject: " + item.Subject);
}
}
}
I am using Microsoft Exchange Services to retrieve contacts from Outlook. I use the following code. It executes without any error. However, I get nothing in Contacts.
public ActionResult Index()
{
ExchangeService _service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
_service.Credentials = new WebCredentials("username", "password");
_service.AutodiscoverUrl("****");
_service.Url = new Uri("https://***/EWS/Exchange.asmx");
foreach (Contact contact in _service.FindItems(WellKnownFolderName.Contacts, new ItemView(int.MaxValue)))
{
// do something
}
return View();
}
How can i get the contacts?
Please help,
Thanks.
I would suggest you clean up your code as there a few reason I can see it failing eg
_service.AutodiscoverUrl("****");
_service.Url = new Uri("https://***/EWS/Exchange.asmx");
use one or the other and for AutoDiscoverURL you may need a callback in here
foreach (Contact contact in _service.FindItems(WellKnownFolderName.Contacts, new ItemView(int.MaxValue)))
First of all a Contacts folder can contain objects other then Contacts so if your code comes across a Distribution list its going to generate an exception. Also using int.MaxValue is a bad idea you should page the Items in groups of 1000 (Throttling on Exchange 2010 will enforce this for you anyway so your code will just fail to get all the Contacts if there are more then 1000). Also does the Mailbox your trying to access belong to the security credentials your using. I would suggest you use something like
String mailboxToAccess = "user#domain.onmicrosoft.com";
ExchangeService _service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
_service.Credentials = new WebCredentials("upn#domain.onmicrosoft.com", "password");
_service.AutodiscoverUrl(mailboxToAccess, redirect => true);
// _service.Url = new Uri("https://***/EWS/Exchange.asmx");
ItemView iv = new ItemView(1000);
FolderId ContactsFolderId = new FolderId(WellKnownFolderName.Contacts,mailboxToAccess);
FindItemsResults<Item> fiResults;
do
{
fiResults = _service.FindItems(ContactsFolderId, iv);
foreach (Item itItem in fiResults.Items)
{
if (itItem is Contact)
{
Contact ContactItem = (Contact)itItem;
Console.WriteLine(ContactItem.Subject);
}
}
iv.Offset += fiResults.Items.Count;
} while (fiResults.MoreAvailable);
You can test EWS itself using the EWSEditor http://ewseditor.codeplex.com/.