[Tizen] Add BuildTools 2.1.0-rc1-02804-05
[platform/upstream/coreclr.git] / Tools / dotnetcli / sdk / NuGetFallbackFolder / microsoft.identitymodel.clients.activedirectory / 3.14.1 / src / src / ADAL.PCL.Desktop / WindowsFormsWebAuthenticationDialogBase.cs
1 //----------------------------------------------------------------------
2 //
3 // Copyright (c) Microsoft Corporation.
4 // All rights reserved.
5 //
6 // This code is licensed under the MIT License.
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files(the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions :
14 //
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 // THE SOFTWARE.
25 //
26 //------------------------------------------------------------------------------
27
28 using System;
29 using System.Collections.Generic;
30 using System.ComponentModel;
31 using System.Diagnostics;
32 using System.Drawing;
33 using System.Globalization;
34 using System.Runtime.InteropServices;
35 using System.Windows.Forms;
36
37 namespace Microsoft.IdentityModel.Clients.ActiveDirectory.Internal
38 {
39     /// <summary>
40     /// Base class for web form
41     /// </summary>
42     [ComVisible(true)]
43     [EditorBrowsable(EditorBrowsableState.Never)]
44     public abstract class WindowsFormsWebAuthenticationDialogBase : Form
45     {
46         private static readonly NavigateErrorStatus NavigateErrorStatus = new NavigateErrorStatus();
47
48         private const int UIWidth = 566;
49
50         private Panel webBrowserPanel;
51         private readonly CustomWebBrowser webBrowser;
52
53         private Uri desiredCallbackUri;
54
55         /// <summary>
56         /// 
57         /// </summary>
58         protected IWin32Window ownerWindow;
59
60         private Keys key = Keys.None;
61
62         private readonly HashSet<string> whiteListedSchemes = new HashSet<string>();
63         internal AuthorizationResult Result { get; set; }
64
65         /// <summary>
66         /// 
67         /// </summary>
68         /// <param name="ownerWindow"></param>
69         protected WindowsFormsWebAuthenticationDialogBase(object ownerWindow)
70         {
71             // From MSDN (http://msdn.microsoft.com/en-us/library/ie/dn720860(v=vs.85).aspx): 
72             // The net session count tracks the number of instances of the web browser control. 
73             // When a web browser control is created, the net session count is incremented. When the control 
74             // is destroyed, the net session count is decremented. When the net session count reaches zero, 
75             // the session cookies for the process are cleared. SetQueryNetSessionCount can be used to prevent 
76             // the session cookies from being cleared for applications where web browser controls are being created 
77             // and destroyed throughout the lifetime of the application. (Because the application lives longer than 
78             // a given instance, session cookies must be retained for a longer periods of time.
79             int sessionCount = NativeMethods.SetQueryNetSessionCount(NativeMethods.SessionOp.SESSION_QUERY);
80             if (sessionCount == 0)
81             {
82                 NativeMethods.SetQueryNetSessionCount(NativeMethods.SessionOp.SESSION_INCREMENT);
83             }
84
85             if (ownerWindow == null)
86             {
87                 this.ownerWindow = null;
88             }
89             else if (ownerWindow is IWin32Window)
90             {
91                 this.ownerWindow = (IWin32Window)ownerWindow;
92             }
93             else if (ownerWindow is IntPtr)
94             {
95                 this.ownerWindow = new WindowsFormsWin32Window { Handle = (IntPtr)ownerWindow };
96             }
97             else
98             {
99                 throw new AdalException(AdalError.InvalidOwnerWindowType,
100                     "Invalid owner window type. Expected types are IWin32Window or IntPtr (for window handle).");
101             }
102
103             this.webBrowser = new CustomWebBrowser();
104             this.webBrowser.PreviewKeyDown += webBrowser_PreviewKeyDown;
105             this.InitializeComponent();
106
107             whiteListedSchemes.Add("browser");
108         }
109
110         private void webBrowser_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
111         {
112             if (e.KeyCode == Keys.Back)
113             {
114                 key = Keys.Back;
115             }
116         }
117
118         /// <summary>
119         /// Gets Web Browser control used by the dialog.
120         /// </summary>
121         public WebBrowser WebBrowser
122         {
123             get { return this.webBrowser; }
124         }
125
126         /// <summary>
127         /// 
128         /// </summary>
129         /// <param name="sender"></param>
130         /// <param name="e"></param>
131         protected virtual void WebBrowserNavigatingHandler(object sender, WebBrowserNavigatingEventArgs e)
132         {
133             if (this.webBrowser.IsDisposed)
134             {
135                 // we cancel all flows in disposed object and just do nothing, let object to close.
136                 // it just for safety.
137                 e.Cancel = true;
138                 return;
139             }
140
141             if (key == Keys.Back)
142             {
143                 //navigation is being done via back key. This needs to be disabled.
144                 key = Keys.None;
145                 e.Cancel = true;
146             }
147
148             // we cancel further processing, if we reached final URL.
149             // Security issue: we prohibit navigation with auth code
150             // if redirect URI is URN, then we prohibit navigation, to prevent random browser popup.
151             e.Cancel = this.CheckForClosingUrl(e.Url);
152
153             // check if the url scheme is of type browser://
154             // this means we need to launch external browser
155             if (!e.Cancel && e.Url.Scheme.Equals("browser", StringComparison.CurrentCultureIgnoreCase))
156             {
157                 Process.Start(e.Url.AbsoluteUri.Replace("browser://", "https://"));
158                 e.Cancel = true;
159             }
160
161             if (!e.Cancel)
162             {
163                 PlatformPlugin.Logger.Verbose(null,
164                     string.Format(CultureInfo.CurrentCulture, " Navigating to '{0}'.",
165                         EncodingHelper.UrlDecode(e.Url.ToString())));
166             }
167         }
168
169         private void WebBrowserNavigatedHandler(object sender, WebBrowserNavigatedEventArgs e)
170         {
171             if (!this.CheckForClosingUrl(e.Url))
172             {
173                 PlatformPlugin.Logger.Verbose(null,
174                     string.Format(CultureInfo.CurrentCulture, " Navigated to '{0}'.",
175                         EncodingHelper.UrlDecode(e.Url.ToString())));
176             }
177         }
178
179         /// <summary>
180         /// 
181         /// </summary>
182         protected virtual void WebBrowserNavigateErrorHandler(object sender, WebBrowserNavigateErrorEventArgs e)
183         {
184             // e.StatusCode - Contains error code which we are able to translate this error to text
185             // ADAL.Native contains a code for translation.
186             if (this.DialogResult == DialogResult.OK)
187             {
188                 return;
189             }
190
191             if (this.webBrowser.IsDisposed)
192             {
193                 // we cancel all flow in disposed object.
194                 e.Cancel = true;
195                 return;
196             }
197
198             if (this.webBrowser.ActiveXInstance != e.WebBrowserActiveXInstance)
199             {
200                 // this event came from internal frame, ignore this.
201                 return;
202             }
203
204             if (e.StatusCode >= 300 && e.StatusCode < 400)
205             {
206                 // we could get redirect flows here as well.
207                 return;
208             }
209
210             e.Cancel = true;
211             this.StopWebBrowser();
212             // in this handler object could be already disposed, so it should be the last method
213             this.OnNavigationCanceled(e.StatusCode);
214         }
215
216         private bool CheckForClosingUrl(Uri url)
217         {
218             // Make change here
219             bool canClose = false;
220             if (url.Authority.Equals(this.desiredCallbackUri.Authority, StringComparison.CurrentCultureIgnoreCase) &&
221                 url.AbsolutePath.Equals(this.desiredCallbackUri.AbsolutePath, StringComparison.CurrentCulture))
222             {
223                 this.Result = new AuthorizationResult(AuthorizationStatus.Success, url.OriginalString);
224                 canClose = true;
225             }
226
227             // if redirect_uri is not hit and scheme of the url is not whitelisted and url is not about:blank
228             // and url scheme is not https then fail to load.
229             if (!canClose && !whiteListedSchemes.Contains(url.Scheme.ToLower(CultureInfo.CurrentCulture)) &&
230                 !url.AbsoluteUri.Equals("about:blank", StringComparison.CurrentCultureIgnoreCase)
231                 && !url.Scheme.Equals("https", StringComparison.CurrentCultureIgnoreCase) 
232                 && !url.Scheme.Equals("javascript", StringComparison.CurrentCultureIgnoreCase))
233             {
234                 this.Result = new AuthorizationResult(AuthorizationStatus.ErrorHttp);
235                 this.Result.Error = AdalError.NonHttpsRedirectNotSupported;
236                 this.Result.ErrorDescription = AdalErrorMessage.NonHttpsRedirectNotSupported;
237                 canClose = true;
238             }
239
240             if (canClose)
241             {
242                 this.StopWebBrowser();
243                 // in this handler object could be already disposed, so it should be the last method
244                 this.OnClosingUrl();
245             }
246
247             return canClose;
248         }
249
250         private void StopWebBrowser()
251         {
252             if (!this.webBrowser.IsDisposed)
253             {
254                 if (this.webBrowser.IsBusy)
255                 {
256                     PlatformPlugin.Logger.Verbose(null,
257                         string.Format(CultureInfo.CurrentCulture,
258                             " WebBrowser state: IsBusy: {0}, ReadyState: {1}, Created: {2}, Disposing: {3}, IsDisposed: {4}, IsOffline: {5}",
259                             this.webBrowser.IsBusy, this.webBrowser.ReadyState, this.webBrowser.Created,
260                             this.webBrowser.Disposing, this.webBrowser.IsDisposed, this.webBrowser.IsOffline));
261                     this.webBrowser.Stop();
262                     PlatformPlugin.Logger.Verbose(null,
263                         string.Format(CultureInfo.CurrentCulture,
264                             " WebBrowser state (after Stop): IsBusy: {0}, ReadyState: {1}, Created: {2}, Disposing: {3}, IsDisposed: {4}, IsOffline: {5}",
265                             this.webBrowser.IsBusy, this.webBrowser.ReadyState, this.webBrowser.Created,
266                             this.webBrowser.Disposing, this.webBrowser.IsDisposed, this.webBrowser.IsOffline));
267                 }
268             }
269         }
270
271         /// <summary>
272         /// 
273         /// </summary>
274         protected abstract void OnClosingUrl();
275
276         /// <summary>
277         /// 
278         /// </summary>
279         /// <param name="statusCode"></param>
280         protected abstract void OnNavigationCanceled(int statusCode);
281
282         internal AuthorizationResult AuthenticateAAD(Uri requestUri, Uri callbackUri)
283         {
284             this.desiredCallbackUri = callbackUri;
285             this.Result = null;
286
287             // The WebBrowser event handlers must not throw exceptions.
288             // If they do then they may be swallowed by the native
289             // browser com control.
290             this.webBrowser.Navigating += this.WebBrowserNavigatingHandler;
291             this.webBrowser.Navigated += this.WebBrowserNavigatedHandler;
292             this.webBrowser.NavigateError += this.WebBrowserNavigateErrorHandler;
293
294             this.webBrowser.Navigate(requestUri);
295             this.OnAuthenticate();
296
297             return this.Result;
298         }
299
300         /// <summary>
301         /// 
302         /// </summary>
303         protected virtual void OnAuthenticate()
304         {
305         }
306
307         private void InitializeComponent()
308         {
309             Screen screen = (this.ownerWindow != null)
310                 ? Screen.FromHandle(this.ownerWindow.Handle)
311                 : Screen.PrimaryScreen;
312
313             // Window height is set to 70% of the screen height.
314             int uiHeight = (int)(Math.Max(screen.WorkingArea.Height, 160) * 70.0 / DpiHelper.ZoomPercent);
315             this.webBrowserPanel = new Panel();
316             this.webBrowserPanel.SuspendLayout();
317             this.SuspendLayout();
318
319             // 
320             // webBrowser
321             // 
322             this.webBrowser.Dock = DockStyle.Fill;
323             this.webBrowser.Location = new Point(0, 25);
324             this.webBrowser.MinimumSize = new Size(20, 20);
325             this.webBrowser.Name = "webBrowser";
326             this.webBrowser.Size = new Size(UIWidth, 565);
327             this.webBrowser.TabIndex = 1;
328             this.webBrowser.IsWebBrowserContextMenuEnabled = false;
329
330             // 
331             // webBrowserPanel
332             // 
333             this.webBrowserPanel.Controls.Add(this.webBrowser);
334             this.webBrowserPanel.Dock = DockStyle.Fill;
335             this.webBrowserPanel.BorderStyle = BorderStyle.None;
336             this.webBrowserPanel.Location = new Point(0, 0);
337             this.webBrowserPanel.Name = "webBrowserPanel";
338             this.webBrowserPanel.Size = new Size(UIWidth, uiHeight);
339             this.webBrowserPanel.TabIndex = 2;
340
341             // 
342             // BrowserAuthenticationWindow
343             // 
344             this.AutoScaleDimensions = new SizeF(6, 13);
345             this.AutoScaleMode = AutoScaleMode.Font;
346             this.ClientSize = new Size(UIWidth, uiHeight);
347             this.Controls.Add(this.webBrowserPanel);
348             this.FormBorderStyle = FormBorderStyle.FixedSingle;
349             this.Name = "BrowserAuthenticationWindow";
350
351             // Move the window to the center of the parent window only if owner window is set.
352             this.StartPosition = (this.ownerWindow != null)
353                 ? FormStartPosition.CenterParent
354                 : FormStartPosition.CenterScreen;
355             this.Text = string.Empty;
356             this.ShowIcon = false;
357             this.MaximizeBox = false;
358             this.MinimizeBox = false;
359
360             // If we don't have an owner we need to make sure that the pop up browser 
361             // window is in the task bar so that it can be selected with the mouse.
362             this.ShowInTaskbar = (null == this.ownerWindow);
363
364             this.webBrowserPanel.ResumeLayout(false);
365             this.ResumeLayout(false);
366         }
367
368         private sealed class WindowsFormsWin32Window : IWin32Window
369         {
370             public IntPtr Handle { get; set; }
371         }
372
373         /// <summary>
374         /// 
375         /// </summary>
376         /// <param name="disposing"></param>
377         protected override void Dispose(bool disposing)
378         {
379             if (disposing)
380             {
381                 StopWebBrowser();
382             }
383
384             base.Dispose(disposing);
385         }
386
387         /// <summary>
388         /// 
389         /// </summary>
390         /// <param name="statusCode"></param>
391         /// <returns></returns>
392         protected AdalException CreateExceptionForAuthenticationUiFailed(int statusCode)
393         {
394             if (NavigateErrorStatus.Messages.ContainsKey(statusCode))
395             {
396                 return new AdalServiceException(
397                     AdalError.AuthenticationUiFailed,
398                     string.Format(CultureInfo.CurrentCulture,
399                         " The browser based authentication dialog failed to complete. Reason: {0}",
400                         NavigateErrorStatus.Messages[statusCode]))
401                 { StatusCode = statusCode };
402             }
403
404             return new AdalServiceException(
405                 AdalError.AuthenticationUiFailed,
406                 string.Format(CultureInfo.CurrentCulture,
407                     " The browser based authentication dialog failed to complete for an unknown reason. StatusCode: {0}",
408                     statusCode))
409             { StatusCode = statusCode };
410         }
411
412         /// <summary>
413         /// 
414         /// </summary>
415         protected static class DpiHelper
416         {
417             static DpiHelper()
418             {
419                 const double DefaultDpi = 96.0;
420
421                 const int LOGPIXELSX = 88;
422                 const int LOGPIXELSY = 90;
423
424                 double deviceDpiX;
425                 double deviceDpiY;
426
427                 IntPtr dC = NativeWrapper.NativeMethods.GetDC(IntPtr.Zero);
428                 if (dC != IntPtr.Zero)
429                 {
430                     deviceDpiX = NativeWrapper.NativeMethods.GetDeviceCaps(dC, LOGPIXELSX);
431                     deviceDpiY = NativeWrapper.NativeMethods.GetDeviceCaps(dC, LOGPIXELSY);
432                     NativeWrapper.NativeMethods.ReleaseDC(IntPtr.Zero, dC);
433                 }
434                 else
435                 {
436                     deviceDpiX = DefaultDpi;
437                     deviceDpiY = DefaultDpi;
438                 }
439
440                 int zoomPercentX = (int)(100 * (deviceDpiX / DefaultDpi));
441                 int zoomPercentY = (int)(100 * (deviceDpiY / DefaultDpi));
442
443                 ZoomPercent = Math.Min(zoomPercentX, zoomPercentY);
444             }
445
446             /// <summary>
447             /// 
448             /// </summary>
449             public static int ZoomPercent { get; private set; }
450         }
451
452
453         internal static class NativeMethods
454         {
455             internal enum SessionOp
456             {
457                 SESSION_QUERY = 0,
458                 SESSION_INCREMENT,
459                 SESSION_DECREMENT
460             };
461
462             [DllImport("IEFRAME.dll", CallingConvention = CallingConvention.StdCall, ExactSpelling = true)]
463             internal static extern int SetQueryNetSessionCount(SessionOp sessionOp);
464         }
465     }
466 }