├── .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 |
115 |
Customize app
116 |
130 |
131 |
132 |
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('
').insertAfter(a(this)).on("click",b),f.trigger(d=a.Event("show.bs.dropdown")),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown"),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=a("[role=menu] li:not(.divider):visible a",f);if(h.length){var i=h.index(h.filter(":focus"));38==b.keyCode&&i>0&&i--,40==b.keyCode&&i ').appendTo(document.body),this.$element.on("click.dismiss.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(window.jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focus",i="hover"==g?"mouseleave":"blur";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show),void 0):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide),void 0):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this.tip();this.setContent(),this.options.animation&&c.addClass("fade");var d="function"==typeof this.options.placement?this.options.placement.call(this,c[0],this.$element[0]):this.options.placement,e=/\s?auto?\s?/i,f=e.test(d);f&&(d=d.replace(e,"")||"top"),c.detach().css({top:0,left:0,display:"block"}).addClass(d),this.options.container?c.appendTo(this.options.container):c.insertAfter(this.$element);var g=this.getPosition(),h=c[0].offsetWidth,i=c[0].offsetHeight;if(f){var j=this.$element.parent(),k=d,l=document.documentElement.scrollTop||document.body.scrollTop,m="body"==this.options.container?window.innerWidth:j.outerWidth(),n="body"==this.options.container?window.innerHeight:j.outerHeight(),o="body"==this.options.container?0:j.offset().left;d="bottom"==d&&g.top+g.height+i-l>n?"top":"top"==d&&g.top-l-i<0?"bottom":"right"==d&&g.right+h>m?"left":"left"==d&&g.left-h
'}),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 |
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 | Get data for mailbox [select smtp address]:
55 | @Html.DropDownList("Mailbox", Model.MailboxList, new { @onchange = "MailboxSelectionChanged(this.value)" })
56 |
57 |
58 |
59 |
60 |
61 |
69 |
70 | }
71 | else // Token aquesition had an error display error message
72 | {
73 |
74 | Error Aquiring Access Token: [@Model.ErrorMessage]
75 |
76 |
77 |
82 |
83 | }
84 |
85 |
86 | @Html.TextBoxFor(model => model.TenantId)
87 |
88 |
89 | @Html.TextBoxFor(model => model.TenantDomain)
90 |
91 |
92 | @Html.TextBoxFor(model => model.LoggedOnUser)
93 |
94 |
95 | @Html.TextBoxFor(model => model.AppIsAuthorized)
96 |
97 |
98 | @Html.TextBoxFor(model => model.AccessTokenAquiredWithoutError)
99 |
100 |
101 | @Html.TextBoxFor(model => model.AccessToken)
102 |
103 |
104 | @Html.TextBoxFor(model => model.MailboxSmtpAddress)
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 |
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 |
--------------------------------------------------------------------------------