diff --git a/Management/Controllers/ExchangeAccountController.cs b/Management/Controllers/ExchangeAccountController.cs index 45e7c75..90d042a 100644 --- a/Management/Controllers/ExchangeAccountController.cs +++ b/Management/Controllers/ExchangeAccountController.cs @@ -271,10 +271,11 @@ public ActionResult Edit([Bind(Include = "AccountId,Name,Account,PasswordUnmaske Match lnk = _emailRgx.Match(ews.Account); ews.Account = lnk.Success ? lnk.Value : ""; - resolveAccount(ews); + // resolveAccount(ews); // Not required since we use MS Graph - but some validation here would probably be sensible if (ModelState.IsValid) { + ews.UpdatePassword(db); db.Entry(ews).State = EntityState.Modified; db.Entry(ews).Property(l => l.Password).IsModified = ews.PasswordSet; //db.Entry(ews).Property(l => l.Url).IsModified = false; diff --git a/Management/Views/ExchangeAccount/_Edit.cshtml b/Management/Views/ExchangeAccount/_Edit.cshtml index 8a2de09..1087a5e 100644 --- a/Management/Views/ExchangeAccount/_Edit.cshtml +++ b/Management/Views/ExchangeAccount/_Edit.cshtml @@ -28,18 +28,18 @@ @Html.ValidationMessageFor(model => model.PasswordUnmasked) -
- @Html.LabelFor(model => model.Url) -
-
- @Html.EditorFor(model => model.Url) - @Html.ValidationMessageFor(model => model.Url) -
- -
- @Html.LabelFor(model => model.EwsVersion) -
-
- @Html.DropDownListFor(model => model.EwsVersion, ewsVersions) - @Html.ValidationMessageFor(model => model.EwsVersion) -
+ @*
*@ + @* @Html.LabelFor(model => model.Url) *@ + @*
*@ + @*
*@ + @* @Html.EditorFor(model => model.Url) *@ + @* @Html.ValidationMessageFor(model => model.Url) *@ + @*
*@ + @* *@ + @*
*@ + @* @Html.LabelFor(model => model.EwsVersion) *@ + @*
*@ + @*
*@ + @* @Html.DropDownListFor(model => model.EwsVersion, ewsVersions) *@ + @* @Html.ValidationMessageFor(model => model.EwsVersion) *@ + @*
*@ diff --git a/Presentation/Outlook.cs b/Presentation/Outlook.cs index 1b1a558..31b3bfc 100644 --- a/Presentation/Outlook.cs +++ b/Presentation/Outlook.cs @@ -19,6 +19,7 @@ using Microsoft.Exchange.WebServices.Data; using System.Web.Script.Serialization; using DisplayMonkey.Language; +using Microsoft.Graph.Models; namespace DisplayMonkey { @@ -44,7 +45,7 @@ public class Outlook : Frame private int ShowAsFlags { get; set; } - public bool IsShowAsAllowed(LegacyFreeBusyStatus flag) + public bool IsShowAsAllowed(FreeBusyStatus flag) { return (ShowAsFlags & (1 << (int)flag)) != 0; } diff --git a/Presentation/Presentation.csproj b/Presentation/Presentation.csproj index bc3b1b6..723d1e1 100644 --- a/Presentation/Presentation.csproj +++ b/Presentation/Presentation.csproj @@ -13,7 +13,7 @@ Properties DisplayMonkey DisplayMonkey.Presentation - v4.5 + v4.8 @@ -48,6 +48,15 @@ false + + ..\packages\Azure.Core.1.32.0\lib\net461\Azure.Core.dll + + + ..\packages\Azure.Identity.1.9.0\lib\netstandard2.0\Azure.Identity.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + ..\packages\EWS-Api-2.1.1.0.0\lib\net35\Microsoft.Exchange.WebServices.dll @@ -55,13 +64,136 @@ ..\packages\EWS-Api-2.1.1.0.0\lib\net35\Microsoft.Exchange.WebServices.Auth.dll + + ..\packages\Microsoft.Graph.5.14.0\lib\netstandard2.0\Microsoft.Graph.dll + + + ..\packages\Microsoft.Graph.Core.3.0.7\lib\net462\Microsoft.Graph.Core.dll + + + ..\packages\Microsoft.Identity.Client.4.49.1\lib\net461\Microsoft.Identity.Client.dll + + + ..\packages\Microsoft.Identity.Client.Extensions.Msal.2.25.3\lib\net45\Microsoft.Identity.Client.Extensions.Msal.dll + + + ..\packages\Microsoft.IdentityModel.Abstractions.6.30.1\lib\net472\Microsoft.IdentityModel.Abstractions.dll + + + ..\packages\Microsoft.IdentityModel.JsonWebTokens.6.30.1\lib\net472\Microsoft.IdentityModel.JsonWebTokens.dll + + + ..\packages\Microsoft.IdentityModel.Logging.6.30.1\lib\net472\Microsoft.IdentityModel.Logging.dll + + + ..\packages\Microsoft.IdentityModel.Protocols.6.30.1\lib\net472\Microsoft.IdentityModel.Protocols.dll + + + ..\packages\Microsoft.IdentityModel.Protocols.OpenIdConnect.6.30.1\lib\net472\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll + + + ..\packages\Microsoft.IdentityModel.Tokens.6.30.1\lib\net472\Microsoft.IdentityModel.Tokens.dll + + + ..\packages\Microsoft.Kiota.Abstractions.1.1.2\lib\netstandard2.0\Microsoft.Kiota.Abstractions.dll + + + ..\packages\Microsoft.Kiota.Authentication.Azure.1.0.2\lib\netstandard2.0\Microsoft.Kiota.Authentication.Azure.dll + + + ..\packages\Microsoft.Kiota.Http.HttpClientLibrary.1.0.2\lib\netstandard2.0\Microsoft.Kiota.Http.HttpClientLibrary.dll + + + ..\packages\Microsoft.Kiota.Serialization.Form.1.0.1\lib\netstandard2.0\Microsoft.Kiota.Serialization.Form.dll + + + ..\packages\Microsoft.Kiota.Serialization.Json.1.0.6\lib\netstandard2.0\Microsoft.Kiota.Serialization.Json.dll + + + ..\packages\Microsoft.Kiota.Serialization.Text.1.0.1\lib\netstandard2.0\Microsoft.Kiota.Serialization.Text.dll + + + + ..\packages\Newtonsoft.Json.6.0.1\lib\net45\Newtonsoft.Json.dll + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + - + + ..\packages\System.Diagnostics.DiagnosticSource.6.0.0\lib\net461\System.Diagnostics.DiagnosticSource.dll + + + + ..\packages\System.IdentityModel.Tokens.Jwt.6.30.1\lib\net472\System.IdentityModel.Tokens.Jwt.dll + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + ..\packages\System.Memory.Data.1.0.2\lib\net461\System.Memory.Data.dll + + + ..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll + + + ..\packages\System.Net.Http.WinHttpHandler.7.0.0\lib\net462\System.Net.Http.WinHttpHandler.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + + + + ..\packages\System.Security.Claims.4.3.0\lib\net46\System.Security.Claims.dll + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\packages\System.Security.Cryptography.ProtectedData.4.7.0\lib\net461\System.Security.Cryptography.ProtectedData.dll + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + ..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.6.0.0\lib\net461\System.Text.Json.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + @@ -70,6 +202,9 @@ + + ..\packages\Tavis.UriTemplates.2.0.0\lib\netstandard2.0\Tavis.UriTemplates.dll + @@ -450,6 +585,15 @@ xcopy "$(SolutionDir)Management\Content\Site.css" "$(SolutionDir)Presentation\Styles\Site.css" /Y /U + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. + + + + + - + - + + + + + + - + - + - - + + - - + + - - - + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Presentation/getOutlook.ashx.cs b/Presentation/getOutlook.ashx.cs index b5620da..1b26586 100644 --- a/Presentation/getOutlook.ashx.cs +++ b/Presentation/getOutlook.ashx.cs @@ -10,23 +10,24 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Linq; using System.Web; -//using System.Web.UI; -//using System.Web.UI.WebControls; using System.Web.Script.Serialization; -using Microsoft.Exchange.WebServices.Data; -using System.Net; +using Azure.Identity; using DisplayMonkey.Language; +using Microsoft.Graph; +using Microsoft.Graph.Models; +using Microsoft.Graph.Models.ODataErrors; namespace DisplayMonkey { public partial class getOutlook : HttpTaskAsyncHandler { public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContext context) - { - HttpRequest request = context.Request; - HttpResponse response = context.Response; + { + HttpRequest request = context.Request; + HttpResponse response = context.Response; int frameId = request.IntOrZero("frame"); int panelId = request.IntOrZero("panel"); @@ -35,17 +36,17 @@ public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContex int reserveMinutes = request.IntOrZero("reserveMinutes"); int trace = request.IntOrZero("trace"); - string json = ""; - - try - { + string json = ""; + + try + { // set culture Outlook outlook = new Outlook(frameId); Location location = new Location(displayId); if (string.IsNullOrWhiteSpace(culture)) culture = location.Culture; - + if (!string.IsNullOrWhiteSpace(culture)) { System.Globalization.CultureInfo cultureInfo = new System.Globalization.CultureInfo(culture); @@ -56,7 +57,7 @@ public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContex // EWS: get data OutlookData data = await HttpRuntime.Cache.GetOrAddAbsoluteAsync( string.Format("outlook_{0}_{1}_{2}", location.LocationId, outlook.FrameId, outlook.Version), - async (expire) => + async (expire) => { expire.When = DateTime.Now.AddMinutes(outlook.CacheInterval); return await OutlookData.FromFrameAsync(outlook, location, reserveMinutes); @@ -70,12 +71,12 @@ public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContex //int showEvents = outlook.ShowEvents; List currentList = data.events - .Where(e => e.Ends >= locationTime) - .Take(Math.Max(1, outlook.ShowEvents)) - .ToList() + .Where(e => e.Ends >= locationTime) + .Take(Math.Max(1, outlook.ShowEvents)) + .ToList() ; - - EventEntry + + EventEntry currentEvent = null, firstEvent = currentList.FirstOrDefault() ; @@ -86,26 +87,29 @@ public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContex } DateTime endTime = locationToday.Add(data.endTime); - string + string strCurrentEvent = "", strCurrentStatus = string.Format( Resources.Outlook_AvailableUntil, endTime.ToShortTimeString() - ) + ) ; TimeSpan availableTime = new TimeSpan(0); - + if (currentEvent != null) { DateTime tomorrow = locationToday.AddDays(1); - strCurrentEvent = string.Format("{0}, {1} - {2}", + strCurrentEvent = string.Format("{0}, {1} - {2}", currentEvent.Subject, - (currentEvent.Starts >= tomorrow ? currentEvent.Starts.ToShortDateString() + " " : "") + currentEvent.Starts.ToShortTimeString(), - (currentEvent.Ends >= tomorrow ? currentEvent.Ends.ToShortDateString() + " " : "") + currentEvent.Ends.ToShortTimeString() - ); + (currentEvent.Starts >= tomorrow ? currentEvent.Starts.ToShortDateString() + " " : "") + + currentEvent.Starts.ToShortTimeString(), + (currentEvent.Ends >= tomorrow ? currentEvent.Ends.ToShortDateString() + " " : "") + + currentEvent.Ends.ToShortTimeString() + ); TimeSpan gap = currentEvent.Ends.Subtract(locationTime); if (gap.Hours > 0) - strCurrentStatus = string.Format(Resources.Outlook_EndsInHrsMin, (int)gap.TotalHours, gap.Minutes); + strCurrentStatus = string.Format(Resources.Outlook_EndsInHrsMin, (int)gap.TotalHours, + gap.Minutes); else strCurrentStatus = string.Format(Resources.Outlook_EndsInMin, gap.Minutes); } @@ -113,7 +117,8 @@ public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContex { availableTime = firstEvent.Starts.Subtract(locationTime); if (availableTime.Hours > 0) - strCurrentStatus = string.Format(Resources.Outlook_AvailableForHrsMin, (int)availableTime.TotalHours, availableTime.Minutes); + strCurrentStatus = string.Format(Resources.Outlook_AvailableForHrsMin, + (int)availableTime.TotalHours, availableTime.Minutes); else strCurrentStatus = string.Format(Resources.Outlook_AvailableForMin, availableTime.Minutes); } @@ -165,23 +170,23 @@ public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContex { items = currentList, }, - labels = new[] + labels = new[] { - new {key = "subject", value = Resources.Outlook_Event}, - new {key = "starts", value = Resources.Outlook_Starts}, - new {key = "ends", value = Resources.Outlook_Ends}, - new {key = "duration", value = Resources.Outlook_Duration}, - new {key = "sensitivity", value = Resources.Outlook_Sensitivity}, - new {key = "showAs", value = Resources.OutlookShowAs}, - new {key = "noEvents", value = Resources.Outlook_NoEventsToday}, - new {key = "bookingImpossible", value = Resources.Outlook_BookingImpossible}, - new {key = "bookingSent", value = Resources.Outlook_BookingSent}, + new { key = "subject", value = Resources.Outlook_Event }, + new { key = "starts", value = Resources.Outlook_Starts }, + new { key = "ends", value = Resources.Outlook_Ends }, + new { key = "duration", value = Resources.Outlook_Duration }, + new { key = "sensitivity", value = Resources.Outlook_Sensitivity }, + new { key = "showAs", value = Resources.OutlookShowAs }, + new { key = "noEvents", value = Resources.Outlook_NoEventsToday }, + new { key = "bookingImpossible", value = Resources.Outlook_BookingImpossible }, + new { key = "bookingSent", value = Resources.Outlook_BookingSent }, }, }); - } + } - catch (Exception ex) - { + catch (Exception ex) + { JavaScriptSerializer s = new JavaScriptSerializer(); if (trace == 0) json = s.Serialize(new @@ -215,7 +220,7 @@ public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContex response.Cache.SetSlidingExpiration(true); response.Cache.SetNoStore(); response.ContentType = "application/json"; - response.Write(json); + response.Write(json); response.Flush(); } @@ -223,7 +228,7 @@ public override async System.Threading.Tasks.Task ProcessRequestAsync(HttpContex private class OutlookData { - public IEnumerable events = null; + public IEnumerable events; public TimeSpan startTime = new TimeSpan(0, 0, 0); public TimeSpan endTime = new TimeSpan(23, 59, 59); public TimeZoneInfo timeZone = TimeZoneInfo.Local; @@ -236,254 +241,133 @@ public OutlookData(Outlook outlook, Location location, int reserveMinutes) #region -------- EWS Data Call --------f - private static ExtendedPropertyDefinition PR_TextBody = new ExtendedPropertyDefinition(0x1000, MapiPropertyType.String); - private static ExtendedPropertyDefinition PR_Sensitivity = new ExtendedPropertyDefinition(0x0036, MapiPropertyType.Integer); - private OutlookData() { } - public static async System.Threading.Tasks.Task FromFrameAsync(Outlook outlook, Location location, int reserveMinutes) + public static async System.Threading.Tasks.Task FromFrameAsync(Outlook outlook, + Location location, int reserveMinutes) { - OutlookData data = new OutlookData(); - DateTime - locationTime = location.LocationTime, - locationToday = new DateTime(locationTime.Year, locationTime.Month, locationTime.Day), - windowBeg = locationToday, //locationTime, - windowEnd = locationToday.AddDays(1).AddMilliseconds(-1) - ; - - // EWS: create connection point - ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack; - - ExchangeService service = new ExchangeService(outlook.EwsVersion) + var tenantId = ConfigurationManager.AppSettings["AzureActiveDirectoryTenantId"]; + var clientId = ConfigurationManager.AppSettings["AzureActiveDirectoryClientId"]; + if (tenantId == default || clientId == default) { - Credentials = new WebCredentials( - outlook.Account, - Encryptor.Current.Decrypt(outlook.Password) // 1.4.0 - ), - }; - - // https://msdn.microsoft.com/en-us/library/office/dn458789%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396#bk_howmaintained - // https://social.msdn.microsoft.com/Forums/lync/en-US/bd032e3d-2501-40ba-a2b0-29a404685c35/error-exchange-web-services-are-not-currently-available?forum=exchangesvrdevelopment - service.HttpHeaders.Add("X-AnchorMailbox", outlook.Account); - - // EWS: get URL - if (!string.IsNullOrWhiteSpace(outlook.URL)) - { - service.Url = new Uri(outlook.URL); - } - else - { - service.Url = await HttpRuntime.Cache.GetOrAddSlidingAsync( - string.Format("exchange_account_{0}", outlook.Account), - async (expire) => - { - expire.After = TimeSpan.FromMinutes(60); - return await System.Threading.Tasks.Task.Run(() => - { - service.AutodiscoverUrl(outlook.Account, RedirectionUrlValidationCallback); - return service.Url; - }); - }); + throw new Exception( + "App Settings must specify Azure Active Directory Client and Tenant ID to use Outlook frame"); } - // mailbox: get display name - data.DisplayName = outlook.Name; - if (string.IsNullOrWhiteSpace(data.DisplayName)) - { - var match = await System.Threading.Tasks.Task.Run(() => service.ResolveName(outlook.Mailbox)); - if (match.Count > 0) + var useNamePasswordCredential = new UsernamePasswordCredential(outlook.Account, + Encryptor.Current.Decrypt(outlook.Password), + tenantId, + clientId); + + // We don't provide a way for the user to consent to these scopes. + // Either issue admin consent in AAD or use another method to trigger the user consent flow. + var graphServiceClient = new GraphServiceClient(useNamePasswordCredential, + new[] { "user.read", "calendars.readwrite", "calendars.readwrite.shared", "MailboxSettings.Read" }); + + // The data we'll now fill, and then return to the client. + var data = new OutlookData(); + var locationTime = location.LocationTime; + var locationToday = new DateTime(locationTime.Year, locationTime.Month, locationTime.Day); + var windowBeg = locationToday; + var windowEnd = locationToday.AddDays(1).AddMilliseconds(-1); + + // 1. Get the owner name of the specified mailboxes calendar and set the data.DisplayName property + // 2. Check if the user has working hours set. If they do, set data.startTime and data.endTime and data.timeZone to those working hours + // 3. If reserveMinutes > 0 then create a new event in the calendar for reserveMinutes number of minutes + // 4. Get a list of events today + // 5. Filter those events based on TBC criteria and map to data.events + + // Working Hours are stored in a users Mailbox Settings. + var mailboxSettings = await graphServiceClient.Me.MailboxSettings.GetAsync( + config => { - if (match[0].Contact != null) - { - data.DisplayName = - match[0].Contact.CompleteName.FullName - ?? match[0].Contact.DisplayName - ?? data.DisplayName - ; - } - - else if (match[0].Mailbox != null) - { - data.DisplayName = - match[0].Mailbox.Name - ?? data.DisplayName - ; - } - } - //else - // throw new ApplicationException(string.Format("Mailbox {0} not found", mailbox)); + config.QueryParameters.Select = new[] { "workingHours" }; + }); + + // If we have working hours set, then set the start and end times. Note we always assume Local time. This might be bad? + // I'm unsure of the behaviour of the graph api when there are no working hours set. + if (mailboxSettings.WorkingHours != null) + { + data.startTime = mailboxSettings.WorkingHours.StartTime?.DateTime.TimeOfDay ?? DateTime.Today.TimeOfDay; + data.endTime = mailboxSettings.WorkingHours.EndTime?.DateTime.TimeOfDay ?? + DateTime.Today.AddHours(23).AddMinutes(59).TimeOfDay; + data.timeZone = TimeZoneInfo.Local; } - // mailbox: get availability - GetUserAvailabilityResults uars = await System.Threading.Tasks.Task.Run(() => + // Get the name of the owner of the calendar. + var calendarDetails = await graphServiceClient.Users[outlook.Account].Calendar.GetAsync(c => { - return service.GetUserAvailability( - new AttendeeInfo[] { new AttendeeInfo(outlook.Mailbox, MeetingAttendeeType.Required, true) }, - new TimeWindow(locationToday, locationToday.AddDays(1)), - AvailabilityData.FreeBusy - ); + c.QueryParameters.Select = new[] { "owner" }; }); - var u = uars.AttendeesAvailability[0]; - if (u.WorkingHours != null) - { - data.startTime = u.WorkingHours.StartTime; - data.endTime = u.WorkingHours.EndTime; - data.timeZone = u.WorkingHours.TimeZone; - } + data.DisplayName = calendarDetails.Owner.Name; + // Do we need to create an event in the calendar? if (reserveMinutes > 0) { - Appointment appointment = new Appointment(service) - { - Subject = outlook.BookingSubject, - Body = string.Format("{0} | {1}", Resources.Outlook_BookingOnDemand, Resources.DisplayMonkey), - Start = DateTime.Now, - End = DateTime.Now.AddMinutes(reserveMinutes), - }; - - if (outlook.Mailbox == outlook.Account) + try { - await System.Threading.Tasks.Task.Run(() => + _ = await graphServiceClient.Users[outlook.Account].Calendar.Events.PostAsync(new Event { - appointment.Save(SendInvitationsMode.SendToNone); - return true; + Subject = outlook.BookingSubject, + Body = new ItemBody + { + Content = $"{Resources.Outlook_BookingOnDemand} | {Resources.DisplayMonkey}", + }, + Start = new DateTimeTimeZone + { + DateTime = DateTime.Now.ToString("O"), + TimeZone = TimeZone.CurrentTimeZone.StandardName + }, + End = new DateTimeTimeZone + { + DateTime = DateTime.Now.AddMinutes(reserveMinutes).ToString("O"), + TimeZone = TimeZone.CurrentTimeZone.StandardName + } }); } - else + catch (ODataError e) { - appointment.Resources.Add(outlook.Mailbox); - await System.Threading.Tasks.Task.Run(() => - { - appointment.Save(SendInvitationsMode.SendOnlyToAll); - return true; - }); + throw new Exception(e.Error.Message); } } - // events: prep filter - FolderId folderId = new FolderId(WellKnownFolderName.Calendar, new Mailbox(outlook.Mailbox)); - CalendarFolder calendar = await System.Threading.Tasks.Task.Run(() => CalendarFolder.Bind(service, folderId, new PropertySet())); - CalendarView cView = new CalendarView(windowBeg, windowEnd) + // Get list of events in the calendar + var calendarView = await graphServiceClient.Users[outlook.Account].CalendarView.GetAsync(c => { - PropertySet = new PropertySet( - //BasePropertySet.FirstClassProperties, - AppointmentSchema.Subject, - AppointmentSchema.DateTimeCreated, - AppointmentSchema.Start, - AppointmentSchema.End, - AppointmentSchema.Duration, - AppointmentSchema.Sensitivity, - AppointmentSchema.LegacyFreeBusyStatus - ) - }; - - // events: get list - var appointments = await System.Threading.Tasks.Task.Run(() => calendar.FindAppointments(cView)); - data.events = appointments - //.FindAppointments(cView) - .Where(a => - (outlook.Privacy != Models.OutlookPrivacy.OutlookPrivacy_NoClassified || a.Sensitivity == Sensitivity.Normal) && - (outlook.IsShowAsAllowed(a.LegacyFreeBusyStatus)) - ) - .OrderBy(i => i.Start) - .ThenBy(i => i.DateTimeCreated) - .ThenBy(i => i.Subject) - //.Take(Math.Max(1, outlook.ShowEvents)) + c.Headers.Add("Prefer", "outlook.body-content-type=\"text\""); + c.QueryParameters.Select = new[] { "subject", "body", "start", "end", "bodyPreview", "createdDateTime", "sensitivity", "showAs"}; + c.QueryParameters.StartDateTime = windowBeg.ToString("O"); + c.QueryParameters.EndDateTime = windowEnd.ToString("O"); + }); + data.events = calendarView.Value + .Where(a => + (outlook.Privacy != Models.OutlookPrivacy.OutlookPrivacy_NoClassified || + a.Sensitivity == Sensitivity.Normal) && + outlook.IsShowAsAllowed(a.ShowAs ?? FreeBusyStatus.Busy) + ) .Select(a => new EventEntry { Subject = - outlook.Privacy == Models.OutlookPrivacy.OutlookPrivacy_All || a.Sensitivity == Sensitivity.Normal ? a.Subject : - DataAccess.StringResource(string.Format("EWS_Sensitivity_{0}", a.Sensitivity.ToString())), - CreatedOn = a.DateTimeCreated, - Starts = a.Start, - Ends = a.End, - Duration = a.Duration, - Sensitivity = a.Sensitivity, + outlook.Privacy == Models.OutlookPrivacy.OutlookPrivacy_All || + a.Sensitivity == Sensitivity.Normal + ? a.Subject + : DataAccess.StringResource($"EWS_Sensitivity_{a.Sensitivity.ToString()}"), + CreatedOn = a.CreatedDateTime?.DateTime ?? new DateTime(), + Starts = DateTime.Parse(a.Start.DateTime), + Ends = DateTime.Parse(a.End.DateTime), + Duration = DateTime.Parse(a.End.DateTime) - DateTime.Parse(a.Start.DateTime), + Sensitivity = a.Sensitivity ?? Sensitivity.Normal, Today = locationToday, - ShowAs = a.LegacyFreeBusyStatus, + ShowAs = a.ShowAs ?? FreeBusyStatus.Busy, }) - .ToList() - ; + .ToList(); return data; } #endregion - - #region -------- EWS Callbacks -------- - - 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; - } - - private static bool CertificateValidationCallBack( - object sender, - System.Security.Cryptography.X509Certificates.X509Certificate certificate, - System.Security.Cryptography.X509Certificates.X509Chain chain, - System.Net.Security.SslPolicyErrors sslPolicyErrors - ) - { - //return true; - // If the certificate is a valid, signed certificate, return true. - if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None) - { - return true; - } - - // If there are errors in the certificate chain, look at each error to determine the cause. - if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0) - { - if (chain != null && chain.ChainStatus != null) - { - foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status - in chain.ChainStatus) - { - if ((certificate.Subject == certificate.Issuer) && - (status.Status == - System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot)) - { - // Self-signed certificates with an untrusted root are valid. - continue; - } - else - { - if (status.Status != - System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError) - { - // If there are any other errors in the certificate chain, the certificate is invalid, - // so the method returns false. - return false; - } - } - } - } - - // When processing reaches this line, the only errors in the certificate chain are - // untrusted root errors for self-signed certificates. These certificates are valid - // for default Exchange server installations, so return true. - return true; - } - - // In all other cases, return false. - return false; - } - - #endregion } #endregion @@ -499,7 +383,7 @@ private class EventEntry : IComparable public TimeSpan Duration { get; set; } public DateTime Today { get; set; } public Sensitivity Sensitivity { get; set; } - public LegacyFreeBusyStatus ShowAs { get; set; } + public FreeBusyStatus ShowAs { get; set; } public int CompareTo(EventEntry rhs) { @@ -510,14 +394,17 @@ public int CompareTo(EventEntry rhs) int createdCheck = this.CreatedOn.CompareTo(rhs.CreatedOn); if (createdCheck != 0) return createdCheck; - + return this.Subject.CompareTo(rhs.Subject); } } private class EventEntryConverter : JavaScriptConverter { - public override IEnumerable SupportedTypes { get { return _supportedTypes; } } + public override IEnumerable SupportedTypes + { + get { return _supportedTypes; } + } public override IDictionary Serialize(object obj, JavaScriptSerializer serializer) { @@ -530,33 +417,40 @@ public override IDictionary Serialize(object obj, JavaScriptSeri serialized["showAs"] = ""; List flags = new List(); - foreach (var x in _sens.Where(e => e.Key == evt.Sensitivity)) // one and only + foreach (var x in _sens.Where(e => e.Key == evt.Sensitivity)) // one and only { flags.Add(x.Value.Flag); serialized["sensitivity"] = DataAccess.StringResource(x.Value.Resource); break; } - foreach (var x in _showAs.Where(e => e.Key == evt.ShowAs)) // one and only + + foreach (var x in _showAs.Where(e => e.Key == evt.ShowAs)) // one and only { flags.Add(x.Value.Flag); serialized["showAs"] = DataAccess.StringResource(x.Value.Resource); break; } + serialized["flags"] = flags.ToArray(); - + DateTime tomorrow = evt.Today.AddDays(1); - serialized["starts"] = (evt.Starts >= tomorrow ? evt.Starts.ToShortDateString() + " " : "") + evt.Starts.ToShortTimeString(); - serialized["ends"] = (evt.Ends >= tomorrow ? evt.Ends.ToShortDateString() + " " : "") + evt.Ends.ToShortTimeString(); - + serialized["starts"] = (evt.Starts >= tomorrow ? evt.Starts.ToShortDateString() + " " : "") + + evt.Starts.ToShortTimeString(); + serialized["ends"] = (evt.Ends >= tomorrow ? evt.Ends.ToShortDateString() + " " : "") + + evt.Ends.ToShortTimeString(); + if ((int)evt.Duration.TotalHours > 0) - serialized["duration"] = string.Format(Resources.Outlook_HrsMin, (int)evt.Duration.TotalHours, evt.Duration.Minutes); + serialized["duration"] = string.Format(Resources.Outlook_HrsMin, (int)evt.Duration.TotalHours, + evt.Duration.Minutes); else serialized["duration"] = string.Format(Resources.Outlook_Min, evt.Duration.Minutes); } + return serialized; } - public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer) + public override object Deserialize(IDictionary dictionary, Type type, + JavaScriptSerializer serializer) { throw new NotImplementedException(); } @@ -564,7 +458,7 @@ public override object Deserialize(IDictionary dictionary, Type #region Private members private static Dictionary _sens; - private static Dictionary _showAs; + private static Dictionary _showAs; private static IEnumerable _supportedTypes; private class EventEntryAttribute @@ -578,18 +472,28 @@ static EventEntryConverter() _supportedTypes = new[] { typeof(EventEntry) }; _sens = new Dictionary(4); - _sens.Add(Sensitivity.Normal, new EventEntryAttribute { Flag = "normal", Resource = "EWS_Sensitivity_Normal" }); // 0 - _sens.Add(Sensitivity.Personal, new EventEntryAttribute { Flag = "personal", Resource = "EWS_Sensitivity_Personal" }); // 1 - _sens.Add(Sensitivity.Private, new EventEntryAttribute { Flag = "private", Resource = "EWS_Sensitivity_Private" }); // 2 - _sens.Add(Sensitivity.Confidential, new EventEntryAttribute { Flag = "confidential", Resource = "EWS_Sensitivity_Confidential" }); // 3 - - _showAs = new Dictionary(6); - _showAs.Add(LegacyFreeBusyStatus.Free, new EventEntryAttribute { Flag = "free", Resource = "EWS_ShowAs_Free" }); // 0 - _showAs.Add(LegacyFreeBusyStatus.Tentative, new EventEntryAttribute { Flag = "tentative", Resource = "EWS_ShowAs_Tentative" }); // 1 - _showAs.Add(LegacyFreeBusyStatus.Busy, new EventEntryAttribute { Flag = "busy", Resource = "EWS_ShowAs_Busy" }); // 2 - _showAs.Add(LegacyFreeBusyStatus.OOF, new EventEntryAttribute { Flag = "oof", Resource = "EWS_ShowAs_Oof" }); // 3 - _showAs.Add(LegacyFreeBusyStatus.WorkingElsewhere, new EventEntryAttribute { Flag = "wew", Resource = "EWS_ShowAs_Wew" }); // 4 - _showAs.Add(LegacyFreeBusyStatus.NoData, new EventEntryAttribute { Flag = "noData", Resource = "EWS_ShowAs_NoData" }); // 5 + _sens.Add(Sensitivity.Normal, + new EventEntryAttribute { Flag = "normal", Resource = "EWS_Sensitivity_Normal" }); // 0 + _sens.Add(Sensitivity.Personal, + new EventEntryAttribute { Flag = "personal", Resource = "EWS_Sensitivity_Personal" }); // 1 + _sens.Add(Sensitivity.Private, + new EventEntryAttribute { Flag = "private", Resource = "EWS_Sensitivity_Private" }); // 2 + _sens.Add(Sensitivity.Confidential, + new EventEntryAttribute { Flag = "confidential", Resource = "EWS_Sensitivity_Confidential" }); // 3 + + _showAs = new Dictionary(6); + _showAs.Add(FreeBusyStatus.Free, + new EventEntryAttribute { Flag = "free", Resource = "EWS_ShowAs_Free" }); // 0 + _showAs.Add(FreeBusyStatus.Tentative, + new EventEntryAttribute { Flag = "tentative", Resource = "EWS_ShowAs_Tentative" }); // 1 + _showAs.Add(FreeBusyStatus.Busy, + new EventEntryAttribute { Flag = "busy", Resource = "EWS_ShowAs_Busy" }); // 2 + _showAs.Add(FreeBusyStatus.Oof, + new EventEntryAttribute { Flag = "oof", Resource = "EWS_ShowAs_Oof" }); // 3 + _showAs.Add(FreeBusyStatus.WorkingElsewhere, + new EventEntryAttribute { Flag = "wew", Resource = "EWS_ShowAs_Wew" }); // 4 + _showAs.Add(FreeBusyStatus.Unknown, + new EventEntryAttribute { Flag = "noData", Resource = "EWS_ShowAs_NoData" }); // 5 } #endregion diff --git a/Presentation/packages.config b/Presentation/packages.config index 1221489..80fe289 100644 --- a/Presentation/packages.config +++ b/Presentation/packages.config @@ -1,7 +1,53 @@  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file