-
Notifications
You must be signed in to change notification settings - Fork 343
/
SeleniumWebUI.cs
161 lines (139 loc) · 5.33 KB
/
SeleniumWebUI.cs
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
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.UI;
using OpenQA.Selenium;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace Microsoft.Identity.Test.Integration.Infrastructure
{
internal class SeleniumWebUI : ICustomWebUi
{
private readonly Action<IWebDriver> _seleniumAutomationLogic;
private const string CloseWindowSuccessHtml = @"<html>
<head><title>Authentication Complete</title></head>
<body>
Authentication complete. You can return to the application. Feel free to close this browser tab.
</body>
</html>";
private const string CloseWindowFailureHtml = @"<html>
<head><title>Authentication Failed</title></head>
<body>
Authentication failed. You can return to the application. Feel free to close this browser tab.
</br></br></br></br>
Error details: error {0} error_description: {1}
</body>
</html>";
public SeleniumWebUI(Action<IWebDriver> seleniumAutomationLogic)
{
_seleniumAutomationLogic = seleniumAutomationLogic;
}
public async Task<Uri> AcquireAuthorizationCodeAsync(
Uri authorizationUri,
Uri redirectUri,
CancellationToken cancellationToken)
{
if (redirectUri.IsDefaultPort)
{
throw new InvalidOperationException("Cannot listen to localhost (no port), please call UpdateRedirectUri to get a free localhost:port address");
}
Uri result = await SeleniumAcquireAuthAsync(
authorizationUri,
redirectUri,
cancellationToken)
.ConfigureAwait(true);
return result;
}
public void ValidateRedirectUri(Uri redirectUri)
{
if (!redirectUri.IsLoopback)
{
throw new ArgumentException("Only loopback redirect uri");
}
if (redirectUri.IsDefaultPort)
{
throw new ArgumentException("Port required");
}
}
public static string FindFreeLocalhostRedirectUri()
{
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
int port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return "http://localhost:" + port;
}
private IWebDriver InitDriverAndGoToUrl(string url)
{
IWebDriver driver = null;
try
{
driver = SeleniumExtensions.CreateDefaultWebDriver();
driver.Navigate().GoToUrl(url);
return driver;
}
catch (Exception)
{
driver?.Dispose();
throw;
}
}
private async Task<Uri> SeleniumAcquireAuthAsync(
Uri authorizationUri,
Uri redirectUri,
CancellationToken cancellationToken)
{
using (var driver = InitDriverAndGoToUrl(authorizationUri.OriginalString))
using (var listener = new SingleMessageTcpListener(redirectUri.Port)) // starts listening
{
Uri authCodeUri = null;
var listenForAuthCodeTask = listener.ListenToSingleRequestAndRespondAsync(
(uri) =>
{
Trace.WriteLine("Intercepted an auth code url: " + uri.ToString());
authCodeUri = uri;
return GetMessageToShowInBroswerAfterAuth(uri);
},
cancellationToken);
try
{
// Run the tcp listener and the selenium automation in parallel
var seleniumAutomationTask = Task.Run(() =>
{
_seleniumAutomationLogic(driver);
});
await Task.WhenAll(seleniumAutomationTask, listenForAuthCodeTask).ConfigureAwait(false);
return authCodeUri;
}
catch (SocketException ex)
{
throw new MsalCustomWebUiFailedException(
"A socket exception occured " + ex.Message + " socket error code " + ex.SocketErrorCode);
}
catch (Exception ex)
{
throw new MsalCustomWebUiFailedException("Unkown error: " + ex.Message);
}
}
}
private static string GetMessageToShowInBroswerAfterAuth(Uri uri)
{
// Parse the uri to understand if an error was returned. This is done just to show the user a nice error message in the browser.
var authCodeQueryKeyValue = HttpUtility.ParseQueryString(uri.Query);
string errorString = authCodeQueryKeyValue.Get("error");
if (!string.IsNullOrEmpty(errorString))
{
return string.Format(
CultureInfo.InvariantCulture,
CloseWindowFailureHtml,
errorString,
authCodeQueryKeyValue.Get("error_description"));
}
return CloseWindowSuccessHtml;
}
}
}