├── .gitignore ├── AccessMailboxAsApp ├── AccessMailboxAsApp.sln └── AccessMailboxAsApp │ ├── AccessMailboxAsApp.csproj │ ├── App_Classes │ ├── AppConfig.cs │ ├── AppHttpRequest.cs │ ├── AppSessionVariables.cs │ ├── AppWebRequest.cs │ └── WebConvert.cs │ ├── App_Start │ ├── BundleConfig.cs │ ├── FilterConfig.cs │ └── RouteConfig.cs │ ├── Content │ ├── Site.css │ ├── bootstrap.css │ └── bootstrap.min.css │ ├── Controllers │ └── HomeController.cs │ ├── Global.asax │ ├── Global.asax.cs │ ├── Models │ └── AppState.cs │ ├── Project_Readme.html │ ├── Properties │ └── AssemblyInfo.cs │ ├── Scripts │ ├── _references.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── jquery-1.10.2.intellisense.js │ ├── jquery-1.10.2.js │ ├── jquery-1.10.2.min.js │ ├── jquery-1.10.2.min.map │ ├── jquery.validate-vsdoc.js │ ├── jquery.validate.js │ ├── jquery.validate.min.js │ ├── jquery.validate.unobtrusive.js │ ├── jquery.validate.unobtrusive.min.js │ ├── modernizr-2.6.2.js │ ├── respond.js │ └── respond.min.js │ ├── Views │ ├── Home │ │ ├── About.cshtml │ │ ├── Contact.cshtml │ │ └── index.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ └── _Layout.cshtml │ ├── Web.config │ └── _ViewStart.cshtml │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Web.config │ ├── favicon.ico │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff │ └── packages.config ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Ignore X509 certificates with private keys 5 | *.pfx 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.sln.docstates 11 | 12 | # Build results 13 | [Dd]ebug/ 14 | [Dd]ebugPublic/ 15 | [Rr]elease/ 16 | [Rr]eleases/ 17 | x64/ 18 | x86/ 19 | build/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Roslyn cache directories 25 | *.ide/ 26 | 27 | # MSTest test Results 28 | [Tt]est[Rr]esult*/ 29 | [Bb]uild[Ll]og.* 30 | 31 | #NUNIT 32 | *.VisualState.xml 33 | TestResult.xml 34 | 35 | # Build Results of an ATL Project 36 | [Dd]ebugPS/ 37 | [Rr]eleasePS/ 38 | dlldata.c 39 | 40 | *_i.c 41 | *_p.c 42 | *_i.h 43 | *.ilk 44 | *.meta 45 | *.obj 46 | *.pch 47 | *.pdb 48 | *.pgc 49 | *.pgd 50 | *.rsp 51 | *.sbr 52 | *.tlb 53 | *.tli 54 | *.tlh 55 | *.tmp 56 | *.tmp_proj 57 | *.log 58 | *.vspscc 59 | *.vssscc 60 | .builds 61 | *.pidb 62 | *.svclog 63 | *.scc 64 | 65 | # Chutzpah Test files 66 | _Chutzpah* 67 | 68 | # Visual C++ cache files 69 | ipch/ 70 | *.aps 71 | *.ncb 72 | *.opensdf 73 | *.sdf 74 | *.cachefile 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | *.vspx 80 | 81 | # TFS 2012 Local Workspace 82 | $tf/ 83 | 84 | # Guidance Automation Toolkit 85 | *.gpState 86 | 87 | # ReSharper is a .NET coding add-in 88 | _ReSharper*/ 89 | *.[Rr]e[Ss]harper 90 | *.DotSettings.user 91 | 92 | # JustCode is a .NET coding addin-in 93 | .JustCode 94 | 95 | # TeamCity is a build add-in 96 | _TeamCity* 97 | 98 | # DotCover is a Code Coverage Tool 99 | *.dotCover 100 | 101 | # NCrunch 102 | _NCrunch_* 103 | .*crunch*.local.xml 104 | 105 | # MightyMoose 106 | *.mm.* 107 | AutoTest.Net/ 108 | 109 | # Web workbench (sass) 110 | .sass-cache/ 111 | 112 | # Installshield output folder 113 | [Ee]xpress/ 114 | 115 | # DocProject is a documentation generator add-in 116 | DocProject/buildhelp/ 117 | DocProject/Help/*.HxT 118 | DocProject/Help/*.HxC 119 | DocProject/Help/*.hhc 120 | DocProject/Help/*.hhk 121 | DocProject/Help/*.hhp 122 | DocProject/Help/Html2 123 | DocProject/Help/html 124 | 125 | # Click-Once directory 126 | publish/ 127 | 128 | # Publish Web Output 129 | *.[Pp]ublish.xml 130 | *.azurePubxml 131 | # TODO: Comment the next line if you want to checkin your web deploy settings 132 | # but database connection strings (with potential passwords) will be unencrypted 133 | *.pubxml 134 | *.publishproj 135 | 136 | # NuGet Packages 137 | *.nupkg 138 | # The packages folder can be ignored because of Package Restore 139 | **/packages/* 140 | # except build/, which is used as an MSBuild target. 141 | !**/packages/build/ 142 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 143 | #!**/packages/repositories.config 144 | 145 | # Windows Azure Build Output 146 | csx/ 147 | *.build.csdef 148 | 149 | # Windows Store app package directory 150 | AppPackages/ 151 | 152 | # Others 153 | sql/ 154 | *.Cache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | 165 | # RIA/Silverlight projects 166 | Generated_Code/ 167 | 168 | # Backup & report files from converting an old project file 169 | # to a newer Visual Studio version. Backup files are not needed, 170 | # because we have git ;-) 171 | _UpgradeReport_Files/ 172 | Backup*/ 173 | UpgradeLog*.XML 174 | UpgradeLog*.htm 175 | 176 | # SQL Server files 177 | *.mdf 178 | *.ldf 179 | 180 | # Business Intelligence projects 181 | *.rdl.data 182 | *.bim.layout 183 | *.bim_*.settings 184 | 185 | # Microsoft Fakes 186 | FakesAssemblies/ 187 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30501.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccessMailboxAsApp", "AccessMailboxAsApp\AccessMailboxAsApp.csproj", "{D9610C85-1DB6-4F3D-BB76-B6B2891BD52B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D9610C85-1DB6-4F3D-BB76-B6B2891BD52B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {D9610C85-1DB6-4F3D-BB76-B6B2891BD52B}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {D9610C85-1DB6-4F3D-BB76-B6B2891BD52B}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {D9610C85-1DB6-4F3D-BB76-B6B2891BD52B}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/AccessMailboxAsApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {D9610C85-1DB6-4F3D-BB76-B6B2891BD52B} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | AccessMailboxAsApp 15 | AccessMailboxAsApp 16 | v4.5 17 | false 18 | true 19 | 44304 20 | 21 | 22 | 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\ 29 | TRACE;DEBUG;BUGBUG 30 | prompt 31 | 4 32 | 33 | 34 | pdbonly 35 | true 36 | bin\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | 43 | 44 | ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.12.111071459\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll 45 | 46 | 47 | ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.12.111071459\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ..\packages\System.IdentityModel.Tokens.Jwt.4.0.1\lib\net45\System.IdentityModel.Tokens.Jwt.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | True 74 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 75 | 76 | 77 | 78 | 79 | 80 | 81 | True 82 | ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.Helpers.dll 83 | 84 | 85 | True 86 | ..\packages\Microsoft.AspNet.Mvc.5.1.2\lib\net45\System.Web.Mvc.dll 87 | 88 | 89 | ..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll 90 | 91 | 92 | True 93 | ..\packages\Microsoft.AspNet.Razor.3.1.2\lib\net45\System.Web.Razor.dll 94 | 95 | 96 | True 97 | ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.dll 98 | 99 | 100 | True 101 | ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Deployment.dll 102 | 103 | 104 | True 105 | ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Razor.dll 106 | 107 | 108 | True 109 | ..\packages\WebGrease.1.5.2\lib\WebGrease.dll 110 | 111 | 112 | True 113 | ..\packages\Antlr.3.4.1.9004\lib\Antlr3.Runtime.dll 114 | 115 | 116 | 117 | 118 | ..\packages\Newtonsoft.Json.5.0.6\lib\net45\Newtonsoft.Json.dll 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | Global.asax 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | Always 148 | 149 | 150 | Always 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | Designer 167 | 168 | 169 | Web.config 170 | 171 | 172 | Web.config 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 10.0 195 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | True 208 | True 209 | 51758 210 | / 211 | http://localhost:51758/ 212 | False 213 | False 214 | 215 | 216 | False 217 | 218 | 219 | 220 | 221 | 227 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/App_Classes/AppConfig.cs: -------------------------------------------------------------------------------- 1 | //Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. 2 | // 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Web; 7 | using System.Configuration; 8 | 9 | namespace AccessMailboxAsApp.App_Classes 10 | { 11 | public static class AppConfigSettings 12 | { 13 | public const string ClientId = "ClientId"; 14 | public const string ClientSecret = "ClientSecret"; 15 | public const string ClientCertificatePfx = "ClientCertificatePfx"; 16 | public const string ClientCertificatePfxPassword = "ClientCertificatePfxPassword"; 17 | public const string AuthorizationUri = "AuthorizationUri"; 18 | public const string TokenIssueingUri = "TokenIssueingUri"; 19 | public const string RedirectUri = "RedirectUri"; 20 | public const string RedirectUriLocalHost = "RedirectUriLocalHost"; 21 | public const string ExchangeResourceUri = "ExchangeResourceUri"; 22 | public const string GraphResourceUri = "GraphResourceUri"; 23 | public const string SignoutUri = "SignoutUri"; 24 | public const string DebugOffice365User = "DebugOffice365User"; 25 | } 26 | 27 | public class AppConfig 28 | { 29 | public string ClientId { get; set; } 30 | public string ClientSecret { get; set; } 31 | public string ClientCertificatePfx { get; set; } 32 | public string ClientCertificatePfxPassword { get; set; } 33 | public string AuthorizationUri { get; set; } 34 | public string TokenIssueingUri { get; set; } 35 | public string SignoutUri { get; set; } 36 | public string RedirectUri { get; set; } 37 | public string ExchangeResourceUri { get; set; } 38 | public string GraphResourceUri { get; set; } 39 | public string DebugOffice365User { get; set; } 40 | 41 | public AppConfig() 42 | { 43 | this.ClientId = this.Read(AppConfigSettings.ClientId); 44 | this.ClientSecret = this.Read(AppConfigSettings.ClientSecret); 45 | this.AuthorizationUri = this.Read(AppConfigSettings.AuthorizationUri); 46 | this.TokenIssueingUri = this.Read(AppConfigSettings.TokenIssueingUri); 47 | this.SignoutUri = this.Read(AppConfigSettings.SignoutUri); 48 | this.RedirectUri = this.Read( 49 | #if DEBUG 50 | AppConfigSettings.RedirectUriLocalHost 51 | #else 52 | AppConfigSettings.RedirectUri 53 | #endif 54 | ); 55 | this.ExchangeResourceUri = this.Read(AppConfigSettings.ExchangeResourceUri); 56 | this.GraphResourceUri = this.Read(AppConfigSettings.GraphResourceUri); 57 | this.ClientCertificatePfx = this.Read(AppConfigSettings.ClientCertificatePfx); 58 | this.ClientCertificatePfxPassword = this.Read(AppConfigSettings.ClientCertificatePfxPassword); 59 | this.DebugOffice365User = this.Read(AppConfigSettings.DebugOffice365User); 60 | } 61 | 62 | private string Read(string appSettingName) 63 | { 64 | string res = String.Empty; 65 | 66 | try 67 | { // fails if null or empty 68 | string appSetting = ConfigurationManager.AppSettings[appSettingName]; 69 | res = appSetting; 70 | } 71 | catch { } 72 | 73 | return res; 74 | } 75 | 76 | 77 | } 78 | } 79 | 80 | // MIT License: 81 | 82 | // Permission is hereby granted, free of charge, to any person obtaining 83 | // a copy of this software and associated documentation files (the 84 | // ""Software""), to deal in the Software without restriction, including 85 | // without limitation the rights to use, copy, modify, merge, publish, 86 | // distribute, sublicense, and/or sell copies of the Software, and to 87 | // permit persons to whom the Software is furnished to do so, subject to 88 | // the following conditions: 89 | 90 | // The above copyright notice and this permission notice shall be 91 | // included in all copies or substantial portions of the Software. 92 | 93 | // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, 94 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 95 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 96 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 97 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 98 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 99 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/App_Classes/AppHttpRequest.cs: -------------------------------------------------------------------------------- 1 | //Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. 2 | // 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Net.Http; 10 | using System.Web; 11 | 12 | namespace AccessMailboxAsApp.App_Classes 13 | { 14 | public class HttpRequestHelper 15 | { 16 | public static string MakeHttpRequest(Func requestCreator, string accessToken) 17 | { 18 | using (HttpClient client = new HttpClient()) 19 | { 20 | // add propper instrumentation headers 21 | string clientRequestId = Guid.NewGuid().ToString(); 22 | client.DefaultRequestHeaders.Add("client-request-id", clientRequestId); 23 | client.DefaultRequestHeaders.Add("return-client-request-id", "true"); 24 | client.DefaultRequestHeaders.Add("UserAgent", "MatthiasLeibmannsAppOnlyAppSampleBeta/0.1"); 25 | client.DefaultRequestHeaders.Add("Authorization", String.Format("Bearer {0}", accessToken)); 26 | 27 | using (HttpRequestMessage request = requestCreator.Invoke()) 28 | { 29 | try 30 | { 31 | HttpResponseMessage httpResponse = client.SendAsync(request).Result; 32 | if (httpResponse.StatusCode == HttpStatusCode.OK) 33 | { 34 | string responseString = httpResponse.Content.ReadAsStringAsync().Result; 35 | return responseString; 36 | } 37 | 38 | string error = string.Format("\"error\" : \"{0}\"", httpResponse.ReasonPhrase); 39 | if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) 40 | { 41 | //httpResponse.Headers.Select(h => h.Key.Equals("x-ms-diagnostics")).First(); 42 | foreach (var header in httpResponse.Headers) 43 | { 44 | // x-ms-diagnostics contains details why request was unauthorized 45 | if (header.Key.Equals("x-ms-diagnostics")) 46 | { 47 | string e = string.Format("{0}", header.Value.ToArray()); 48 | e = e.Replace("\"", "'"); 49 | error = error + string.Format(", \"x-ms-diagnostics\" : \"{0}\"", e); 50 | } 51 | } 52 | } 53 | 54 | string responseString2 = string.Format("{{{0}}}", error); 55 | return responseString2; 56 | 57 | } 58 | catch (WebException webex) 59 | { 60 | HttpWebResponse httpWebResponse = webex.Response as HttpWebResponse; 61 | 62 | if (httpWebResponse != null) 63 | { 64 | using (Stream serviceResponseStream = httpWebResponse.GetResponseStream()) 65 | { 66 | using (StreamReader reader = new StreamReader(serviceResponseStream)) 67 | { 68 | JObject jsonResponse = JObject.Parse(reader.ReadToEnd()); 69 | return jsonResponse.ToString(); 70 | } 71 | } 72 | } 73 | 74 | string error = string.Format("\"error\" : \"{0}\"", webex.Message); 75 | string responseString = string.Format("{{{0}}}", error); 76 | return responseString; 77 | } 78 | catch (Exception ex) 79 | { 80 | string error = string.Format("\"error\" : \"{0}\"", ex.Message); 81 | string responseString = string.Format("{{{0}}}", error); 82 | return responseString; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | // MIT License: 91 | 92 | // Permission is hereby granted, free of charge, to any person obtaining 93 | // a copy of this software and associated documentation files (the 94 | // ""Software""), to deal in the Software without restriction, including 95 | // without limitation the rights to use, copy, modify, merge, publish, 96 | // distribute, sublicense, and/or sell copies of the Software, and to 97 | // permit persons to whom the Software is furnished to do so, subject to 98 | // the following conditions: 99 | 100 | // The above copyright notice and this permission notice shall be 101 | // included in all copies or substantial portions of the Software. 102 | 103 | // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, 104 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 105 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 106 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 107 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 108 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 109 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/App_Classes/AppSessionVariables.cs: -------------------------------------------------------------------------------- 1 | //Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. 2 | // 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Web; 7 | 8 | namespace AccessMailboxAsApp.App_Classes 9 | { 10 | public class AppSessionVariables 11 | { 12 | public const string IsAuthorized = "ISAUTHORIZED"; 13 | public const string MailboxList = "MBXLIST"; 14 | public const string AuthState = "AUTHSTATE"; 15 | } 16 | } 17 | 18 | // MIT License: 19 | 20 | // Permission is hereby granted, free of charge, to any person obtaining 21 | // a copy of this software and associated documentation files (the 22 | // ""Software""), to deal in the Software without restriction, including 23 | // without limitation the rights to use, copy, modify, merge, publish, 24 | // distribute, sublicense, and/or sell copies of the Software, and to 25 | // permit persons to whom the Software is furnished to do so, subject to 26 | // the following conditions: 27 | 28 | // The above copyright notice and this permission notice shall be 29 | // included in all copies or substantial portions of the Software. 30 | 31 | // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, 32 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 33 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 34 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 35 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 36 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 37 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/App_Classes/AppWebRequest.cs: -------------------------------------------------------------------------------- 1 | //Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. 2 | // 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Web; 9 | 10 | namespace AccessMailboxAsApp.App_Classes 11 | { 12 | public class AppKeyValue 13 | { 14 | private string key; 15 | public string Key { get { return key; } } 16 | 17 | private string value; 18 | public string Value { get { return value; } } 19 | 20 | public AppKeyValue(string key, string value) 21 | { 22 | this.key = key; 23 | this.value = value; 24 | } 25 | } 26 | 27 | public class AppWebRequest 28 | { 29 | public StringContent RequestContent { get; set; } 30 | public string Method { get; set; } 31 | public string JsonFormattedResponse { get; set; } 32 | public string Json { get; set; } 33 | public HttpWebResponse httpResponse { get; set; } 34 | public Boolean RequestWasSuccessful { get; set; } 35 | 36 | public void Reset() 37 | { 38 | RequestContent = null; 39 | Method = string.Empty; 40 | JsonFormattedResponse = string.Empty; 41 | Json = string.Empty; 42 | httpResponse = null; 43 | RequestWasSuccessful = false; 44 | } 45 | } 46 | } 47 | 48 | // MIT License: 49 | 50 | // Permission is hereby granted, free of charge, to any person obtaining 51 | // a copy of this software and associated documentation files (the 52 | // ""Software""), to deal in the Software without restriction, including 53 | // without limitation the rights to use, copy, modify, merge, publish, 54 | // distribute, sublicense, and/or sell copies of the Software, and to 55 | // permit persons to whom the Software is furnished to do so, subject to 56 | // the following conditions: 57 | 58 | // The above copyright notice and this permission notice shall be 59 | // included in all copies or substantial portions of the Software. 60 | 61 | // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, 62 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 63 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 64 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 65 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 66 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 67 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/App_Classes/WebConvert.cs: -------------------------------------------------------------------------------- 1 | //Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. 2 | // 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Web; 8 | 9 | namespace AccessMailboxAsApp.App_Classes 10 | { 11 | public static class WebConvert 12 | { 13 | private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 14 | 15 | public static Int64 EpocTime(DateTime time) 16 | { 17 | TimeSpan timeSpan = time - UnixEpoch; 18 | 19 | return ((Int64)timeSpan.TotalSeconds); 20 | } 21 | 22 | public static string Base64UrlDecode(string value) 23 | { 24 | byte[] valueBytes = Base64UrlDecodeToBytes(value); 25 | return Encoding.UTF8.GetString(valueBytes); 26 | } 27 | 28 | public static byte[] Base64UrlDecodeToBytes(string value) 29 | { 30 | value = value.Replace('-', '+').Replace('_', '/'); 31 | 32 | switch (value.Length % 4) 33 | { 34 | case 0: 35 | break; 36 | case 1: 37 | throw new ArgumentException("Value has an invalid length"); 38 | case 2: 39 | value += "=="; 40 | break; 41 | case 3: 42 | value += "="; 43 | break; 44 | } 45 | 46 | return Convert.FromBase64String(value); 47 | } 48 | 49 | public static string Base64UrlEncoded(byte[] data) 50 | { 51 | string base64String = Convert.ToBase64String(data); 52 | return base64String.Replace('+', '-').Replace('/', '_').TrimEnd('='); 53 | } 54 | 55 | public static string Base64UrlEncoded(string value) 56 | { 57 | byte[] valueBytes = Encoding.UTF8.GetBytes(value); 58 | return Base64UrlEncoded(valueBytes); 59 | } 60 | 61 | public static byte[] ConvertToBigEndian(Int32 i) 62 | { 63 | byte[] temp = BitConverter.GetBytes(i); 64 | 65 | if (BitConverter.IsLittleEndian) 66 | { 67 | Array.Reverse(temp); 68 | } 69 | 70 | return temp; 71 | } 72 | 73 | public static byte[] HexStringToBytes(string value) 74 | { 75 | char[] chars = value.ToCharArray(); 76 | byte[] bytes = new byte[value.Length / 2]; 77 | 78 | for (int i = 0; i < chars.Length / 2; i++) 79 | { 80 | string hexChar = value.Substring(i * 2, 2); 81 | 82 | bytes[i] = (byte)Convert.ToInt32(hexChar, 16); 83 | } 84 | 85 | return bytes; 86 | } 87 | } 88 | } 89 | 90 | // MIT License: 91 | 92 | // Permission is hereby granted, free of charge, to any person obtaining 93 | // a copy of this software and associated documentation files (the 94 | // ""Software""), to deal in the Software without restriction, including 95 | // without limitation the rights to use, copy, modify, merge, publish, 96 | // distribute, sublicense, and/or sell copies of the Software, and to 97 | // permit persons to whom the Software is furnished to do so, subject to 98 | // the following conditions: 99 | 100 | // The above copyright notice and this permission notice shall be 101 | // included in all copies or substantial portions of the Software. 102 | 103 | // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, 104 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 105 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 106 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 107 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 108 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 109 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Optimization; 3 | 4 | namespace AccessMailboxAsApp 5 | { 6 | public class BundleConfig 7 | { 8 | // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862 9 | public static void RegisterBundles(BundleCollection bundles) 10 | { 11 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 12 | "~/Scripts/jquery-{version}.js")); 13 | 14 | bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( 15 | "~/Scripts/jquery.validate*")); 16 | 17 | // Use the development version of Modernizr to develop with and learn from. Then, when you're 18 | // ready for production, use the build tool at http://modernizr.com to pick only the tests you need. 19 | bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( 20 | "~/Scripts/modernizr-*")); 21 | 22 | bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( 23 | "~/Scripts/bootstrap.js", 24 | "~/Scripts/respond.js")); 25 | 26 | bundles.Add(new StyleBundle("~/Content/css").Include( 27 | "~/Content/bootstrap.css", 28 | "~/Content/site.css")); 29 | 30 | // Set EnableOptimizations to false for debugging. For more information, 31 | // visit http://go.microsoft.com/fwlink/?LinkId=301862 32 | BundleTable.EnableOptimizations = true; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | 4 | namespace AccessMailboxAsApp 5 | { 6 | public class FilterConfig 7 | { 8 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 9 | { 10 | filters.Add(new HandleErrorAttribute()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace AccessMailboxAsApp 9 | { 10 | public class RouteConfig 11 | { 12 | public static void RegisterRoutes(RouteCollection routes) 13 | { 14 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 | 16 | routes.MapRoute( 17 | name: "Default", 18 | url: "{controller}/{action}/{id}", 19 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 20 | ); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Content/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Set padding to keep content from hitting the edges */ 7 | .body-content { 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | 12 | /* Set width on the form input elements since they're 100% wide by default */ 13 | input, 14 | select, 15 | textarea { 16 | max-width: 280px; 17 | } 18 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | //Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. 2 | // 3 | using AccessMailboxAsApp.App_Classes; 4 | using AccessMailboxAsApp.Models; 5 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Security.Cryptography.X509Certificates; 10 | using System.Web; 11 | using System.Web.Mvc; 12 | using System.Reflection; 13 | using System.Threading.Tasks; 14 | using System.Text.RegularExpressions; 15 | using Newtonsoft.Json; 16 | using Newtonsoft.Json.Linq; 17 | using System.Net.Http; 18 | using System.Text; 19 | using System.Globalization; 20 | using System.Security.Cryptography; 21 | using System.IdentityModel.Tokens; 22 | using System.Net.Http.Headers; 23 | using System.IdentityModel.Protocols.WSTrust; 24 | using System.Security.Claims; 25 | 26 | namespace AccessMailboxAsApp.Controllers 27 | { 28 | // From: Jason Johnston@https://github.com/jasonjoh/office365-azure-guides/blob/master/code/parse-token.cs 29 | static class Base64UrlEncoder 30 | { 31 | static char Base64PadCharacter = '='; 32 | static string DoubleBase64PadCharacter = String.Format(CultureInfo.InvariantCulture, "{0}{0}", Base64PadCharacter); 33 | static char Base64Character62 = '+'; 34 | static char Base64Character63 = '/'; 35 | static char Base64UrlCharacter62 = '-'; 36 | static char Base64UrlCharacter63 = '_'; 37 | 38 | public static byte[] DecodeBytes(string arg) 39 | { 40 | string s = arg; 41 | s = s.Replace(Base64UrlCharacter62, Base64Character62); // 62nd char of encoding 42 | s = s.Replace(Base64UrlCharacter63, Base64Character63); // 63rd char of encoding 43 | switch (s.Length % 4) // Pad 44 | { 45 | case 0: 46 | break; // No pad chars in this case 47 | case 2: 48 | s += DoubleBase64PadCharacter; break; // Two pad chars 49 | case 3: 50 | s += Base64PadCharacter; break; // One pad char 51 | default: 52 | throw new ArgumentException("Illegal base64url string!", arg); 53 | } 54 | return Convert.FromBase64String(s); // Standard base64 decoder 55 | } 56 | 57 | public static string Decode(string arg) 58 | { 59 | return Encoding.UTF8.GetString(DecodeBytes(arg)); 60 | } 61 | } 62 | 63 | public class HomeController : Controller 64 | { 65 | private static AppConfig appConfig = new AppConfig(); 66 | 67 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 68 | public class MultipleButtonAttribute : ActionNameSelectorAttribute 69 | { 70 | public string Name { get; set; } 71 | public string Argument { get; set; } 72 | 73 | public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo) 74 | { 75 | var isValidName = false; 76 | var keyValue = string.Format("{0}:{1}", Name, Argument); 77 | var value = controllerContext.Controller.ValueProvider.GetValue(keyValue); 78 | 79 | if (value != null) 80 | { 81 | controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument; 82 | isValidName = true; 83 | } 84 | 85 | return isValidName; 86 | } 87 | } 88 | 89 | // 90 | // GET: /Home/ 91 | public async Task Index() 92 | { 93 | // Force SSL 94 | if (!Request.IsSecureConnection) 95 | { 96 | string httplength = "http"; 97 | string nonsecureurl = Request.Url.AbsoluteUri.Substring(httplength.Length); 98 | string secureurl = String.Format("https{0}", nonsecureurl); 99 | RedirectResult result = Redirect(secureurl); 100 | result.ExecuteResult(this.ControllerContext); 101 | } 102 | 103 | // This is where state of the app is maintained and data passed between view and controller 104 | AppState appState = new AppState(); 105 | 106 | // Authorization back from AAD in a form post as requested in the authorize request 107 | if(Request.Form != null) 108 | { 109 | // Did it return with an error? 110 | if (!String.IsNullOrEmpty(Request.Form["error"])) 111 | { 112 | appState.ErrorMessage = Request.Form["error"]; 113 | appState.AppIsAuthorized = false; 114 | 115 | return View(appState); 116 | } 117 | 118 | // Authorized without error: Check to see if we have an ID token 119 | if (String.IsNullOrEmpty(Request.Form["id_token"])) 120 | { 121 | appState.AppIsAuthorized = false; 122 | } 123 | else 124 | { 125 | // Was it correlated with authorize request 126 | var authstate = Session[AppSessionVariables.AuthState] as String; 127 | Session[AppSessionVariables.AuthState] = null; 128 | if (String.IsNullOrEmpty(authstate)) 129 | { 130 | appState.ErrorMessage = "Oops. Something went wrong with the authorization state (No auth state). Please retry."; 131 | appState.AppIsAuthorized = false; 132 | 133 | return View(appState); 134 | } 135 | if (!Request.Form["state"].Equals(authstate)) 136 | { 137 | appState.ErrorMessage = "Oops. Something went wrong with the authorization state (Invalid auth state). Please retry."; 138 | appState.AppIsAuthorized = false; 139 | 140 | return View(appState); 141 | } 142 | 143 | // Get the TenantId out of the ID Token to address tenant specific token endpoint. 144 | // No validation of ID Token as the only info we need is the tenantID 145 | // If for any case your app wants to use the ID Token to authenticate 146 | // it must be validated. 147 | JwtToken openIDToken = GetTenantId(Request.Form["id_token"]); 148 | appState.TenantId = openIDToken.tid; 149 | appState.TenantDomain = openIDToken.domain; 150 | appState.LoggedOnUser = openIDToken.upn; 151 | 152 | appState.AppIsAuthorized = true; 153 | } 154 | } 155 | 156 | if(appState.AppIsAuthorized) 157 | { 158 | // Get app-only access tokens .... 159 | try 160 | { 161 | // Get an app-only access token for the AAD Graph Rest APIs 162 | var authResult = await GetAppOnlyAccessToken(appConfig.GraphResourceUri, appState.TenantId); 163 | 164 | // Get list of users in this Tenant from AAD Graph to fill drop down with smtp addresses 165 | List users = GetUsers(appState.TenantId, authResult.AccessToken); 166 | 167 | appState.MailboxList = this.BuildMailboxDropDownList(string.Empty, users); 168 | appState.MailboxSmtpAddress = appState.MailboxList.SelectedValue as String; 169 | 170 | // For convenience maintain this list as a session 171 | Session[AppSessionVariables.MailboxList] = users; 172 | 173 | // Get app-only access tokens for Exchange Rest APIs 174 | authResult = await GetAppOnlyAccessToken(appConfig.ExchangeResourceUri, appState.TenantId); 175 | 176 | appState.AccessToken = authResult.AccessToken; 177 | appState.AccessTokenAquiredWithoutError = true; 178 | 179 | SetSessionInProgress(); 180 | } 181 | catch(Exception ex) 182 | { 183 | appState.ErrorMessage = ex.Message; 184 | } 185 | } 186 | 187 | return View(appState); 188 | } 189 | 190 | private void SetSessionInProgress() 191 | { 192 | Session[AppSessionVariables.IsAuthorized] = true; 193 | } 194 | 195 | private bool IsSessionInProgress() 196 | { 197 | bool? inprogress = Session[AppSessionVariables.IsAuthorized] as bool?; 198 | if (null == inprogress) 199 | return false; 200 | 201 | return (bool) inprogress; 202 | } 203 | 204 | private ViewResult RedirectHome() 205 | { 206 | RedirectResult result = Redirect(appConfig.RedirectUri); 207 | result.ExecuteResult(this.ControllerContext); 208 | 209 | return View("Index", new AppState()); 210 | } 211 | 212 | private SelectList BuildMailboxDropDownList(string selectedSmtp, List users) 213 | { 214 | string selectedValue = ""; 215 | List items = new List(); 216 | foreach (GraphUser u in users) 217 | { 218 | if (!string.IsNullOrEmpty(selectedSmtp)) 219 | { 220 | if (selectedSmtp.Equals(u.mail)) 221 | { 222 | selectedValue = u.mail; 223 | } 224 | } 225 | else 226 | { 227 | selectedValue = selectedSmtp = u.mail; 228 | } 229 | if (IsValidSmtp(u.mail)) 230 | { 231 | items.Add( 232 | new SelectListItem 233 | { 234 | Text = u.mail, 235 | Value = u.mail, 236 | } 237 | ); 238 | } 239 | } 240 | 241 | SelectList selectList = new SelectList( 242 | items, "Value", "Text", selectedValue); 243 | 244 | return selectList; 245 | } 246 | 247 | public class GraphUsers 248 | { 249 | public List data { get; set; } 250 | } 251 | 252 | public class GraphUser 253 | { 254 | public string objectId; 255 | public string mail; 256 | } 257 | 258 | private List GetUsers(string tenantId, string accessToken) 259 | { 260 | string api = String.Format("{0}{1}/users?api-version=2013-04-05", appConfig.GraphResourceUri, tenantId); 261 | 262 | Func requestCreator = () => 263 | { 264 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, api); 265 | request.Headers.TryAddWithoutValidation("Content-Type", "application/json"); 266 | return request; 267 | }; 268 | 269 | string json = HttpRequestHelper.MakeHttpRequest(requestCreator, accessToken); 270 | var data = JObject.Parse(json).SelectToken("value"); 271 | List users = JsonConvert.DeserializeObject>(data.ToString()); 272 | 273 | return users; 274 | } 275 | 276 | private bool IsValidSmtp(string smtp) 277 | { 278 | if(string.IsNullOrEmpty(smtp)) 279 | return false; 280 | 281 | // Copied from: http://www.regular-expressions.info/email.html 282 | string smtpRegexPattern = @"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$"; 283 | Regex rgx = new Regex(smtpRegexPattern, RegexOptions.IgnoreCase); 284 | MatchCollection matches = rgx.Matches(smtp); 285 | if (matches.Count == 0) 286 | return false; 287 | 288 | return true; 289 | } 290 | 291 | private void MakeExchangeRestApiRequest(string api, ref AppState passedAppState) 292 | { 293 | if (IsValidSmtp(passedAppState.MailboxSmtpAddress)) 294 | { 295 | passedAppState.Request = api; 296 | 297 | Func requestCreator = () => 298 | { 299 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, api); 300 | request.Headers.Add("Accept", "application/json; odata.metadata=none"); 301 | return request; 302 | }; 303 | 304 | string json = HttpRequestHelper.MakeHttpRequest(requestCreator, passedAppState.AccessToken); 305 | passedAppState.Response = JObject.Parse(json).ToString(/*Newtonsoft.Json.Formatting.Indented*/); 306 | } 307 | else 308 | { 309 | passedAppState.ErrorMessage = "Invalid SMTP"; 310 | } 311 | 312 | List users = Session[AppSessionVariables.MailboxList] as List; 313 | passedAppState.MailboxList = BuildMailboxDropDownList(passedAppState.MailboxSmtpAddress, users); 314 | } 315 | 316 | [HttpPost] 317 | [MultipleButton(Name = "action", Argument = "Messages")] 318 | public ActionResult GetMessages(AppState passedAppState) 319 | { 320 | if (!IsSessionInProgress()) 321 | { 322 | return RedirectHome(); 323 | } 324 | 325 | string api = String.Format("{0}api/v1.0/users('{1}')/folders/inbox/messages?$top=50", appConfig.ExchangeResourceUri, passedAppState.MailboxSmtpAddress); 326 | 327 | MakeExchangeRestApiRequest(api, ref passedAppState); 328 | 329 | return View("Index", passedAppState); 330 | } 331 | 332 | [HttpPost] 333 | [MultipleButton(Name = "action", Argument = "Calendar")] 334 | public ActionResult GetEvents(AppState passedAppState) 335 | { 336 | if (!IsSessionInProgress()) 337 | { 338 | return RedirectHome(); 339 | } 340 | 341 | string api = String.Format("{0}api/v1.0/users('{1}')/events?$top=50", appConfig.ExchangeResourceUri, passedAppState.MailboxSmtpAddress); 342 | 343 | MakeExchangeRestApiRequest(api, ref passedAppState); 344 | 345 | return View("Index", passedAppState); 346 | } 347 | 348 | [HttpPost] 349 | [MultipleButton(Name = "action", Argument = "Contacts")] 350 | public ActionResult GetContacts(AppState passedAppState) 351 | { 352 | if (!IsSessionInProgress()) 353 | { 354 | return RedirectHome(); 355 | } 356 | 357 | string api = String.Format("{0}api/v1.0/users('{1}')/contacts?$top=50", appConfig.ExchangeResourceUri, passedAppState.MailboxSmtpAddress); 358 | 359 | MakeExchangeRestApiRequest(api, ref passedAppState); 360 | 361 | return View("Index", passedAppState); 362 | } 363 | 364 | [HttpPost] 365 | [MultipleButton(Name = "action", Argument = "StartOver")] 366 | public ActionResult StartOver(AppState passedAppState) 367 | { 368 | if (!IsSessionInProgress()) 369 | { 370 | return RedirectHome(); 371 | } 372 | 373 | AppState appState = new AppState(); 374 | 375 | Session.Clear(); 376 | 377 | UriBuilder signOutRequest = new UriBuilder(appConfig.SignoutUri.Replace("common", passedAppState.TenantId)); 378 | 379 | signOutRequest.Query = "post_logout_redirect_uri=" + HttpUtility.UrlEncode(appConfig.RedirectUri); 380 | 381 | RedirectResult result = Redirect(signOutRequest.Uri.ToString()); 382 | result.ExecuteResult(this.ControllerContext); 383 | 384 | return View("Index", appState); 385 | } 386 | 387 | [HttpPost] 388 | [MultipleButton(Name = "action", Argument = "Authorize")] 389 | public ActionResult Auhorize(AppState passedAppState) 390 | { 391 | passedAppState.AppIsAuthorized = false; 392 | 393 | // hit the common endpoint for authorization, 394 | // after authorization we will use the tenant specific endpoint for getting app-only tokens 395 | UriBuilder authorizeRequest = new UriBuilder(appConfig.AuthorizationUri); 396 | 397 | // Maintain state for authorize request to prvenet cross forgery attacks 398 | var authstate = Guid.NewGuid().ToString(); 399 | Session[AppSessionVariables.AuthState] = authstate; 400 | 401 | authorizeRequest.Query = 402 | "state=" + authstate + 403 | "&response_type=code+id_token" + 404 | "&scope=openid" + 405 | "&nonce=" + Guid.NewGuid().ToString() + 406 | "&client_id=" + appConfig.ClientId + 407 | "&redirect_uri=" + HttpUtility.UrlEncode(appConfig.RedirectUri) + 408 | "&resource=" + HttpUtility.UrlEncode(appConfig.GraphResourceUri) + 409 | #if DEBUG 410 | "&login_hint=" + appConfig.DebugOffice365User + 411 | #endif 412 | "&prompt=admin_consent" + 413 | "&response_mode=form_post"; 414 | 415 | RedirectResult result = Redirect(authorizeRequest.Uri.ToString()); 416 | result.ExecuteResult(this.ControllerContext); 417 | 418 | return View("Index", passedAppState); 419 | } 420 | 421 | private string Base64UrlDecodeJwtTokenPayload(string base64UrlEncodedJwtToken) 422 | { 423 | string payload = base64UrlEncodedJwtToken.Split('.')[1]; 424 | 425 | return Base64UrlEncoder.Decode(payload); 426 | } 427 | 428 | public class JwtToken 429 | { 430 | public string tid { get; set; } 431 | public string upn { get; set; } 432 | public string domain { get { return (string.IsNullOrEmpty(upn)) ? "string.Empty" : upn.Split('@')[1]; } } 433 | } 434 | 435 | private JwtToken GetTenantId(string id_token) 436 | { 437 | string encodedOpenIdToken = id_token; 438 | 439 | string decodedToken = Base64UrlDecodeJwtTokenPayload(encodedOpenIdToken); 440 | 441 | JwtToken token = JsonConvert.DeserializeObject(decodedToken); 442 | 443 | return token; 444 | } 445 | 446 | private async Task GetAppOnlyAccessToken(string resource, string tenantId) 447 | { 448 | string authority = appConfig.AuthorizationUri.Replace("common", tenantId); 449 | Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authenticationContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext( 450 | authority, 451 | false); 452 | 453 | string certfile = Server.MapPath(appConfig.ClientCertificatePfx); 454 | 455 | X509Certificate2 cert = new X509Certificate2( 456 | certfile, 457 | appConfig.ClientCertificatePfxPassword, 458 | X509KeyStorageFlags.MachineKeySet); 459 | 460 | // ADAL new ... (in Beta, might change) 461 | // ClientAssertionCertificate cac = new ClientAssertionCertificate( 462 | // appConfig.ClientId, 463 | // cert.GetRawCertData(), 464 | // appConfig.ClientCertificatePfxPassword); 465 | 466 | // ADAL current released (2.12.111071459) 467 | ClientAssertionCertificate cac = new ClientAssertionCertificate( 468 | appConfig.ClientId, cert); 469 | 470 | var authenticationResult = await authenticationContext.AcquireTokenAsync( 471 | resource, 472 | cac); 473 | 474 | return authenticationResult; 475 | } 476 | 477 | 478 | /** 479 | * Below is only to better understand how a client assertion can be build and an access token request be submitted via straight HTTP. 480 | * It is by no means meant for production or to replace what ADAL provides. If you develop in .Net you should use ADAL. 481 | */ 482 | 483 | // Copied from: 484 | // https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/blob/master/src/ADAL.NET/CryptographyHelper.cs 485 | // This method returns an AsymmetricSignatureFormatter capable of supporting Sha256 signatures. 486 | private static RSACryptoServiceProvider GetCryptoProviderForSha256(RSACryptoServiceProvider rsaProvider) 487 | { 488 | const int PROV_RSA_AES = 24; // CryptoApi provider type for an RSA provider supporting sha-256 digital signatures 489 | if (rsaProvider.CspKeyContainerInfo.ProviderType == PROV_RSA_AES) 490 | { 491 | return rsaProvider; 492 | } 493 | 494 | CspParameters csp = new CspParameters 495 | { 496 | ProviderType = PROV_RSA_AES, 497 | KeyContainerName = rsaProvider.CspKeyContainerInfo.KeyContainerName, 498 | KeyNumber = (int)rsaProvider.CspKeyContainerInfo.KeyNumber 499 | }; 500 | 501 | if (rsaProvider.CspKeyContainerInfo.MachineKeyStore) 502 | { 503 | csp.Flags = CspProviderFlags.UseMachineKeyStore; 504 | } 505 | 506 | // 507 | // If UseExistingKey is not specified, the CLR will generate a key for a non-existent group. 508 | // With this flag, a CryptographicException is thrown instead. 509 | // 510 | csp.Flags |= CspProviderFlags.UseExistingKey; 511 | return new RSACryptoServiceProvider(csp); 512 | } 513 | 514 | public class AADClientCredentialSuccessResponse 515 | { 516 | //{ 517 | // "token_type": "Bearer", 518 | // "expires_in": "3600", 519 | // "expires_on": "1423336547", 520 | // "not_before": "1423332647", 521 | // "resource": "https://outlook.office365.com/", 522 | // "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciO....ZVvynkUXjZPNg1oJWDKBymPL-U0WA" 523 | //} 524 | public string token_type; 525 | public string expires_in; 526 | public string expires_on; 527 | public string not_before; 528 | public string resource; 529 | public string access_token; 530 | }; 531 | 532 | public class AADClientCredentialErrorResponse 533 | { 534 | //{ 535 | // "error": "invalid_client", 536 | // "error_description": "AADSTS70002: Error ...", 537 | // "error_codes": [ 538 | // 70002, 539 | // 50012 540 | // ], 541 | // "timestamp": "2015-02-07 18:44:09Z", 542 | // "trace_id": "dabcfa26-ea8d-46c5-81bc-ff57a0895629", 543 | // "correlation_id": "8e270f2d-ba05-42fb-a7ab-e819d142c843", 544 | // "submit_url": null, 545 | // "context": null 546 | //} 547 | public string error; 548 | public string error_description; 549 | public string[] error_codes; 550 | public string timestamp; 551 | public string trace_id; 552 | public string correlation_id; 553 | public string submit_url; 554 | public string context; 555 | } 556 | 557 | // Get the access token via straight http post request doing client credential flow 558 | private async Task GetAppOnlyAccessTokenWithHttpRequest(string resource, string tenantId) 559 | { 560 | /** 561 | * use the tenant specific endpoint for requesting the app-only access token 562 | */ 563 | string tokenIssueEndpoint = appConfig.TokenIssueingUri.Replace("common", tenantId); 564 | 565 | /** 566 | * sign the assertion with the private key 567 | */ 568 | string certfile = Server.MapPath(appConfig.ClientCertificatePfx); 569 | X509Certificate2 cert = new X509Certificate2( 570 | certfile, 571 | appConfig.ClientCertificatePfxPassword, 572 | X509KeyStorageFlags.MachineKeySet); 573 | 574 | /** 575 | * Example building assertion using Json Tokenhandler. 576 | * Sort of cheating, but just if someone wonders ... there are always more ways to do something :-) 577 | */ 578 | Dictionary claims = new Dictionary() 579 | { 580 | { "sub", appConfig.ClientId }, 581 | { "jti", Guid.NewGuid().ToString() }, 582 | }; 583 | 584 | JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); 585 | X509SigningCredentials signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest); 586 | 587 | JwtSecurityToken selfSignedToken = new JwtSecurityToken( 588 | appConfig.ClientId, 589 | tokenIssueEndpoint, 590 | claims.Select(c => new Claim(c.Key, c.Value)), 591 | DateTime.UtcNow, 592 | DateTime.UtcNow.Add(TimeSpan.FromMinutes(15)), 593 | signingCredentials); 594 | 595 | string signedAssertion = tokenHandler.WriteToken(selfSignedToken); 596 | 597 | //---- End example with Json Tokenhandler... now to the fun part doing it all ourselves ... 598 | 599 | /** 600 | * Example building assertion from scratch with Crypto APIs 601 | */ 602 | JObject clientAssertion = new JObject(); 603 | clientAssertion.Add("aud", tokenIssueEndpoint); 604 | clientAssertion.Add("iss", appConfig.ClientId); 605 | clientAssertion.Add("sub", appConfig.ClientId); 606 | clientAssertion.Add("jti", Guid.NewGuid().ToString()); 607 | clientAssertion.Add("nbf", WebConvert.EpocTime(DateTime.UtcNow + TimeSpan.FromMinutes(-5))); 608 | clientAssertion.Add("exp", WebConvert.EpocTime(DateTime.UtcNow + TimeSpan.FromMinutes(15))); 609 | 610 | string assertionPayload = clientAssertion.ToString(Newtonsoft.Json.Formatting.None); 611 | 612 | X509AsymmetricSecurityKey x509Key = new X509AsymmetricSecurityKey(cert); 613 | RSACryptoServiceProvider rsa = x509Key.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, true) as RSACryptoServiceProvider; 614 | RSACryptoServiceProvider newRsa = GetCryptoProviderForSha256(rsa); 615 | SHA256Cng sha = new SHA256Cng(); 616 | 617 | JObject header = new JObject(new JProperty("alg", "RS256")); 618 | string thumbprint = WebConvert.Base64UrlEncoded(WebConvert.HexStringToBytes(cert.Thumbprint)); 619 | header.Add(new JProperty("x5t", thumbprint)); 620 | 621 | string encodedHeader = WebConvert.Base64UrlEncoded(header.ToString()); 622 | string encodedPayload = WebConvert.Base64UrlEncoded(assertionPayload); 623 | 624 | string signingInput = String.Concat(encodedHeader, ".", encodedPayload); 625 | 626 | byte[] signature = newRsa.SignData(Encoding.UTF8.GetBytes(signingInput), sha); 627 | 628 | signedAssertion = string.Format("{0}.{1}.{2}", 629 | encodedHeader, 630 | encodedPayload, 631 | WebConvert.Base64UrlEncoded(signature)); 632 | 633 | /** 634 | * build the request payload 635 | */ 636 | FormUrlEncodedContent tokenRequestForm; 637 | tokenRequestForm = new FormUrlEncodedContent( 638 | new[] { 639 | new KeyValuePair("resource", appConfig.ExchangeResourceUri), 640 | new KeyValuePair("client_id", appConfig.ClientId), 641 | new KeyValuePair("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"), 642 | new KeyValuePair("client_assertion", signedAssertion), 643 | new KeyValuePair("grant_type","client_credentials"), 644 | } 645 | ); 646 | 647 | /* 648 | * Do the web request 649 | */ 650 | HttpClient client = new HttpClient(); 651 | 652 | Task requestString = tokenRequestForm.ReadAsStringAsync(); 653 | StringContent requestContent = new StringContent(requestString.Result); 654 | requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); 655 | requestContent.Headers.Add("client-request-id", System.Guid.NewGuid().ToString()); 656 | requestContent.Headers.Add("return-client-request-id", "true"); 657 | requestContent.Headers.Add("UserAgent", "MatthiasLeibmannsAppOnlyAppSampleBeta/0.1"); 658 | 659 | HttpResponseMessage response = client.PostAsync(tokenIssueEndpoint, requestContent).Result; 660 | JObject jsonResponse = JObject.Parse(response.Content.ReadAsStringAsync().Result); 661 | JsonSerializer jsonSerializer = new JsonSerializer(); 662 | 663 | if(response.IsSuccessStatusCode == true) 664 | { 665 | AADClientCredentialSuccessResponse s = (AADClientCredentialSuccessResponse)jsonSerializer.Deserialize(new JTokenReader(jsonResponse), typeof(AADClientCredentialSuccessResponse)); 666 | return s.access_token; 667 | } 668 | 669 | AADClientCredentialErrorResponse e = (AADClientCredentialErrorResponse)jsonSerializer.Deserialize(new JTokenReader(jsonResponse), typeof(AADClientCredentialErrorResponse)); 670 | throw new Exception(e.error_description); 671 | } 672 | 673 | } 674 | } 675 | 676 | // MIT License: 677 | 678 | // Permission is hereby granted, free of charge, to any person obtaining 679 | // a copy of this software and associated documentation files (the 680 | // ""Software""), to deal in the Software without restriction, including 681 | // without limitation the rights to use, copy, modify, merge, publish, 682 | // distribute, sublicense, and/or sell copies of the Software, and to 683 | // permit persons to whom the Software is furnished to do so, subject to 684 | // the following conditions: 685 | 686 | // The above copyright notice and this permission notice shall be 687 | // included in all copies or substantial portions of the Software. 688 | 689 | // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, 690 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 691 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 692 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 693 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 694 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 695 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="AccessMailboxAsApp.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Optimization; 7 | using System.Web.Routing; 8 | 9 | namespace AccessMailboxAsApp 10 | { 11 | public class MvcApplication : System.Web.HttpApplication 12 | { 13 | protected void Application_Start() 14 | { 15 | AreaRegistration.RegisterAllAreas(); 16 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 17 | RouteConfig.RegisterRoutes(RouteTable.Routes); 18 | BundleConfig.RegisterBundles(BundleTable.Bundles); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Models/AppState.cs: -------------------------------------------------------------------------------- 1 | //Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. 2 | // 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Web; 7 | using System.Web.Mvc; 8 | 9 | namespace AccessMailboxAsApp.Models 10 | { 11 | public class AppState 12 | { 13 | public string TenantId { get; set; } 14 | public string TenantDomain { get; set; } 15 | public string LoggedOnUser { get; set; } 16 | public string MailboxSmtpAddress { get; set; } 17 | public bool AppIsAuthorized { get; set; } 18 | public bool AccessTokenAquiredWithoutError { get; set; } 19 | public string ErrorMessage { get; set; } 20 | public string AccessToken { get; set; } 21 | public string Request { get; set; } 22 | public string Response { get; set; } 23 | public SelectList MailboxList { get; set; } 24 | 25 | public AppState() 26 | { 27 | TenantId = String.Empty; 28 | TenantDomain = String.Empty; 29 | LoggedOnUser = String.Empty; 30 | MailboxSmtpAddress = String.Empty; 31 | ErrorMessage = String.Empty; 32 | AppIsAuthorized = false; 33 | AccessTokenAquiredWithoutError = false; 34 | AccessToken = String.Empty; 35 | Request = String.Empty; 36 | Response = String.Empty; 37 | MailboxList = null; 38 | } 39 | } 40 | } 41 | 42 | // MIT License: 43 | 44 | // Permission is hereby granted, free of charge, to any person obtaining 45 | // a copy of this software and associated documentation files (the 46 | // ""Software""), to deal in the Software without restriction, including 47 | // without limitation the rights to use, copy, modify, merge, publish, 48 | // distribute, sublicense, and/or sell copies of the Software, and to 49 | // permit persons to whom the Software is furnished to do so, subject to 50 | // the following conditions: 51 | 52 | // The above copyright notice and this permission notice shall be 53 | // included in all copies or substantial portions of the Software. 54 | 55 | // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, 56 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 57 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 58 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 59 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 60 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 61 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Project_Readme.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Your ASP.NET application 6 | 95 | 96 | 97 | 98 | 102 | 103 |
104 |
105 |

