Skip to content

Commit 16623dc

Browse files
authored
Team admin (#85)
* Add format to Identity objects * Add account name to the Identity's format * Add CHECK_ITEM and log messages * Add Add-TeamAdmin function * Add Remove-TfsTeamAdmin support * Fix 'get project by ID' * Add TfsCmdletsLib C# project to implement custom VssHttpClient classes * Add GenericHttpClient to support future "generic" (non-library-dependent) API calls * Add TeamAdminHttpClient to wrap missing TeamAdmin API classes
1 parent 109bdcf commit 16623dc

File tree

12 files changed

+569
-56
lines changed

12 files changed

+569
-56
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Text.RegularExpressions;
6+
using Microsoft.VisualStudio.Services.Common;
7+
using Microsoft.VisualStudio.Services.WebApi;
8+
9+
namespace TfsCmdlets
10+
{
11+
public class GenericHttpClient : VssHttpClientBase
12+
{
13+
#region Constructors and fields
14+
15+
public GenericHttpClient(Uri baseUrl, VssCredentials credentials) : base(baseUrl, credentials)
16+
{
17+
}
18+
19+
public GenericHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings) : base(
20+
baseUrl, credentials, settings)
21+
{
22+
}
23+
24+
public GenericHttpClient(Uri baseUrl, VssCredentials credentials, params DelegatingHandler[] handlers) : base(
25+
baseUrl, credentials, handlers)
26+
{
27+
}
28+
29+
public GenericHttpClient(Uri baseUrl, HttpMessageHandler pipeline, bool disposeHandler) : base(baseUrl,
30+
pipeline, disposeHandler)
31+
{
32+
}
33+
34+
public GenericHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings,
35+
params DelegatingHandler[] handlers) : base(baseUrl, credentials, settings, handlers)
36+
{
37+
}
38+
39+
#endregion
40+
41+
/// <summary>
42+
/// Sends a GET request to an Azure DevOps API
43+
/// </summary>
44+
/// <typeparam name="T">The typed model (JSON proxy class) corresponding to the API result</typeparam>
45+
/// <param name="apiPath">The path to the API, relative to the collection/organization URL</param>
46+
/// <param name="apiVersion">The version of the requested API (e.g. "5.1")</param>
47+
/// <param name="additionalHeaders">Any additional HTTP headers that must be sent along the request</param>
48+
/// <param name="queryParameters">Any query parameters ("query string") that are part of the request</param>
49+
/// <param name="mediaType">The MIME content type of the response</param>
50+
/// <param name="userState">User-defined, arbitrary data sent as a "userstate" HTTP header</param>
51+
/// <returns>The response of the API, converted to the model type T</returns>
52+
public T Get<T>(
53+
string apiPath,
54+
string apiVersion = "1.0",
55+
IDictionary<string, string> additionalHeaders = null,
56+
IDictionary<string, string> queryParameters = null,
57+
string mediaType = "application/json",
58+
object userState = null)
59+
{
60+
var msg = CreateMessage(HttpMethod.Get, apiPath, apiVersion,
61+
additionalHeaders, queryParameters, mediaType,
62+
null, null);
63+
64+
return SendAsync<T>(msg, userState).GetAwaiter().GetResult();
65+
}
66+
67+
/// <summary>
68+
/// Sends a GET request to an Azure DevOps API
69+
/// </summary>
70+
/// <param name="apiPath">The path to the API, relative to the collection/organization URL</param>
71+
/// <param name="apiVersion">The version of the requested API (e.g. "5.1")</param>
72+
/// <param name="additionalHeaders">Any additional HTTP headers that must be sent along the request</param>
73+
/// <param name="queryParameters">Any query parameters ("query string") that are part of the request</param>
74+
/// <param name="mediaType">The MIME content type of the response</param>
75+
/// <param name="userState">User-defined, arbitrary data sent as a "userstate" HTTP header</param>
76+
/// <returns>The response of the API as an HttpResponseMessage object</returns>
77+
public HttpResponseMessage Get(
78+
string apiPath,
79+
string apiVersion = "1.0",
80+
IDictionary<string, string> additionalHeaders = null,
81+
IDictionary<string, string> queryParameters = null,
82+
string mediaType = "application/json",
83+
object userState = null)
84+
{
85+
var msg = CreateMessage(HttpMethod.Get, apiPath, apiVersion,
86+
additionalHeaders, queryParameters,
87+
mediaType, null, null);
88+
89+
return Send(msg, userState);
90+
}
91+
92+
/// <summary>
93+
/// Sends a POST request to an Azure DevOps API
94+
/// </summary>
95+
/// <typeparam name="T">The typed model (JSON proxy class) corresponding to the API parameter (content) object</typeparam>
96+
/// <typeparam name="TResult">The typed model (JSON proxy class) corresponding to the API result</typeparam>
97+
/// <param name="apiPath">The path to the API, relative to the collection/organization URL</param>
98+
/// <param name="value">The API parameters sent as the request body</param>
99+
/// <param name="apiVersion">The version of the requested API (e.g. "5.1")</param>
100+
/// <param name="additionalHeaders">Any additional HTTP headers that must be sent along the request</param>
101+
/// <param name="queryParameters">Any query parameters ("query string") that are part of the request</param>
102+
/// <param name="mediaType">The MIME content type of the response</param>
103+
/// <param name="userState">User-defined, arbitrary data sent as a "userstate" HTTP header</param>
104+
/// <returns>The response of the API as an HttpResponseMessage object</returns>
105+
public TResult Post<T, TResult>(
106+
string apiPath,
107+
T value,
108+
string apiVersion = "1.0",
109+
IDictionary<string, string> additionalHeaders = null,
110+
IDictionary<string, string> queryParameters = null,
111+
string mediaType = "application/json",
112+
object userState = null)
113+
{
114+
var content = new ObjectContent<T>(value, new VssJsonMediaTypeFormatter());
115+
116+
var msg = CreateMessage(HttpMethod.Post, apiPath, apiVersion,
117+
additionalHeaders, queryParameters,
118+
mediaType, null, content);
119+
120+
var result = SendAsync<TResult>(msg, userState).GetAwaiter().GetResult();
121+
122+
return result;
123+
}
124+
125+
/// <summary>
126+
/// Sends a POST request to an Azure DevOps API
127+
/// </summary>
128+
/// <param name="apiPath">The path to the API, relative to the collection/organization URL</param>
129+
/// <param name="content">The API parameters sent as the request body</param>
130+
/// <param name="apiVersion">The version of the requested API (e.g. "5.1")</param>
131+
/// <param name="additionalHeaders">Any additional HTTP headers that must be sent along the request</param>
132+
/// <param name="queryParameters">Any query parameters ("query string") that are part of the request</param>
133+
/// <param name="mediaType">The MIME content type of the response</param>
134+
/// <param name="userState">User-defined, arbitrary data sent as a "userstate" HTTP header</param>
135+
/// <returns>The response of the API as an HttpResponseMessage object</returns>
136+
public HttpResponseMessage Post(
137+
string apiPath,
138+
HttpContent content,
139+
string apiVersion = "1.0",
140+
IDictionary<string, string> additionalHeaders = null,
141+
IDictionary<string, string> queryParameters = null,
142+
string mediaType = "application/json",
143+
object userState = null)
144+
{
145+
var msg = CreateMessage(HttpMethod.Post, apiPath, apiVersion,
146+
additionalHeaders, queryParameters,
147+
mediaType, null, content);
148+
149+
return Send(msg, userState);
150+
}
151+
152+
public T PostForm<T>(
153+
string formPath,
154+
Dictionary<string,string> formData,
155+
bool sendVerificationToken = false,
156+
string tokenRequestPath = null,
157+
IDictionary<string, string> additionalHeaders = null,
158+
IDictionary<string, string> queryParameters = null,
159+
string responseMediaType = "text/html",
160+
object userState = null)
161+
{
162+
if (formData == null) { throw new ArgumentNullException(nameof(formData)); }
163+
164+
if (sendVerificationToken)
165+
{
166+
var response = Get(tokenRequestPath);
167+
var html = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
168+
var matches = Regex.Match(html, "name=\"__RequestVerificationToken\" value=\"([^\\\"]*)");
169+
170+
if (matches.Groups.Count > 0)
171+
{
172+
formData["__RequestVerificationToken"] = matches.Groups[1].Value;
173+
}
174+
175+
var cookies = response.Headers.GetValues("Set-Cookie")?.ToList();
176+
177+
if (cookies?.Count > 0)
178+
{
179+
if (additionalHeaders == null)
180+
{
181+
additionalHeaders = new Dictionary<string, string>();
182+
}
183+
184+
var tokenCookie = string.Join("; ", cookies
185+
.Where(c => c.StartsWith("__RequestVerificationToken"))
186+
.Select(c => c.Substring(0, c.IndexOf(';'))));
187+
188+
if (additionalHeaders.ContainsKey("Cookie"))
189+
{
190+
var presetCookie = additionalHeaders["Cookie"];
191+
additionalHeaders["Cookie"] = $"{tokenCookie};{presetCookie}";
192+
}
193+
else
194+
{
195+
additionalHeaders["Cookie"] = $"{tokenCookie};";
196+
}
197+
}
198+
}
199+
200+
var content = new FormUrlEncodedContent(formData);
201+
202+
var msg = CreateMessage(HttpMethod.Post, formPath, "1",
203+
additionalHeaders, queryParameters,
204+
responseMediaType, null, content);
205+
206+
return SendAsync<T>(msg, userState).GetAwaiter().GetResult();
207+
}
208+
209+
private HttpRequestMessage CreateMessage(HttpMethod method, string apiPath, string apiVersion,
210+
IDictionary<string, string> additionalHeaders,
211+
IDictionary<string, string> queryParameters, string mediaType, object routeValues,
212+
HttpContent content)
213+
{
214+
var msg = CreateRequestMessage(
215+
method,
216+
additionalHeaders,
217+
new ApiResourceLocation
218+
{
219+
ReleasedVersion = new Version(1, 0),
220+
MinVersion = new Version(1, 0),
221+
MaxVersion = new Version(9, 9),
222+
RouteTemplate = apiPath,
223+
ResourceVersion = 1
224+
},
225+
routeValues,
226+
new ApiResourceVersion(apiVersion),
227+
content,
228+
queryParameters,
229+
mediaType);
230+
return msg;
231+
}
232+
}
233+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"profiles": {
3+
"TfsCmdletsLib": {
4+
"commandName": "Executable"
5+
}
6+
}
7+
}

0 commit comments

Comments
 (0)