|
1 |
| -using System.ComponentModel; |
| 1 | +using System; |
| 2 | +using System.ComponentModel; |
| 3 | +using System.IO; |
| 4 | +using System.Linq; |
2 | 5 | using System.Net;
|
| 6 | +using System.Net.Http; |
3 | 7 | using System.Net.Http.Headers;
|
4 | 8 | using Microsoft.VisualStudio.Services.WebApi;
|
5 | 9 | using System.Text;
|
|
8 | 12 | using Microsoft.TeamFoundation.Core.WebApi;
|
9 | 13 | using Microsoft.VisualStudio.Services.Common;
|
10 | 14 |
|
11 |
| -void PrintError(Exception exception, string indent = "") |
| 15 | +namespace SpecSync.AzureDevOps.ConnectionTester |
12 | 16 | {
|
13 |
| - var errorCode = ""; |
14 |
| - if (exception is Win32Exception win32Exception) |
| 17 | + static class Program |
15 | 18 | {
|
16 |
| - errorCode = $" (0x{win32Exception.NativeErrorCode:x8})"; |
17 |
| - } |
18 |
| - Console.WriteLine($"{indent}{exception.GetType().FullName}{errorCode}: {exception.Message}"); |
19 |
| - if (exception is AggregateException aggregateException) |
20 |
| - { |
21 |
| - foreach (var innerEx in aggregateException.InnerExceptions) |
| 19 | + public static void Main(string[] args) |
22 | 20 | {
|
23 |
| - PrintError(innerEx, indent + " ---> "); |
24 |
| - } |
25 |
| - } |
26 |
| - else if (exception.InnerException != null) |
27 |
| - { |
28 |
| - PrintError(exception.InnerException, indent + " ---> "); |
29 |
| - } |
30 |
| - |
31 |
| - //if (indent == "") |
32 |
| - //{ |
33 |
| - // Console.WriteLine(); |
34 |
| - // Console.WriteLine(exception); |
35 |
| - //} |
36 |
| -} |
37 | 21 |
|
38 |
| -X509Certificate LoadClientCertificate(string filePath) |
39 |
| -{ |
40 |
| - try |
41 |
| - { |
42 |
| - return new X509Certificate2(filePath); |
43 |
| - } |
44 |
| - catch (Exception) |
45 |
| - { |
46 |
| - Console.Write("Please specify password for client certificate or leave empty if no password required: "); |
47 |
| - var certPassword = Console.ReadLine(); |
48 |
| - return new X509Certificate2(filePath, certPassword); |
49 |
| - } |
50 |
| -} |
| 22 | + Console.WriteLine("*** SpecSync for Azure DevOps Connection Tester ***"); |
| 23 | + Console.WriteLine(); |
| 24 | + |
| 25 | + if (args.Length < 2) |
| 26 | + { |
| 27 | + Console.WriteLine("Usage:"); |
| 28 | + Console.WriteLine( |
| 29 | + " SpecSync.ConnectionTester.exe <project-url> <pat> [<client-certificate-file>.pfx]"); |
| 30 | + Console.WriteLine("OR"); |
| 31 | + Console.WriteLine( |
| 32 | + " SpecSync.ConnectionTester.exe <project-url> <username> <password> [<client-certificate-file>.pfx]"); |
| 33 | + return; |
| 34 | + } |
| 35 | + |
| 36 | + var projectUrl = args[0]; |
| 37 | + var userNameOrPat = args[1]; |
| 38 | + var password = args.Length == 2 || args[2].EndsWith(".pfx", StringComparison.InvariantCultureIgnoreCase) |
| 39 | + ? "" |
| 40 | + : args[2]; |
| 41 | + var clientCertificateFile = |
| 42 | + args.Last().EndsWith(".pfx", StringComparison.InvariantCultureIgnoreCase) ? args.Last() : null; |
| 43 | + |
| 44 | + ParseAdoProjectUrl(projectUrl, out var collectionUrl, out var projectName); |
| 45 | + |
| 46 | + if (clientCertificateFile != null) |
| 47 | + { |
| 48 | + clientCertificateFile = Path.GetFullPath(clientCertificateFile); |
| 49 | + if (!File.Exists(clientCertificateFile)) |
| 50 | + throw new InvalidOperationException( |
| 51 | + $"Client certificate file does not exist: {clientCertificateFile}"); |
| 52 | + } |
| 53 | + |
| 54 | + Console.WriteLine($"Collection URL: {collectionUrl}"); |
| 55 | + Console.WriteLine($"Project Name: {projectName}"); |
| 56 | + if (clientCertificateFile != null) |
| 57 | + Console.WriteLine($"Client certificate file: {clientCertificateFile}"); |
| 58 | + Console.WriteLine(); |
| 59 | + |
| 60 | + var clientCertificate = clientCertificateFile != null ? LoadClientCertificate(clientCertificateFile) : null; |
| 61 | + |
| 62 | + Console.WriteLine("Testing connection with Azure DevOps .NET API..."); |
| 63 | + |
| 64 | + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; |
| 65 | + |
| 66 | + try |
| 67 | + { |
| 68 | + var vssConnection = CreateVssConnection(new Uri(collectionUrl), |
| 69 | + new VssBasicCredential(userNameOrPat, password), |
| 70 | + clientCertificate); |
| 71 | + vssConnection.ConnectAsync().Wait(); |
| 72 | + vssConnection.GetClient<ProjectHttpClient>().GetProject(projectName).Wait(); |
| 73 | + Console.WriteLine("Succeeded!"); |
| 74 | + } |
| 75 | + catch (Exception ex) |
| 76 | + { |
| 77 | + Console.WriteLine("Failed!"); |
| 78 | + PrintError(ex); |
| 79 | + } |
| 80 | + |
| 81 | + Console.WriteLine(); |
| 82 | + Console.WriteLine(); |
| 83 | + Console.WriteLine("Testing connection with HttpClient..."); |
| 84 | + try |
| 85 | + { |
| 86 | + var handler = new HttpClientHandler(); |
| 87 | + if (clientCertificate != null) |
| 88 | + handler.ClientCertificates.Add(clientCertificate); |
| 89 | + else |
| 90 | + handler.ClientCertificateOptions = ClientCertificateOption.Automatic; |
| 91 | + |
| 92 | + var httpClient = new HttpClient(handler); |
| 93 | + httpClient.BaseAddress = new Uri(collectionUrl + "/"); |
| 94 | + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", |
| 95 | + Convert.ToBase64String(Encoding.UTF8.GetBytes(userNameOrPat + ":" + password))); |
| 96 | + var response = httpClient.GetAsync($"_apis/projects/{projectName}?includeHistory=False").Result; |
| 97 | + Console.WriteLine($" {response.RequestMessage?.RequestUri}"); |
| 98 | + Console.WriteLine($" {(int)response.StatusCode} ({response.StatusCode})"); |
| 99 | + if (response.StatusCode != HttpStatusCode.OK) |
| 100 | + Console.WriteLine("Failed! Wrong HTTP status code."); |
| 101 | + else |
| 102 | + Console.WriteLine("Succeeded!"); |
| 103 | + } |
| 104 | + catch (Exception ex) |
| 105 | + { |
| 106 | + Console.WriteLine("Failed!"); |
| 107 | + PrintError(ex); |
| 108 | + } |
| 109 | + |
| 110 | + Console.WriteLine(); |
| 111 | + Console.WriteLine(); |
| 112 | + } |
51 | 113 |
|
52 |
| -void ParseAdoProjectUrl(string url, out string adoCollectionUrl, out string adoProjectName) |
53 |
| -{ |
54 |
| - url = url.TrimEnd('/'); |
55 |
| - var lastSlash = url.LastIndexOf('/'); |
56 |
| - if (lastSlash < 0) |
57 |
| - { |
58 |
| - throw new InvalidOperationException($"Unable to parse Azure DevOps project URL: {url}"); |
59 |
| - } |
60 |
| - adoCollectionUrl = url.Substring(0, lastSlash); |
61 |
| - adoProjectName = url.Substring(lastSlash + 1); |
62 |
| - adoProjectName = WebUtility.UrlDecode(adoProjectName); |
63 |
| -} |
| 114 | + static VssConnection CreateVssConnection(Uri adoCollectionUrl, VssCredentials credentials, X509Certificate clientCertificate) |
| 115 | + { |
| 116 | + var vssHttpRequestSettings = VssClientHttpRequestSettings.Default.Clone(); |
| 117 | + vssHttpRequestSettings.ServerCertificateValidationCallback = ServerCertificateValidationCallback; |
| 118 | + if (clientCertificate != null) |
| 119 | + { |
| 120 | + vssHttpRequestSettings.ClientCertificateManager = new ClientCertificateManager(); |
| 121 | + vssHttpRequestSettings.ClientCertificateManager.ClientCertificates.Add(clientCertificate); |
| 122 | + } |
| 123 | + |
| 124 | + var httpMessageHandlers = Enumerable.Empty<DelegatingHandler>(); |
| 125 | + |
| 126 | + var innerHandler = new HttpClientHandler(); |
| 127 | + var vssHttpMessageHandler = new VssHttpMessageHandler(credentials, vssHttpRequestSettings, innerHandler); |
| 128 | + if (clientCertificate == null) |
| 129 | + innerHandler.ClientCertificateOptions = ClientCertificateOption.Automatic; |
| 130 | + return new VssConnection(adoCollectionUrl, |
| 131 | + vssHttpMessageHandler, |
| 132 | + httpMessageHandlers); |
| 133 | + } |
64 | 134 |
|
65 |
| -VssConnection CreateVssConnection(Uri adoCollectionUrl, VssCredentials credentials, X509Certificate? clientCertificate) |
66 |
| -{ |
67 |
| - var vssHttpRequestSettings = VssClientHttpRequestSettings.Default.Clone(); |
68 |
| - vssHttpRequestSettings.ServerCertificateValidationCallback = ServerCertificateValidationCallback; |
69 |
| - if (clientCertificate != null) |
70 |
| - { |
71 |
| - vssHttpRequestSettings.ClientCertificateManager = new ClientCertificateManager(); |
72 |
| - vssHttpRequestSettings.ClientCertificateManager.ClientCertificates.Add(clientCertificate); |
73 |
| - } |
| 135 | + static X509Certificate LoadClientCertificate(string filePath) |
| 136 | + { |
| 137 | + try |
| 138 | + { |
| 139 | + return new X509Certificate2(filePath); |
| 140 | + } |
| 141 | + catch (Exception) |
| 142 | + { |
| 143 | + Console.Write( |
| 144 | + "Please specify password for client certificate or leave empty if no password required: "); |
| 145 | + var certPassword = Console.ReadLine(); |
| 146 | + return new X509Certificate2(filePath, certPassword); |
| 147 | + } |
| 148 | + } |
74 | 149 |
|
75 |
| - var httpMessageHandlers = Enumerable.Empty<DelegatingHandler>(); |
| 150 | + static bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, |
| 151 | + SslPolicyErrors errors) |
| 152 | + { |
| 153 | + if (errors == SslPolicyErrors.None) |
| 154 | + { |
| 155 | + Console.WriteLine(" SSL validation passed."); |
| 156 | + return true; |
| 157 | + } |
| 158 | + |
| 159 | + var hashString = certificate.GetCertHashString(); |
| 160 | + Console.WriteLine($"SSL policy error(s) '{errors}' found for certificate thumbprint '{hashString}'."); |
| 161 | + Console.WriteLine(" SSL validation failed. Ignoring..."); |
| 162 | + return true; |
| 163 | + } |
76 | 164 |
|
77 |
| - var innerHandler = new HttpClientHandler(); |
78 |
| - var vssHttpMessageHandler = new VssHttpMessageHandler(credentials, vssHttpRequestSettings, innerHandler); |
79 |
| - if (clientCertificate == null) |
80 |
| - innerHandler.ClientCertificateOptions = ClientCertificateOption.Automatic; |
81 |
| - return new VssConnection(adoCollectionUrl, |
82 |
| - vssHttpMessageHandler, |
83 |
| - httpMessageHandlers); |
84 |
| -} |
| 165 | + static void ParseAdoProjectUrl(string url, out string adoCollectionUrl, out string adoProjectName) |
| 166 | + { |
| 167 | + url = url.TrimEnd('/'); |
| 168 | + var lastSlash = url.LastIndexOf('/'); |
| 169 | + if (lastSlash < 0) |
| 170 | + { |
| 171 | + throw new InvalidOperationException($"Unable to parse Azure DevOps project URL: {url}"); |
| 172 | + } |
| 173 | + |
| 174 | + adoCollectionUrl = url.Substring(0, lastSlash); |
| 175 | + adoProjectName = url.Substring(lastSlash + 1); |
| 176 | + adoProjectName = WebUtility.UrlDecode(adoProjectName); |
| 177 | + } |
85 | 178 |
|
86 |
| -bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) |
87 |
| -{ |
88 |
| - if (errors == SslPolicyErrors.None) |
89 |
| - { |
90 |
| - Console.WriteLine(" SSL validation passed."); |
91 |
| - return true; |
| 179 | + static void PrintError(Exception exception, string indent = "") |
| 180 | + { |
| 181 | + var errorCode = ""; |
| 182 | + if (exception is Win32Exception win32Exception) |
| 183 | + { |
| 184 | + errorCode = $" (0x{win32Exception.NativeErrorCode:x8})"; |
| 185 | + } |
| 186 | + |
| 187 | + Console.WriteLine($"{indent}{exception.GetType().FullName}{errorCode}: {exception.Message}"); |
| 188 | + if (exception is AggregateException aggregateException) |
| 189 | + { |
| 190 | + foreach (var innerEx in aggregateException.InnerExceptions) |
| 191 | + { |
| 192 | + PrintError(innerEx, indent + " ---> "); |
| 193 | + } |
| 194 | + } |
| 195 | + else if (exception.InnerException != null) |
| 196 | + { |
| 197 | + PrintError(exception.InnerException, indent + " ---> "); |
| 198 | + } |
| 199 | + |
| 200 | + //more detailed log: |
| 201 | + //if (indent == "") |
| 202 | + //{ |
| 203 | + // Console.WriteLine(); |
| 204 | + // Console.WriteLine(exception); |
| 205 | + //} |
| 206 | + } |
92 | 207 | }
|
93 |
| - |
94 |
| - var hashString = certificate.GetCertHashString(); |
95 |
| - Console.WriteLine($"SSL policy error(s) '{errors}' found for certificate thumbprint '{hashString}'."); |
96 |
| - Console.WriteLine(" SSL validation failed. Ignoring..."); |
97 |
| - return true; |
98 |
| -} |
99 |
| - |
100 |
| - |
101 |
| - |
102 |
| -Console.WriteLine("*** SpecSync for Azure DevOps Connection Tester ***"); |
103 |
| -Console.WriteLine(); |
104 |
| - |
105 |
| -if (args.Length < 2) |
106 |
| -{ |
107 |
| - Console.WriteLine("Usage:"); |
108 |
| - Console.WriteLine(" SpecSync.ConnectionTester.exe <project-url> <pat> [<client-certificate-file>.pfx]"); |
109 |
| - Console.WriteLine("OR"); |
110 |
| - Console.WriteLine(" SpecSync.ConnectionTester.exe <project-url> <username> <password> [<client-certificate-file>.pfx]"); |
111 |
| - return; |
112 |
| -} |
113 |
| - |
114 |
| -var projectUrl = args[0]; |
115 |
| -var userNameOrPat = args[1]; |
116 |
| -var password = args.Length == 2 || args[2].EndsWith(".pfx", StringComparison.InvariantCultureIgnoreCase) ? "" : args[2]; |
117 |
| -var clientCertificateFile = args.Last().EndsWith(".pfx", StringComparison.InvariantCultureIgnoreCase) ? args.Last() : null; |
118 |
| - |
119 |
| -ParseAdoProjectUrl(projectUrl, out var collectionUrl, out var projectName); |
120 |
| - |
121 |
| -if (clientCertificateFile != null) |
122 |
| -{ |
123 |
| - clientCertificateFile = Path.GetFullPath(clientCertificateFile); |
124 |
| - if (!File.Exists(clientCertificateFile)) |
125 |
| - throw new InvalidOperationException($"Client certificate file does not exist: {clientCertificateFile}"); |
126 |
| -} |
127 |
| - |
128 |
| -Console.WriteLine($"Collection URL: {collectionUrl}"); |
129 |
| -Console.WriteLine($"Project Name: {projectName}"); |
130 |
| -if (clientCertificateFile != null) |
131 |
| - Console.WriteLine($"Client certificate file: {clientCertificateFile}"); |
132 |
| -Console.WriteLine(); |
133 |
| - |
134 |
| -var clientCertificate = clientCertificateFile != null ? LoadClientCertificate(clientCertificateFile) : null; |
135 |
| - |
136 |
| -Console.WriteLine("Testing connection with Azure DevOps .NET API..."); |
137 |
| - |
138 |
| -ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; |
139 |
| - |
140 |
| -try |
141 |
| -{ |
142 |
| - var vssConnection = CreateVssConnection(new Uri(collectionUrl), new VssBasicCredential(userNameOrPat, password), clientCertificate); |
143 |
| - vssConnection.ConnectAsync().Wait(); |
144 |
| - vssConnection.GetClient<ProjectHttpClient>().GetProject(projectName).Wait(); |
145 |
| - Console.WriteLine("Succeeded!"); |
146 |
| -} |
147 |
| -catch (Exception ex) |
148 |
| -{ |
149 |
| - Console.WriteLine("Failed!"); |
150 |
| - PrintError(ex); |
151 |
| -} |
152 |
| -Console.WriteLine(); |
153 |
| -Console.WriteLine(); |
154 |
| -Console.WriteLine("Testing connection with HttpClient..."); |
155 |
| -try |
156 |
| -{ |
157 |
| - var handler = new HttpClientHandler(); |
158 |
| - if (clientCertificate != null) |
159 |
| - handler.ClientCertificates.Add(clientCertificate); |
160 |
| - else |
161 |
| - handler.ClientCertificateOptions = ClientCertificateOption.Automatic; |
162 |
| - |
163 |
| - var httpClient = new HttpClient(handler); |
164 |
| - httpClient.BaseAddress = new Uri(collectionUrl + "/"); |
165 |
| - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(userNameOrPat + ":" + password))); |
166 |
| - var response = httpClient.GetAsync($"_apis/projects/{projectName}?includeHistory=False").Result; |
167 |
| - Console.WriteLine($" {response.RequestMessage?.RequestUri}"); |
168 |
| - Console.WriteLine($" {(int)response.StatusCode} ({response.StatusCode})"); |
169 |
| - if (response.StatusCode != HttpStatusCode.OK) |
170 |
| - Console.WriteLine("Failed! Wrong HTTP status code."); |
171 |
| - else |
172 |
| - Console.WriteLine("Succeeded!"); |
173 |
| -} |
174 |
| -catch (Exception ex) |
175 |
| -{ |
176 |
| - Console.WriteLine("Failed!"); |
177 |
| - PrintError(ex); |
178 | 208 | }
|
179 |
| -Console.WriteLine(); |
180 |
| -Console.WriteLine(); |
|
0 commit comments