This application consists of:

106 |
    107 |
  • Sample pages showing basic nav between Home, About, and Contact
  • 108 |
  • Theming using Bootstrap
  • 109 |
  • Authentication, if selected, shows how to register and sign in
  • 110 |
  • ASP.NET features managed using NuGet
  • 111 |
112 |
113 | 114 | 131 | 132 |
133 |

Deploy

134 | 139 |
140 | 141 |
142 |

Get help

143 | 147 |
148 |
149 | 150 | 151 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AccessMailboxAsApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AccessMailboxAsApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("89dd3f45-cdca-4789-94fe-8ff10b6fd2e3")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Scripts/_references.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattleib/o365api-as-apponly-webapp/0e801729949a8f7f8be4c27c569d840b332d2a9d/AccessMailboxAsApp/AccessMailboxAsApp/Scripts/_references.js -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Scripts/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | 16 | /** 17 | * bootstrap.js v3.0.0 by @fat and @mdo 18 | * Copyright 2013 Twitter Inc. 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | */ 21 | if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery); -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Scripts/jquery.validate.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! jQuery Validation Plugin - v1.11.1 - 3/22/2013\n* https://github.com/jzaefferer/jquery-validation 16 | * Copyright (c) 2013 Jörn Zaefferer; Licensed MIT */(function(t){t.extend(t.fn,{validate:function(e){if(!this.length)return e&&e.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."),void 0;var i=t.data(this[0],"validator");return i?i:(this.attr("novalidate","novalidate"),i=new t.validator(e,this[0]),t.data(this[0],"validator",i),i.settings.onsubmit&&(this.validateDelegate(":submit","click",function(e){i.settings.submitHandler&&(i.submitButton=e.target),t(e.target).hasClass("cancel")&&(i.cancelSubmit=!0),void 0!==t(e.target).attr("formnovalidate")&&(i.cancelSubmit=!0)}),this.submit(function(e){function s(){var s;return i.settings.submitHandler?(i.submitButton&&(s=t("").attr("name",i.submitButton.name).val(t(i.submitButton).val()).appendTo(i.currentForm)),i.settings.submitHandler.call(i,i.currentForm,e),i.submitButton&&s.remove(),!1):!0}return i.settings.debug&&e.preventDefault(),i.cancelSubmit?(i.cancelSubmit=!1,s()):i.form()?i.pendingRequest?(i.formSubmitted=!0,!1):s():(i.focusInvalid(),!1)})),i)},valid:function(){if(t(this[0]).is("form"))return this.validate().form();var e=!0,i=t(this[0].form).validate();return this.each(function(){e=e&&i.element(this)}),e},removeAttrs:function(e){var i={},s=this;return t.each(e.split(/\s/),function(t,e){i[e]=s.attr(e),s.removeAttr(e)}),i},rules:function(e,i){var s=this[0];if(e){var r=t.data(s.form,"validator").settings,n=r.rules,a=t.validator.staticRules(s);switch(e){case"add":t.extend(a,t.validator.normalizeRule(i)),delete a.messages,n[s.name]=a,i.messages&&(r.messages[s.name]=t.extend(r.messages[s.name],i.messages));break;case"remove":if(!i)return delete n[s.name],a;var u={};return t.each(i.split(/\s/),function(t,e){u[e]=a[e],delete a[e]}),u}}var o=t.validator.normalizeRules(t.extend({},t.validator.classRules(s),t.validator.attributeRules(s),t.validator.dataRules(s),t.validator.staticRules(s)),s);if(o.required){var l=o.required;delete o.required,o=t.extend({required:l},o)}return o}}),t.extend(t.expr[":"],{blank:function(e){return!t.trim(""+t(e).val())},filled:function(e){return!!t.trim(""+t(e).val())},unchecked:function(e){return!t(e).prop("checked")}}),t.validator=function(e,i){this.settings=t.extend(!0,{},t.validator.defaults,e),this.currentForm=i,this.init()},t.validator.format=function(e,i){return 1===arguments.length?function(){var i=t.makeArray(arguments);return i.unshift(e),t.validator.format.apply(this,i)}:(arguments.length>2&&i.constructor!==Array&&(i=t.makeArray(arguments).slice(1)),i.constructor!==Array&&(i=[i]),t.each(i,function(t,i){e=e.replace(RegExp("\\{"+t+"\\}","g"),function(){return i})}),e)},t.extend(t.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",validClass:"valid",errorElement:"label",focusInvalid:!0,errorContainer:t([]),errorLabelContainer:t([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(t){this.lastActive=t,this.settings.focusCleanup&&!this.blockFocusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,t,this.settings.errorClass,this.settings.validClass),this.addWrapper(this.errorsFor(t)).hide())},onfocusout:function(t){this.checkable(t)||!(t.name in this.submitted)&&this.optional(t)||this.element(t)},onkeyup:function(t,e){(9!==e.which||""!==this.elementValue(t))&&(t.name in this.submitted||t===this.lastElement)&&this.element(t)},onclick:function(t){t.name in this.submitted?this.element(t):t.parentNode.name in this.submitted&&this.element(t.parentNode)},highlight:function(e,i,s){"radio"===e.type?this.findByName(e.name).addClass(i).removeClass(s):t(e).addClass(i).removeClass(s)},unhighlight:function(e,i,s){"radio"===e.type?this.findByName(e.name).removeClass(i).addClass(s):t(e).removeClass(i).addClass(s)}},setDefaults:function(e){t.extend(t.validator.defaults,e)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",maxlength:t.validator.format("Please enter no more than {0} characters."),minlength:t.validator.format("Please enter at least {0} characters."),rangelength:t.validator.format("Please enter a value between {0} and {1} characters long."),range:t.validator.format("Please enter a value between {0} and {1}."),max:t.validator.format("Please enter a value less than or equal to {0}."),min:t.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:!1,prototype:{init:function(){function e(e){var i=t.data(this[0].form,"validator"),s="on"+e.type.replace(/^validate/,"");i.settings[s]&&i.settings[s].call(i,this[0],e)}this.labelContainer=t(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||t(this.currentForm),this.containers=t(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var i=this.groups={};t.each(this.settings.groups,function(e,s){"string"==typeof s&&(s=s.split(/\s/)),t.each(s,function(t,s){i[s]=e})});var s=this.settings.rules;t.each(s,function(e,i){s[e]=t.validator.normalizeRule(i)}),t(this.currentForm).validateDelegate(":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'] ,[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'] ","focusin focusout keyup",e).validateDelegate("[type='radio'], [type='checkbox'], select, option","click",e),this.settings.invalidHandler&&t(this.currentForm).bind("invalid-form.validate",this.settings.invalidHandler)},form:function(){return this.checkForm(),t.extend(this.submitted,this.errorMap),this.invalid=t.extend({},this.errorMap),this.valid()||t(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var t=0,e=this.currentElements=this.elements();e[t];t++)this.check(e[t]);return this.valid()},element:function(e){e=this.validationTargetFor(this.clean(e)),this.lastElement=e,this.prepareElement(e),this.currentElements=t(e);var i=this.check(e)!==!1;return i?delete this.invalid[e.name]:this.invalid[e.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),i},showErrors:function(e){if(e){t.extend(this.errorMap,e),this.errorList=[];for(var i in e)this.errorList.push({message:e[i],element:this.findByName(i)[0]});this.successList=t.grep(this.successList,function(t){return!(t.name in e)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){t.fn.resetForm&&t(this.currentForm).resetForm(),this.submitted={},this.lastElement=null,this.prepareForm(),this.hideErrors(),this.elements().removeClass(this.settings.errorClass).removeData("previousValue")},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(t){var e=0;for(var i in t)e++;return e},hideErrors:function(){this.addWrapper(this.toHide).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{t(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(e){}},findLastActive:function(){var e=this.lastActive;return e&&1===t.grep(this.errorList,function(t){return t.element.name===e.name}).length&&e},elements:function(){var e=this,i={};return t(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, [disabled]").not(this.settings.ignore).filter(function(){return!this.name&&e.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.name in i||!e.objectLength(t(this).rules())?!1:(i[this.name]=!0,!0)})},clean:function(e){return t(e)[0]},errors:function(){var e=this.settings.errorClass.replace(" ",".");return t(this.settings.errorElement+"."+e,this.errorContext)},reset:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=t([]),this.toHide=t([]),this.currentElements=t([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(t){this.reset(),this.toHide=this.errorsFor(t)},elementValue:function(e){var i=t(e).attr("type"),s=t(e).val();return"radio"===i||"checkbox"===i?t("input[name='"+t(e).attr("name")+"']:checked").val():"string"==typeof s?s.replace(/\r/g,""):s},check:function(e){e=this.validationTargetFor(this.clean(e));var i,s=t(e).rules(),r=!1,n=this.elementValue(e);for(var a in s){var u={method:a,parameters:s[a]};try{if(i=t.validator.methods[a].call(this,n,e,u.parameters),"dependency-mismatch"===i){r=!0;continue}if(r=!1,"pending"===i)return this.toHide=this.toHide.not(this.errorsFor(e)),void 0;if(!i)return this.formatAndAdd(e,u),!1}catch(o){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+e.id+", check the '"+u.method+"' method.",o),o}}return r?void 0:(this.objectLength(s)&&this.successList.push(e),!0)},customDataMessage:function(e,i){return t(e).data("msg-"+i.toLowerCase())||e.attributes&&t(e).attr("data-msg-"+i.toLowerCase())},customMessage:function(t,e){var i=this.settings.messages[t];return i&&(i.constructor===String?i:i[e])},findDefined:function(){for(var t=0;arguments.length>t;t++)if(void 0!==arguments[t])return arguments[t];return void 0},defaultMessage:function(e,i){return this.findDefined(this.customMessage(e.name,i),this.customDataMessage(e,i),!this.settings.ignoreTitle&&e.title||void 0,t.validator.messages[i],"Warning: No message defined for "+e.name+"")},formatAndAdd:function(e,i){var s=this.defaultMessage(e,i.method),r=/\$?\{(\d+)\}/g;"function"==typeof s?s=s.call(this,i.parameters,e):r.test(s)&&(s=t.validator.format(s.replace(r,"{$1}"),i.parameters)),this.errorList.push({message:s,element:e}),this.errorMap[e.name]=s,this.submitted[e.name]=s},addWrapper:function(t){return this.settings.wrapper&&(t=t.add(t.parent(this.settings.wrapper))),t},defaultShowErrors:function(){var t,e;for(t=0;this.errorList[t];t++){var i=this.errorList[t];this.settings.highlight&&this.settings.highlight.call(this,i.element,this.settings.errorClass,this.settings.validClass),this.showLabel(i.element,i.message)}if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(t=0;this.successList[t];t++)this.showLabel(this.successList[t]);if(this.settings.unhighlight)for(t=0,e=this.validElements();e[t];t++)this.settings.unhighlight.call(this,e[t],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return t(this.errorList).map(function(){return this.element})},showLabel:function(e,i){var s=this.errorsFor(e);s.length?(s.removeClass(this.settings.validClass).addClass(this.settings.errorClass),s.html(i)):(s=t("<"+this.settings.errorElement+">").attr("for",this.idOrName(e)).addClass(this.settings.errorClass).html(i||""),this.settings.wrapper&&(s=s.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.append(s).length||(this.settings.errorPlacement?this.settings.errorPlacement(s,t(e)):s.insertAfter(e))),!i&&this.settings.success&&(s.text(""),"string"==typeof this.settings.success?s.addClass(this.settings.success):this.settings.success(s,e)),this.toShow=this.toShow.add(s)},errorsFor:function(e){var i=this.idOrName(e);return this.errors().filter(function(){return t(this).attr("for")===i})},idOrName:function(t){return this.groups[t.name]||(this.checkable(t)?t.name:t.id||t.name)},validationTargetFor:function(t){return this.checkable(t)&&(t=this.findByName(t.name).not(this.settings.ignore)[0]),t},checkable:function(t){return/radio|checkbox/i.test(t.type)},findByName:function(e){return t(this.currentForm).find("[name='"+e+"']")},getLength:function(e,i){switch(i.nodeName.toLowerCase()){case"select":return t("option:selected",i).length;case"input":if(this.checkable(i))return this.findByName(i.name).filter(":checked").length}return e.length},depend:function(t,e){return this.dependTypes[typeof t]?this.dependTypes[typeof t](t,e):!0},dependTypes:{"boolean":function(t){return t},string:function(e,i){return!!t(e,i.form).length},"function":function(t,e){return t(e)}},optional:function(e){var i=this.elementValue(e);return!t.validator.methods.required.call(this,i,e)&&"dependency-mismatch"},startRequest:function(t){this.pending[t.name]||(this.pendingRequest++,this.pending[t.name]=!0)},stopRequest:function(e,i){this.pendingRequest--,0>this.pendingRequest&&(this.pendingRequest=0),delete this.pending[e.name],i&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(t(this.currentForm).submit(),this.formSubmitted=!1):!i&&0===this.pendingRequest&&this.formSubmitted&&(t(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(e){return t.data(e,"previousValue")||t.data(e,"previousValue",{old:null,valid:!0,message:this.defaultMessage(e,"remote")})}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(e,i){e.constructor===String?this.classRuleSettings[e]=i:t.extend(this.classRuleSettings,e)},classRules:function(e){var i={},s=t(e).attr("class");return s&&t.each(s.split(" "),function(){this in t.validator.classRuleSettings&&t.extend(i,t.validator.classRuleSettings[this])}),i},attributeRules:function(e){var i={},s=t(e),r=s[0].getAttribute("type");for(var n in t.validator.methods){var a;"required"===n?(a=s.get(0).getAttribute(n),""===a&&(a=!0),a=!!a):a=s.attr(n),/min|max/.test(n)&&(null===r||/number|range|text/.test(r))&&(a=Number(a)),a?i[n]=a:r===n&&"range"!==r&&(i[n]=!0)}return i.maxlength&&/-1|2147483647|524288/.test(i.maxlength)&&delete i.maxlength,i},dataRules:function(e){var i,s,r={},n=t(e);for(i in t.validator.methods)s=n.data("rule-"+i.toLowerCase()),void 0!==s&&(r[i]=s);return r},staticRules:function(e){var i={},s=t.data(e.form,"validator");return s.settings.rules&&(i=t.validator.normalizeRule(s.settings.rules[e.name])||{}),i},normalizeRules:function(e,i){return t.each(e,function(s,r){if(r===!1)return delete e[s],void 0;if(r.param||r.depends){var n=!0;switch(typeof r.depends){case"string":n=!!t(r.depends,i.form).length;break;case"function":n=r.depends.call(i,i)}n?e[s]=void 0!==r.param?r.param:!0:delete e[s]}}),t.each(e,function(s,r){e[s]=t.isFunction(r)?r(i):r}),t.each(["minlength","maxlength"],function(){e[this]&&(e[this]=Number(e[this]))}),t.each(["rangelength","range"],function(){var i;e[this]&&(t.isArray(e[this])?e[this]=[Number(e[this][0]),Number(e[this][1])]:"string"==typeof e[this]&&(i=e[this].split(/[\s,]+/),e[this]=[Number(i[0]),Number(i[1])]))}),t.validator.autoCreateRanges&&(e.min&&e.max&&(e.range=[e.min,e.max],delete e.min,delete e.max),e.minlength&&e.maxlength&&(e.rangelength=[e.minlength,e.maxlength],delete e.minlength,delete e.maxlength)),e},normalizeRule:function(e){if("string"==typeof e){var i={};t.each(e.split(/\s/),function(){i[this]=!0}),e=i}return e},addMethod:function(e,i,s){t.validator.methods[e]=i,t.validator.messages[e]=void 0!==s?s:t.validator.messages[e],3>i.length&&t.validator.addClassRules(e,t.validator.normalizeRule(e))},methods:{required:function(e,i,s){if(!this.depend(s,i))return"dependency-mismatch";if("select"===i.nodeName.toLowerCase()){var r=t(i).val();return r&&r.length>0}return this.checkable(i)?this.getLength(e,i)>0:t.trim(e).length>0},email:function(t,e){return this.optional(e)||/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(t)},url:function(t,e){return this.optional(e)||/^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(t)},date:function(t,e){return this.optional(e)||!/Invalid|NaN/.test(""+new Date(t))},dateISO:function(t,e){return this.optional(e)||/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(t)},number:function(t,e){return this.optional(e)||/^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(t)},digits:function(t,e){return this.optional(e)||/^\d+$/.test(t)},creditcard:function(t,e){if(this.optional(e))return"dependency-mismatch";if(/[^0-9 \-]+/.test(t))return!1;var i=0,s=0,r=!1;t=t.replace(/\D/g,"");for(var n=t.length-1;n>=0;n--){var a=t.charAt(n);s=parseInt(a,10),r&&(s*=2)>9&&(s-=9),i+=s,r=!r}return 0===i%10},minlength:function(e,i,s){var r=t.isArray(e)?e.length:this.getLength(t.trim(e),i);return this.optional(i)||r>=s},maxlength:function(e,i,s){var r=t.isArray(e)?e.length:this.getLength(t.trim(e),i);return this.optional(i)||s>=r},rangelength:function(e,i,s){var r=t.isArray(e)?e.length:this.getLength(t.trim(e),i);return this.optional(i)||r>=s[0]&&s[1]>=r},min:function(t,e,i){return this.optional(e)||t>=i},max:function(t,e,i){return this.optional(e)||i>=t},range:function(t,e,i){return this.optional(e)||t>=i[0]&&i[1]>=t},equalTo:function(e,i,s){var r=t(s);return this.settings.onfocusout&&r.unbind(".validate-equalTo").bind("blur.validate-equalTo",function(){t(i).valid()}),e===r.val()},remote:function(e,i,s){if(this.optional(i))return"dependency-mismatch";var r=this.previousValue(i);if(this.settings.messages[i.name]||(this.settings.messages[i.name]={}),r.originalMessage=this.settings.messages[i.name].remote,this.settings.messages[i.name].remote=r.message,s="string"==typeof s&&{url:s}||s,r.old===e)return r.valid;r.old=e;var n=this;this.startRequest(i);var a={};return a[i.name]=e,t.ajax(t.extend(!0,{url:s,mode:"abort",port:"validate"+i.name,dataType:"json",data:a,success:function(s){n.settings.messages[i.name].remote=r.originalMessage;var a=s===!0||"true"===s;if(a){var u=n.formSubmitted;n.prepareElement(i),n.formSubmitted=u,n.successList.push(i),delete n.invalid[i.name],n.showErrors()}else{var o={},l=s||n.defaultMessage(i,"remote");o[i.name]=r.message=t.isFunction(l)?l(e):l,n.invalid[i.name]=!0,n.showErrors(o)}r.valid=a,n.stopRequest(i,a)}},s)),"pending"}}}),t.format=t.validator.format})(jQuery),function(t){var e={};if(t.ajaxPrefilter)t.ajaxPrefilter(function(t,i,s){var r=t.port;"abort"===t.mode&&(e[r]&&e[r].abort(),e[r]=s)});else{var i=t.ajax;t.ajax=function(s){var r=("mode"in s?s:t.ajaxSettings).mode,n=("port"in s?s:t.ajaxSettings).port;return"abort"===r?(e[n]&&e[n].abort(),e[n]=i.apply(this,arguments),e[n]):i.apply(this,arguments)}}}(jQuery),function(t){t.extend(t.fn,{validateDelegate:function(e,i,s){return this.bind(i,function(i){var r=t(i.target);return r.is(e)?s.apply(r,arguments):void 0})}})}(jQuery); -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Scripts/jquery.validate.unobtrusive.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! 16 | ** Unobtrusive validation support library for jQuery and jQuery Validate 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | 20 | /*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ 21 | /*global document: false, jQuery: false */ 22 | 23 | (function ($) { 24 | var $jQval = $.validator, 25 | adapters, 26 | data_validation = "unobtrusiveValidation"; 27 | 28 | function setValidationValues(options, ruleName, value) { 29 | options.rules[ruleName] = value; 30 | if (options.message) { 31 | options.messages[ruleName] = options.message; 32 | } 33 | } 34 | 35 | function splitAndTrim(value) { 36 | return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g); 37 | } 38 | 39 | function escapeAttributeValue(value) { 40 | // As mentioned on http://api.jquery.com/category/selectors/ 41 | return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); 42 | } 43 | 44 | function getModelPrefix(fieldName) { 45 | return fieldName.substr(0, fieldName.lastIndexOf(".") + 1); 46 | } 47 | 48 | function appendModelPrefix(value, prefix) { 49 | if (value.indexOf("*.") === 0) { 50 | value = value.replace("*.", prefix); 51 | } 52 | return value; 53 | } 54 | 55 | function onError(error, inputElement) { // 'this' is the form element 56 | var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"), 57 | replaceAttrValue = container.attr("data-valmsg-replace"), 58 | replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null; 59 | 60 | container.removeClass("field-validation-valid").addClass("field-validation-error"); 61 | error.data("unobtrusiveContainer", container); 62 | 63 | if (replace) { 64 | container.empty(); 65 | error.removeClass("input-validation-error").appendTo(container); 66 | } 67 | else { 68 | error.hide(); 69 | } 70 | } 71 | 72 | function onErrors(event, validator) { // 'this' is the form element 73 | var container = $(this).find("[data-valmsg-summary=true]"), 74 | list = container.find("ul"); 75 | 76 | if (list && list.length && validator.errorList.length) { 77 | list.empty(); 78 | container.addClass("validation-summary-errors").removeClass("validation-summary-valid"); 79 | 80 | $.each(validator.errorList, function () { 81 | $("
  • ").html(this.message).appendTo(list); 82 | }); 83 | } 84 | } 85 | 86 | function onSuccess(error) { // 'this' is the form element 87 | var container = error.data("unobtrusiveContainer"), 88 | replaceAttrValue = container.attr("data-valmsg-replace"), 89 | replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null; 90 | 91 | if (container) { 92 | container.addClass("field-validation-valid").removeClass("field-validation-error"); 93 | error.removeData("unobtrusiveContainer"); 94 | 95 | if (replace) { 96 | container.empty(); 97 | } 98 | } 99 | } 100 | 101 | function onReset(event) { // 'this' is the form element 102 | var $form = $(this); 103 | $form.data("validator").resetForm(); 104 | $form.find(".validation-summary-errors") 105 | .addClass("validation-summary-valid") 106 | .removeClass("validation-summary-errors"); 107 | $form.find(".field-validation-error") 108 | .addClass("field-validation-valid") 109 | .removeClass("field-validation-error") 110 | .removeData("unobtrusiveContainer") 111 | .find(">*") // If we were using valmsg-replace, get the underlying error 112 | .removeData("unobtrusiveContainer"); 113 | } 114 | 115 | function validationInfo(form) { 116 | var $form = $(form), 117 | result = $form.data(data_validation), 118 | onResetProxy = $.proxy(onReset, form); 119 | 120 | if (!result) { 121 | result = { 122 | options: { // options structure passed to jQuery Validate's validate() method 123 | errorClass: "input-validation-error", 124 | errorElement: "span", 125 | errorPlacement: $.proxy(onError, form), 126 | invalidHandler: $.proxy(onErrors, form), 127 | messages: {}, 128 | rules: {}, 129 | success: $.proxy(onSuccess, form) 130 | }, 131 | attachValidation: function () { 132 | $form 133 | .unbind("reset." + data_validation, onResetProxy) 134 | .bind("reset." + data_validation, onResetProxy) 135 | .validate(this.options); 136 | }, 137 | validate: function () { // a validation function that is called by unobtrusive Ajax 138 | $form.validate(); 139 | return $form.valid(); 140 | } 141 | }; 142 | $form.data(data_validation, result); 143 | } 144 | 145 | return result; 146 | } 147 | 148 | $jQval.unobtrusive = { 149 | adapters: [], 150 | 151 | parseElement: function (element, skipAttach) { 152 | /// 153 | /// Parses a single HTML element for unobtrusive validation attributes. 154 | /// 155 | /// The HTML element to be parsed. 156 | /// [Optional] true to skip attaching the 157 | /// validation to the form. If parsing just this single element, you should specify true. 158 | /// If parsing several elements, you should specify false, and manually attach the validation 159 | /// to the form when you are finished. The default is false. 160 | var $element = $(element), 161 | form = $element.parents("form")[0], 162 | valInfo, rules, messages; 163 | 164 | if (!form) { // Cannot do client-side validation without a form 165 | return; 166 | } 167 | 168 | valInfo = validationInfo(form); 169 | valInfo.options.rules[element.name] = rules = {}; 170 | valInfo.options.messages[element.name] = messages = {}; 171 | 172 | $.each(this.adapters, function () { 173 | var prefix = "data-val-" + this.name, 174 | message = $element.attr(prefix), 175 | paramValues = {}; 176 | 177 | if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy) 178 | prefix += "-"; 179 | 180 | $.each(this.params, function () { 181 | paramValues[this] = $element.attr(prefix + this); 182 | }); 183 | 184 | this.adapt({ 185 | element: element, 186 | form: form, 187 | message: message, 188 | params: paramValues, 189 | rules: rules, 190 | messages: messages 191 | }); 192 | } 193 | }); 194 | 195 | $.extend(rules, { "__dummy__": true }); 196 | 197 | if (!skipAttach) { 198 | valInfo.attachValidation(); 199 | } 200 | }, 201 | 202 | parse: function (selector) { 203 | /// 204 | /// Parses all the HTML elements in the specified selector. It looks for input elements decorated 205 | /// with the [data-val=true] attribute value and enables validation according to the data-val-* 206 | /// attribute values. 207 | /// 208 | /// Any valid jQuery selector. 209 | var $forms = $(selector) 210 | .parents("form") 211 | .andSelf() 212 | .add($(selector).find("form")) 213 | .filter("form"); 214 | 215 | // :input is a psuedoselector provided by jQuery which selects input and input-like elements 216 | // combining :input with other selectors significantly decreases performance. 217 | $(selector).find(":input").filter("[data-val=true]").each(function () { 218 | $jQval.unobtrusive.parseElement(this, true); 219 | }); 220 | 221 | $forms.each(function () { 222 | var info = validationInfo(this); 223 | if (info) { 224 | info.attachValidation(); 225 | } 226 | }); 227 | } 228 | }; 229 | 230 | adapters = $jQval.unobtrusive.adapters; 231 | 232 | adapters.add = function (adapterName, params, fn) { 233 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation. 234 | /// The name of the adapter to be added. This matches the name used 235 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 236 | /// [Optional] An array of parameter names (strings) that will 237 | /// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and 238 | /// mmmm is the parameter name). 239 | /// The function to call, which adapts the values from the HTML 240 | /// attributes into jQuery Validate rules and/or messages. 241 | /// 242 | if (!fn) { // Called with no params, just a function 243 | fn = params; 244 | params = []; 245 | } 246 | this.push({ name: adapterName, params: params, adapt: fn }); 247 | return this; 248 | }; 249 | 250 | adapters.addBool = function (adapterName, ruleName) { 251 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 252 | /// the jQuery Validate validation rule has no parameter values. 253 | /// The name of the adapter to be added. This matches the name used 254 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 255 | /// [Optional] The name of the jQuery Validate rule. If not provided, the value 256 | /// of adapterName will be used instead. 257 | /// 258 | return this.add(adapterName, function (options) { 259 | setValidationValues(options, ruleName || adapterName, true); 260 | }); 261 | }; 262 | 263 | adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) { 264 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 265 | /// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and 266 | /// one for min-and-max). The HTML parameters are expected to be named -min and -max. 267 | /// The name of the adapter to be added. This matches the name used 268 | /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). 269 | /// The name of the jQuery Validate rule to be used when you only 270 | /// have a minimum value. 271 | /// The name of the jQuery Validate rule to be used when you only 272 | /// have a maximum value. 273 | /// The name of the jQuery Validate rule to be used when you 274 | /// have both a minimum and maximum value. 275 | /// [Optional] The name of the HTML attribute that 276 | /// contains the minimum value. The default is "min". 277 | /// [Optional] The name of the HTML attribute that 278 | /// contains the maximum value. The default is "max". 279 | /// 280 | return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) { 281 | var min = options.params.min, 282 | max = options.params.max; 283 | 284 | if (min && max) { 285 | setValidationValues(options, minMaxRuleName, [min, max]); 286 | } 287 | else if (min) { 288 | setValidationValues(options, minRuleName, min); 289 | } 290 | else if (max) { 291 | setValidationValues(options, maxRuleName, max); 292 | } 293 | }); 294 | }; 295 | 296 | adapters.addSingleVal = function (adapterName, attribute, ruleName) { 297 | /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where 298 | /// the jQuery Validate validation rule has a single value. 299 | /// The name of the adapter to be added. This matches the name used 300 | /// in the data-val-nnnn HTML attribute(where nnnn is the adapter name). 301 | /// [Optional] The name of the HTML attribute that contains the value. 302 | /// The default is "val". 303 | /// [Optional] The name of the jQuery Validate rule. If not provided, the value 304 | /// of adapterName will be used instead. 305 | /// 306 | return this.add(adapterName, [attribute || "val"], function (options) { 307 | setValidationValues(options, ruleName || adapterName, options.params[attribute]); 308 | }); 309 | }; 310 | 311 | $jQval.addMethod("__dummy__", function (value, element, params) { 312 | return true; 313 | }); 314 | 315 | $jQval.addMethod("regex", function (value, element, params) { 316 | var match; 317 | if (this.optional(element)) { 318 | return true; 319 | } 320 | 321 | match = new RegExp(params).exec(value); 322 | return (match && (match.index === 0) && (match[0].length === value.length)); 323 | }); 324 | 325 | $jQval.addMethod("nonalphamin", function (value, element, nonalphamin) { 326 | var match; 327 | if (nonalphamin) { 328 | match = value.match(/\W/g); 329 | match = match && match.length >= nonalphamin; 330 | } 331 | return match; 332 | }); 333 | 334 | if ($jQval.methods.extension) { 335 | adapters.addSingleVal("accept", "mimtype"); 336 | adapters.addSingleVal("extension", "extension"); 337 | } else { 338 | // for backward compatibility, when the 'extension' validation method does not exist, such as with versions 339 | // of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for 340 | // validating the extension, and ignore mime-type validations as they are not supported. 341 | adapters.addSingleVal("extension", "extension", "accept"); 342 | } 343 | 344 | adapters.addSingleVal("regex", "pattern"); 345 | adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"); 346 | adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range"); 347 | adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength"); 348 | adapters.add("equalto", ["other"], function (options) { 349 | var prefix = getModelPrefix(options.element.name), 350 | other = options.params.other, 351 | fullOtherName = appendModelPrefix(other, prefix), 352 | element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0]; 353 | 354 | setValidationValues(options, "equalTo", element); 355 | }); 356 | adapters.add("required", function (options) { 357 | // jQuery Validate equates "required" with "mandatory" for checkbox elements 358 | if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") { 359 | setValidationValues(options, "required", true); 360 | } 361 | }); 362 | adapters.add("remote", ["url", "type", "additionalfields"], function (options) { 363 | var value = { 364 | url: options.params.url, 365 | type: options.params.type || "GET", 366 | data: {} 367 | }, 368 | prefix = getModelPrefix(options.element.name); 369 | 370 | $.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) { 371 | var paramName = appendModelPrefix(fieldName, prefix); 372 | value.data[paramName] = function () { 373 | return $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']").val(); 374 | }; 375 | }); 376 | 377 | setValidationValues(options, "remote", value); 378 | }); 379 | adapters.add("password", ["min", "nonalphamin", "regex"], function (options) { 380 | if (options.params.min) { 381 | setValidationValues(options, "minlength", options.params.min); 382 | } 383 | if (options.params.nonalphamin) { 384 | setValidationValues(options, "nonalphamin", options.params.nonalphamin); 385 | } 386 | if (options.params.regex) { 387 | setValidationValues(options, "regex", options.params.regex); 388 | } 389 | }); 390 | 391 | $(function () { 392 | $jQval.unobtrusive.parse(document); 393 | }); 394 | }(jQuery)); -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Scripts/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /* 16 | ** Unobtrusive validation support library for jQuery and jQuery Validate 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
  • ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this);b.data("validator").resetForm();b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(c){var b=a(c),d=b.data(e),f=a.proxy(n,c);if(!d){d={options:{errorClass:"input-validation-error",errorElement:"span",errorPlacement:a.proxy(m,c),invalidHandler:a.proxy(l,c),messages:{},rules:{},success:a.proxy(k,c)},attachValidation:function(){b.unbind("reset."+e,f).bind("reset."+e,f).validate(this.options)},validate:function(){b.validate();return b.valid()}};b.data(e,d)}return d}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(b){var c=a(b).parents("form").andSelf().add(a(b).find("form")).filter("form");a(b).find(":input").filter("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});c.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){return a(b.form).find(":input").filter("[name='"+f(c)+"']").val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Scripts/respond.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 17 | window.matchMedia = window.matchMedia || (function(doc, undefined){ 18 | 19 | var bool, 20 | docElem = doc.documentElement, 21 | refNode = docElem.firstElementChild || docElem.firstChild, 22 | // fakeBody required for 23 | fakeBody = doc.createElement('body'), 24 | div = doc.createElement('div'); 25 | 26 | div.id = 'mq-test-1'; 27 | div.style.cssText = "position:absolute;top:-100em"; 28 | fakeBody.style.background = "none"; 29 | fakeBody.appendChild(div); 30 | 31 | return function(q){ 32 | 33 | div.innerHTML = '­'; 34 | 35 | docElem.insertBefore(fakeBody, refNode); 36 | bool = div.offsetWidth == 42; 37 | docElem.removeChild(fakeBody); 38 | 39 | return { matches: bool, media: q }; 40 | }; 41 | 42 | })(document); 43 | 44 | 45 | 46 | 47 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 48 | (function( win ){ 49 | //exposed namespace 50 | win.respond = {}; 51 | 52 | //define update even in native-mq-supporting browsers, to avoid errors 53 | respond.update = function(){}; 54 | 55 | //expose media query support flag for external use 56 | respond.mediaQueriesSupported = win.matchMedia && win.matchMedia( "only all" ).matches; 57 | 58 | //if media queries are supported, exit here 59 | if( respond.mediaQueriesSupported ){ return; } 60 | 61 | //define vars 62 | var doc = win.document, 63 | docElem = doc.documentElement, 64 | mediastyles = [], 65 | rules = [], 66 | appendedEls = [], 67 | parsedSheets = {}, 68 | resizeThrottle = 30, 69 | head = doc.getElementsByTagName( "head" )[0] || docElem, 70 | base = doc.getElementsByTagName( "base" )[0], 71 | links = head.getElementsByTagName( "link" ), 72 | requestQueue = [], 73 | 74 | //loop stylesheets, send text content to translate 75 | ripCSS = function(){ 76 | var sheets = links, 77 | sl = sheets.length, 78 | i = 0, 79 | //vars for loop: 80 | sheet, href, media, isCSS; 81 | 82 | for( ; i < sl; i++ ){ 83 | sheet = sheets[ i ], 84 | href = sheet.href, 85 | media = sheet.media, 86 | isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; 87 | 88 | //only links plz and prevent re-parsing 89 | if( !!href && isCSS && !parsedSheets[ href ] ){ 90 | // selectivizr exposes css through the rawCssText expando 91 | if (sheet.styleSheet && sheet.styleSheet.rawCssText) { 92 | translate( sheet.styleSheet.rawCssText, href, media ); 93 | parsedSheets[ href ] = true; 94 | } else { 95 | if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) 96 | || href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){ 97 | requestQueue.push( { 98 | href: href, 99 | media: media 100 | } ); 101 | } 102 | } 103 | } 104 | } 105 | makeRequests(); 106 | }, 107 | 108 | //recurse through request queue, get css text 109 | makeRequests = function(){ 110 | if( requestQueue.length ){ 111 | var thisRequest = requestQueue.shift(); 112 | 113 | ajax( thisRequest.href, function( styles ){ 114 | translate( styles, thisRequest.href, thisRequest.media ); 115 | parsedSheets[ thisRequest.href ] = true; 116 | makeRequests(); 117 | } ); 118 | } 119 | }, 120 | 121 | //find media blocks in css text, convert to style blocks 122 | translate = function( styles, href, media ){ 123 | var qs = styles.match( /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi ), 124 | ql = qs && qs.length || 0, 125 | //try to get CSS path 126 | href = href.substring( 0, href.lastIndexOf( "/" )), 127 | repUrls = function( css ){ 128 | return css.replace( /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, "$1" + href + "$2$3" ); 129 | }, 130 | useMedia = !ql && media, 131 | //vars used in loop 132 | i = 0, 133 | j, fullq, thisq, eachq, eql; 134 | 135 | //if path exists, tack on trailing slash 136 | if( href.length ){ href += "/"; } 137 | 138 | //if no internal queries exist, but media attr does, use that 139 | //note: this currently lacks support for situations where a media attr is specified on a link AND 140 | //its associated stylesheet has internal CSS media queries. 141 | //In those cases, the media attribute will currently be ignored. 142 | if( useMedia ){ 143 | ql = 1; 144 | } 145 | 146 | 147 | for( ; i < ql; i++ ){ 148 | j = 0; 149 | 150 | //media attr 151 | if( useMedia ){ 152 | fullq = media; 153 | rules.push( repUrls( styles ) ); 154 | } 155 | //parse for styles 156 | else{ 157 | fullq = qs[ i ].match( /@media *([^\{]+)\{([\S\s]+?)$/ ) && RegExp.$1; 158 | rules.push( RegExp.$2 && repUrls( RegExp.$2 ) ); 159 | } 160 | 161 | eachq = fullq.split( "," ); 162 | eql = eachq.length; 163 | 164 | for( ; j < eql; j++ ){ 165 | thisq = eachq[ j ]; 166 | mediastyles.push( { 167 | media : thisq.split( "(" )[ 0 ].match( /(only\s+)?([a-zA-Z]+)\s?/ ) && RegExp.$2 || "all", 168 | rules : rules.length - 1, 169 | hasquery: thisq.indexOf("(") > -1, 170 | minw : thisq.match( /\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ), 171 | maxw : thisq.match( /\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ) 172 | } ); 173 | } 174 | } 175 | 176 | applyMedia(); 177 | }, 178 | 179 | lastCall, 180 | 181 | resizeDefer, 182 | 183 | // returns the value of 1em in pixels 184 | getEmValue = function() { 185 | var ret, 186 | div = doc.createElement('div'), 187 | body = doc.body, 188 | fakeUsed = false; 189 | 190 | div.style.cssText = "position:absolute;font-size:1em;width:1em"; 191 | 192 | if( !body ){ 193 | body = fakeUsed = doc.createElement( "body" ); 194 | body.style.background = "none"; 195 | } 196 | 197 | body.appendChild( div ); 198 | 199 | docElem.insertBefore( body, docElem.firstChild ); 200 | 201 | ret = div.offsetWidth; 202 | 203 | if( fakeUsed ){ 204 | docElem.removeChild( body ); 205 | } 206 | else { 207 | body.removeChild( div ); 208 | } 209 | 210 | //also update eminpx before returning 211 | ret = eminpx = parseFloat(ret); 212 | 213 | return ret; 214 | }, 215 | 216 | //cached container for 1em value, populated the first time it's needed 217 | eminpx, 218 | 219 | //enable/disable styles 220 | applyMedia = function( fromResize ){ 221 | var name = "clientWidth", 222 | docElemProp = docElem[ name ], 223 | currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp, 224 | styleBlocks = {}, 225 | lastLink = links[ links.length-1 ], 226 | now = (new Date()).getTime(); 227 | 228 | //throttle resize calls 229 | if( fromResize && lastCall && now - lastCall < resizeThrottle ){ 230 | clearTimeout( resizeDefer ); 231 | resizeDefer = setTimeout( applyMedia, resizeThrottle ); 232 | return; 233 | } 234 | else { 235 | lastCall = now; 236 | } 237 | 238 | for( var i in mediastyles ){ 239 | var thisstyle = mediastyles[ i ], 240 | min = thisstyle.minw, 241 | max = thisstyle.maxw, 242 | minnull = min === null, 243 | maxnull = max === null, 244 | em = "em"; 245 | 246 | if( !!min ){ 247 | min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); 248 | } 249 | if( !!max ){ 250 | max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); 251 | } 252 | 253 | // if there's no media query at all (the () part), or min or max is not null, and if either is present, they're true 254 | if( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){ 255 | if( !styleBlocks[ thisstyle.media ] ){ 256 | styleBlocks[ thisstyle.media ] = []; 257 | } 258 | styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); 259 | } 260 | } 261 | 262 | //remove any existing respond style element(s) 263 | for( var i in appendedEls ){ 264 | if( appendedEls[ i ] && appendedEls[ i ].parentNode === head ){ 265 | head.removeChild( appendedEls[ i ] ); 266 | } 267 | } 268 | 269 | //inject active styles, grouped by media type 270 | for( var i in styleBlocks ){ 271 | var ss = doc.createElement( "style" ), 272 | css = styleBlocks[ i ].join( "\n" ); 273 | 274 | ss.type = "text/css"; 275 | ss.media = i; 276 | 277 | //originally, ss was appended to a documentFragment and sheets were appended in bulk. 278 | //this caused crashes in IE in a number of circumstances, such as when the HTML element had a bg image set, so appending beforehand seems best. Thanks to @dvelyk for the initial research on this one! 279 | head.insertBefore( ss, lastLink.nextSibling ); 280 | 281 | if ( ss.styleSheet ){ 282 | ss.styleSheet.cssText = css; 283 | } 284 | else { 285 | ss.appendChild( doc.createTextNode( css ) ); 286 | } 287 | 288 | //push to appendedEls to track for later removal 289 | appendedEls.push( ss ); 290 | } 291 | }, 292 | //tweaked Ajax functions from Quirksmode 293 | ajax = function( url, callback ) { 294 | var req = xmlHttp(); 295 | if (!req){ 296 | return; 297 | } 298 | req.open( "GET", url, true ); 299 | req.onreadystatechange = function () { 300 | if ( req.readyState != 4 || req.status != 200 && req.status != 304 ){ 301 | return; 302 | } 303 | callback( req.responseText ); 304 | } 305 | if ( req.readyState == 4 ){ 306 | return; 307 | } 308 | req.send( null ); 309 | }, 310 | //define ajax obj 311 | xmlHttp = (function() { 312 | var xmlhttpmethod = false; 313 | try { 314 | xmlhttpmethod = new XMLHttpRequest(); 315 | } 316 | catch( e ){ 317 | xmlhttpmethod = new ActiveXObject( "Microsoft.XMLHTTP" ); 318 | } 319 | return function(){ 320 | return xmlhttpmethod; 321 | }; 322 | })(); 323 | 324 | //translate CSS 325 | ripCSS(); 326 | 327 | //expose update for re-running respond later on 328 | respond.update = ripCSS; 329 | 330 | //adjust on resize 331 | function callMedia(){ 332 | applyMedia( true ); 333 | } 334 | if( win.addEventListener ){ 335 | win.addEventListener( "resize", callMedia, false ); 336 | } 337 | else if( win.attachEvent ){ 338 | win.attachEvent( "onresize", callMedia ); 339 | } 340 | })(this); 341 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Scripts/respond.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 17 | window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='­';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document); 18 | 19 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 20 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this); -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "About"; 3 | } 4 |

    @ViewBag.Title.

    5 |

    @ViewBag.Message

    6 | 7 |

    Use this area to provide additional information.

    8 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Contact"; 3 | } 4 |

    @ViewBag.Title.

    5 |

    @ViewBag.Message

    6 | 7 |
    8 | One Microsoft Way
    9 | Redmond, WA 98052-6399
    10 | P: 11 | 425.555.0100 12 |
    13 | 14 |
    15 | Support: Support@example.com
    16 | Marketing: Marketing@example.com 17 |
    -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Views/Home/index.cshtml: -------------------------------------------------------------------------------- 1 |  4 | @model AccessMailboxAsApp.Models.AppState 5 | @{ 6 | ViewBag.Title = "Access Mailboxes As Daemon App (app-only)"; 7 | } 8 | 9 | 16 | 17 |

    Access Mailboxes As Daemon App (app-only) - Sample Web App

    18 | 19 | @if(Model.AppIsAuthorized == false) 20 | { // App is not yet authorized ! 21 | if (!String.IsNullOrEmpty(Model.ErrorMessage)) 22 | { 23 |

    24 | Error: Authorization failed with: [@Model.ErrorMessage]
    25 |

    26 | } 27 | using (Html.BeginForm("", "Home", FormMethod.Post)) 28 | { 29 |

    30 | You need to sign-up your organization to use this app.
    31 | The sign-up requires an administrator of your organization. 32 |

    33 | 34 | 35 | 36 | 37 |
    Authorize this Application to read Mail, Calendar and Contacts in your Organization:
    38 | } 39 | } 40 | 41 | @if (Model.AppIsAuthorized == true) 42 | { // We are authorized and hopefully get tokens ... 43 |

    44 | Organization: [@Model.TenantDomain]
    45 | Current logged on as user: [@Model.LoggedOnUser]

    46 |

    47 | using (Html.BeginForm("", "Home", FormMethod.Post)) 48 | { 49 | if (Model.AccessTokenAquiredWithoutError) 50 | {// we can do work 51 |

    52 | 53 | 54 | 55 | 57 | 58 |
    Get data for mailbox [select smtp address]:@Html.DropDownList("Mailbox", Model.MailboxList, new { @onchange = "MailboxSelectionChanged(this.value)" }) 56 |
    59 |

    60 |

    61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
    69 |

    70 | } 71 | else // Token aquesition had an error display error message 72 | { 73 |

    74 | Error Aquiring Access Token: [@Model.ErrorMessage]
    75 |

    76 |

    77 | 78 | 79 | 80 | 81 |
    82 |

    83 | } 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
    107 | } // form 108 | } 109 | 110 | @if (!String.IsNullOrEmpty(Model.Request)) 111 | { 112 |
    113 |
    114 | Request:
    115 | Get @Model.Request Http/1.1
    116 | Authorization: Bearer @Model.AccessToken
    117 |
    118 |
    119 |
    120 | Response:
    121 |
    @Model.Response
    122 |
    123 | } 124 | 125 | 147 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | } 4 | 5 | 6 | 7 | 8 | 9 | Error 10 | 11 | 12 |
    13 |

    Error.

    14 |

    An error occurred while processing your request.

    15 |
    16 | 17 | 18 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | @ViewBag.Title - My ASP.NET Application 10 | @Styles.Render("~/Content/css") 11 | @Scripts.Render("~/bundles/modernizr") 12 | 13 | 14 | 35 |
    36 | @RenderBody() 37 |
    38 |
    39 |

    © @DateTime.Now.Year - My ASP.NET Application

    40 |
    41 |
    42 | 43 | @Scripts.Render("~/bundles/jquery") 44 | @Scripts.Render("~/bundles/bootstrap") 45 | @RenderSection("scripts", required: false) 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Views/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 |
    7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 91 | -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattleib/o365api-as-apponly-webapp/0e801729949a8f7f8be4c27c569d840b332d2a9d/AccessMailboxAsApp/AccessMailboxAsApp/favicon.ico -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattleib/o365api-as-apponly-webapp/0e801729949a8f7f8be4c27c569d840b332d2a9d/AccessMailboxAsApp/AccessMailboxAsApp/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattleib/o365api-as-apponly-webapp/0e801729949a8f7f8be4c27c569d840b332d2a9d/AccessMailboxAsApp/AccessMailboxAsApp/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattleib/o365api-as-apponly-webapp/0e801729949a8f7f8be4c27c569d840b332d2a9d/AccessMailboxAsApp/AccessMailboxAsApp/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /AccessMailboxAsApp/AccessMailboxAsApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | o365api-as-apponly-webapp, https://github.com/mattleib/o365api-as-apponly-webapp 2 | 3 | Copyright (c) Microsoft Corporation 4 | All rights reserved. 5 | 6 | MIT License: 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | ""Software""), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #o365api-as-apponly-webapp 2 | 3 | Sample web app that uses client credential flow to access Users, Mail, Calendar, Contacts in Office 365 via Rest APIs. 4 | 5 | For more information about how the protocols work in this scenario, see [Service to Service Calls Using Client Credentials] (http://msdn.microsoft.com/en-us/library/azure/dn645543.aspx) 6 | 7 | For more information about "app-only" aka 'Service or Daemon applications' in Office 365, see the companion blog on: (http://blogs.msdn.com/b/exchangedev/archive/2015/01/22/building-demon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow.aspx) 8 | 9 | ## How To Run This Sample 10 | 11 | To run this sample you will need: 12 | - Visual Studio 2013 13 | - An Internet connection 14 | - An Office 365 Developer Subscription (a free trial is sufficient) 15 | 16 | You can get a Office 365 Developer Subscription by signing up at (https://msdn.microsoft.com/en-us/library/office/fp179924(v=office.15).aspx) 17 | 18 | 19 | ### Step 1: Clone or download this repository 20 | 21 | From your shell or command line: 22 | 23 | `git clone https://github.com/mattleib/o365api-as-apponly-webapp` 24 | 25 | 26 | ### Step 2 Register the sample with your Azure Active Directory tenant 27 | 28 | ####Prereq: Create a certificate for your app as described in the companion blog: (http://blogs.msdn.com/b/exchangedev/archive/2015/01/22/building-demon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow.aspx) 29 | 30 | 1. Sign in to the [Azure management portal](https://manage.windowsazure.com). 31 | 2. Click on Active Directory in the left hand nav. 32 | 3. Click the directory tenant where you wish to register the sample application. 33 | 4. Click the Applications tab. 34 | 5. In the drawer, click Add. 35 | 6. Click "Add an application my organization is developing". 36 | 7. Enter a friendly name for the application, for example "O365AppOnlySample", select "Web Application and/or Web API", and click next. 37 | 8. For the sign-on URL, enter the base URL for the sample, e.g. `https://localhost:44321/Home`. 38 | - *Note*: The sign-on URL must end with **"`Home`"** as the application code expects this. 39 | - *Note*: As host component make sure that is the correct port for your IIS Express SSL that you later use for running/debugging the sample. 40 | 9. For the App ID URI, enter `https:///O365AppOnlySample`, replacing `` with the name of your Azure AD tenant. Click OK to complete the registration. 41 | 10. While still in the Azure portal, click the Configure tab of your application. 42 | 11. Find the Client ID value and copy it aside, you will need this later when configuring your application. 43 | 12. Configure following application permissions for the web app: 44 | - In the section "permissions to other applications" select **"Windows Azure Active Directory"** 45 | - From the "Application Permission" drop-down for "Windows Azure Active Directory" check: **"Read directory data"** 46 | - Select "Add Application" and add **"Office 365 Exchange Online"** 47 | - From the "Application Permission" drop-down for "Office 365 Exchange Online" check: **"Read users' mail"** 48 | - From the "Application Permission" drop-down for "Office 365 Exchange Online" check: **"Read users' calendar"** 49 | - From the "Application Permission" drop-down for "Office 365 Exchange Online" check: **"Read users' contacts"** 50 | 13. Save the configuration so you can view the key value. 51 | 14. Configure the X.509 public certificate as explained in the companion blog: (http://blogs.msdn.com/b/exchangedev/archive/2015/01/22/building-demon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow.aspx) 52 | 53 | 54 | ### Step 3 Configure the sample 55 | 56 | 1. Open the solution in Visual Studio 2013. 57 | 2. Open the `web.config` file. 58 | 3. Find the app key `RedirectUriLocalHost` and replace the value with the value in Step 2. 59 | 4. Find the app key `ClientId` and replace the value with client ID of Step 2. 60 | 5. Find the app key `ClientCertificatePfx` and replace the value with the location where your X.509 certificate with the private key is located 61 | 6. Find the app key `ClientCertificatePfxPassword` and replace the value with the password of your x.509 certificate with the private key 62 | 7. Configure the project to require SSL and make sure the Start URL is set to SSL by: 63 | a) Project Properties box: SSL Enabled, set to true 64 | b) Project Properties box: SSL URL: Make sure it matches the sign-on URL host component as specified in Step 2 65 | c) Project Settings Editor: Set the Start URL to the value as specified in previous step 66 | 67 | 68 | 69 | ### Step 4 Run the sample 70 | 71 | Rebuild the solution, and run it. You might want to go into the solution properties to check if the Startup matches your "https" sign-on URL as specified in Step 2. 72 | 73 | Explore the sample by signing in with your Office 365 Developer Tenant, select a mailbox (you might want to create more mailboxes and fill with data in the Office 365 Admin Portal) and retrieve data. 74 | 75 | 76 | 77 | --------------------------------------------------------------------------------