tgstation-server 5.12.1
The /tg/station 13 server suite
Loading...
Searching...
No Matches
ApiHeaders.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Net.Http.Headers;
5using System.Net.Mime;
6using System.Reflection;
7using System.Text;
8
9using Microsoft.AspNetCore.Http.Headers;
10using Microsoft.Extensions.Primitives;
11using Microsoft.Net.Http.Headers;
12
15
17{
21 public sealed class ApiHeaders
22 {
26 public const string ApiVersionHeader = "Api";
27
31 public const string InstanceIdHeader = "Instance";
32
36 public const string OAuthProviderHeader = "OAuthProvider";
37
41 public const string BearerAuthenticationScheme = "Bearer";
42
46 public const string BasicAuthenticationScheme = "Basic";
47
51 public const string OAuthAuthenticationScheme = "OAuth";
52
56 public const string ApplicationJsonMime = "application/json";
57
62
66 static readonly AssemblyName AssemblyName = Assembly.GetExecutingAssembly().GetName();
67
71 public long? InstanceId { get; set; }
72
76 public ProductHeaderValue? UserAgent => ProductInfoHeaderValue.TryParse(RawUserAgent, out var userAgent) ? userAgent.Product : null;
77
81 public string? RawUserAgent { get; }
82
86 public Version ApiVersion { get; }
87
91 public string? Token { get; }
92
96 public string? Username { get; }
97
101 public string? Password { get; }
102
107
111 public bool IsTokenAuthentication => Token != null && !OAuthProvider.HasValue;
112
118 public static bool CheckCompatibility(Version otherVersion) => Version.Major == (otherVersion?.Major ?? throw new ArgumentNullException(nameof(otherVersion)));
119
126 public ApiHeaders(ProductHeaderValue userAgent, string token, OAuthProvider? oauthProvider = null) : this(userAgent, token, null, null)
127 {
128 if (userAgent == null)
129 throw new ArgumentNullException(nameof(userAgent));
130 if (token == null)
131 throw new ArgumentNullException(nameof(token));
132
133 OAuthProvider = oauthProvider;
134 }
135
142 public ApiHeaders(ProductHeaderValue userAgent, string username, string password) : this(userAgent, null, username, password)
143 {
144 if (userAgent == null)
145 throw new ArgumentNullException(nameof(userAgent));
146 if (username == null)
147 throw new ArgumentNullException(nameof(username));
148 if (password == null)
149 throw new ArgumentNullException(nameof(password));
150 }
151
158#pragma warning disable CA1502
159 public ApiHeaders(RequestHeaders requestHeaders, bool ignoreMissingAuth = false)
160 {
161 if (requestHeaders == null)
162 throw new ArgumentNullException(nameof(requestHeaders));
163
164 var badHeaders = HeaderTypes.None;
165 var errorBuilder = new StringBuilder();
166
167 void AddError(HeaderTypes headerType, string message)
168 {
169 if (badHeaders != HeaderTypes.None)
170 errorBuilder.AppendLine();
171 badHeaders |= headerType;
172 errorBuilder.Append(message);
173 }
174
175 var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(ApplicationJsonMime);
176 if (!requestHeaders.Accept.Any(x => jsonAccept.IsSubsetOf(x)))
177 AddError(HeaderTypes.Accept, $"Client does not accept {ApplicationJsonMime}!");
178
179 if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgentValues) || userAgentValues.Count == 0)
180 AddError(HeaderTypes.UserAgent, $"Missing {HeaderNames.UserAgent} header!");
181 else
182 {
183 RawUserAgent = userAgentValues.First();
184 if (String.IsNullOrWhiteSpace(RawUserAgent))
185 AddError(HeaderTypes.UserAgent, $"Malformed {HeaderNames.UserAgent} header!");
186 }
187
188 // make sure the api header matches ours
189 Version? apiVersion = null;
190 if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != AssemblyName.Name)
191 AddError(HeaderTypes.Api, $"Missing {ApiVersionHeader} header!");
192 else if (!Version.TryParse(apiUserAgent.Product.Version, out apiVersion))
193 AddError(HeaderTypes.Api, $"Malformed {ApiVersionHeader} header!");
194
195 if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization))
196 {
197 if (!ignoreMissingAuth)
198 AddError(HeaderTypes.Authorization, $"Missing {HeaderNames.Authorization} header!");
199 }
200 else
201 {
202 var auth = authorization.First();
203 var splits = new List<string>(auth.Split(' '));
204 var scheme = splits.First();
205 if (String.IsNullOrWhiteSpace(scheme))
206 AddError(HeaderTypes.Authorization, "Missing authentication scheme!");
207 else
208 {
209 splits.RemoveAt(0);
210 var parameter = String.Concat(splits);
211 if (String.IsNullOrEmpty(parameter))
212 AddError(HeaderTypes.Authorization, "Missing authentication parameter!");
213 else
214 {
215 if (requestHeaders.Headers.TryGetValue(InstanceIdHeader, out var instanceIdValues))
216 {
217 var instanceIdString = instanceIdValues.FirstOrDefault();
218 if (instanceIdString != default && Int64.TryParse(instanceIdString, out var instanceId))
219 InstanceId = instanceId;
220 }
221
222 switch (scheme)
223 {
225 if (requestHeaders.Headers.TryGetValue(OAuthProviderHeader, out StringValues oauthProviderValues))
226 {
227 var oauthProviderString = oauthProviderValues.First();
228 if (Enum.TryParse<OAuthProvider>(oauthProviderString, out var oauthProvider))
229 OAuthProvider = oauthProvider;
230 else
231 AddError(HeaderTypes.OAuthProvider, "Invalid OAuth provider!");
232 }
233 else
234 AddError(HeaderTypes.OAuthProvider, $"Missing {OAuthProviderHeader} header!");
235
238 Token = parameter;
239 break;
241 string badBasicAuthHeaderMessage = $"Invalid basic {HeaderNames.Authorization} header!";
242 string joinedString;
243 try
244 {
245 var base64Bytes = Convert.FromBase64String(parameter);
246 joinedString = Encoding.UTF8.GetString(base64Bytes);
247 }
248 catch
249 {
250 AddError(HeaderTypes.Authorization, badBasicAuthHeaderMessage);
251 break;
252 }
253
254 var basicAuthSplits = joinedString.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
255 if (basicAuthSplits.Length < 2)
256 {
257 AddError(HeaderTypes.Authorization, badBasicAuthHeaderMessage);
258 break;
259 }
260
261 Username = basicAuthSplits.First();
262 Password = String.Concat(basicAuthSplits.Skip(1));
263 break;
264 default:
265 AddError(HeaderTypes.Authorization, "Invalid authentication scheme!");
266 break;
267 }
268 }
269 }
270 }
271
272 if (badHeaders != HeaderTypes.None)
273 throw new HeadersException(badHeaders, errorBuilder.ToString());
274
275 ApiVersion = apiVersion!.Semver();
276 }
277#pragma warning restore CA1502
278
286 ApiHeaders(ProductHeaderValue userAgent, string? token, string? username, string? password)
287 {
288 RawUserAgent = userAgent?.ToString();
289 Token = token;
290 Username = username;
291 Password = password;
293 }
294
300
306 public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null)
307 {
308 if (headers == null)
309 throw new ArgumentNullException(nameof(headers));
310 if (instanceId.HasValue && InstanceId.HasValue && instanceId != InstanceId)
311 throw new InvalidOperationException("Specified different instance IDs in constructor and SetRequestHeaders!");
312
313 headers.Clear();
314 headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJsonMime));
315 headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
316 headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
317 if (OAuthProvider.HasValue)
318 {
319 headers.Authorization = new AuthenticationHeaderValue(OAuthAuthenticationScheme, Token);
320 headers.Add(OAuthProviderHeader, OAuthProvider.ToString());
321 }
322 else if (!IsTokenAuthentication)
323 headers.Authorization = new AuthenticationHeaderValue(
325 Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}")));
326 else
327 headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, Token);
328
329 instanceId ??= InstanceId;
330 if (instanceId.HasValue)
331 headers.Add(InstanceIdHeader, instanceId.ToString());
332 }
333 }
334}
Represents the header that must be present for every server request.
Definition: ApiHeaders.cs:22
ProductHeaderValue? UserAgent
The client's user agent as a ProductHeaderValue if valid.
Definition: ApiHeaders.cs:76
const string OAuthAuthenticationScheme
The JWT authentication header scheme.
Definition: ApiHeaders.cs:51
string? RawUserAgent
The client's raw user agent.
Definition: ApiHeaders.cs:81
const string InstanceIdHeader
The InstanceId header key.
Definition: ApiHeaders.cs:31
bool IsTokenAuthentication
If the header uses password or TGS JWT authentication.
Definition: ApiHeaders.cs:111
bool Compatible()
Checks if the ApiVersion is compatible with Version.
static bool CheckCompatibility(Version otherVersion)
Checks if a given otherVersion is compatible with our own.
string? Username
The client's username.
Definition: ApiHeaders.cs:96
Version ApiVersion
The client's API version.
Definition: ApiHeaders.cs:86
string? Token
The client's JWT.
Definition: ApiHeaders.cs:91
ApiHeaders(ProductHeaderValue userAgent, string? token, string? username, string? password)
Initializes a new instance of the ApiHeaders class.
Definition: ApiHeaders.cs:286
void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId=null)
Set HttpRequestHeaders using the ApiHeaders. This initially clears headers .
Definition: ApiHeaders.cs:306
ApiHeaders(RequestHeaders requestHeaders, bool ignoreMissingAuth=false)
Initializes a new instance of the ApiHeaders class.
Definition: ApiHeaders.cs:159
long? InstanceId
The instance EntityId.Id being accessed.
Definition: ApiHeaders.cs:71
static readonly Version Version
Get the version of the Api the caller is using.
Definition: ApiHeaders.cs:61
const string BasicAuthenticationScheme
The JWT authentication header scheme.
Definition: ApiHeaders.cs:46
const string ApiVersionHeader
The ApiVersion header key.
Definition: ApiHeaders.cs:26
const string ApplicationJsonMime
Added to MediaTypeNames.Application in netstandard2.1. Can't use because of Tgstation....
Definition: ApiHeaders.cs:56
const string BearerAuthenticationScheme
The JWT authentication header scheme.
Definition: ApiHeaders.cs:41
ApiHeaders(ProductHeaderValue userAgent, string username, string password)
Initializes a new instance of the ApiHeaders class. Used for password authentication.
Definition: ApiHeaders.cs:142
static readonly AssemblyName AssemblyName
The current System.Reflection.AssemblyName.
Definition: ApiHeaders.cs:66
ApiHeaders(ProductHeaderValue userAgent, string token, OAuthProvider? oauthProvider=null)
Initializes a new instance of the ApiHeaders class. Used for token authentication.
Definition: ApiHeaders.cs:126
const string OAuthProviderHeader
The OAuthProvider header key.
Definition: ApiHeaders.cs:36
string? Password
The client's password.
Definition: ApiHeaders.cs:101
Thrown when trying to generate ApiHeaders from Microsoft.AspNetCore.Http.Headers.RequestHeaders fails...
Attribute for bringing in the HTTP API version from MSBuild.
static ApiVersionAttribute Instance
Return the Assembly's instance of the ApiVersionAttribute.
string RawApiVersion
The Version string of the TGS API definition.
OAuthProvider
List of OAuth providers supported by TGS.
HeaderTypes
Types of individual ApiHeaders.
Definition: HeaderTypes.cs:10