tgstation-server
The /tg/station 13 server suite
ApiHeaders.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Globalization;
4 using System.Linq;
5 using System.Net.Http.Headers;
6 using System.Reflection;
7 using Microsoft.AspNetCore.Http.Headers;
8 using Microsoft.Net.Http.Headers;
9 using Microsoft.Extensions.Primitives;
10 
11 namespace Tgstation.Server.Api
12 {
16  public sealed class ApiHeaders
17  {
21  public const string ApplicationJson = "application/json";
22 
26  const string ApiVersionHeader = "Api";
27 
31  const string usernameHeader = "Username";
32 
36  const string instanceIdHeader = "Instance";
37 
41  const string jwtAuthenticationScheme = "Bearer";
42 
46  const string passwordAuthenticationScheme = "Password";
47 
51  static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();
52 
56  public static Version Version => assemblyName.Version;
57 
61  public long? InstanceId { get; set; }
62 
66  public ProductHeaderValue UserAgent { get; }
67 
71  public Version ApiVersion { get; }
72 
76  public string Token { get; }
77 
81  public string Username { get; }
82 
86  public string Password { get; }
87 
91  public bool IsTokenAuthentication => Token != null;
92 
98  public static bool CheckCompatibility(Version otherVersion) => !(Version.Major != otherVersion.Major || Version.Minor != otherVersion.Minor || Version.Build > otherVersion.Build);
99 
105  public ApiHeaders(ProductHeaderValue userAgent, string token) : this(userAgent, token, null, null)
106  {
107  if (userAgent == null)
108  throw new ArgumentNullException(nameof(userAgent));
109  if (token == null)
110  throw new ArgumentNullException(nameof(token));
111  }
112 
119  public ApiHeaders(ProductHeaderValue userAgent, string username, string password) : this(userAgent, null, username, password)
120  {
121  if (userAgent == null)
122  throw new ArgumentNullException(nameof(userAgent));
123  if (username == null)
124  throw new ArgumentNullException(nameof(username));
125  if (password == null)
126  throw new ArgumentNullException(nameof(password));
127  }
128 
133  public ApiHeaders(RequestHeaders requestHeaders)
134  {
135  var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(ApplicationJson);
136  if (!requestHeaders.Accept.Any(x => x.MediaType == jsonAccept.MediaType))
137  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", ApplicationJson));
138 
139  if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgentValues) || !ProductInfoHeaderValue.TryParse(userAgentValues.FirstOrDefault(), out var clientUserAgent))
140  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} headers!", HeaderNames.UserAgent));
141 
142  //assure the client user agent has a name and version
143  if (String.IsNullOrWhiteSpace(clientUserAgent.Product.Name) || !Version.TryParse(clientUserAgent.Product.Version, out var clientVersion))
144  throw new InvalidOperationException("Malformed client user agent!");
145 
146  //make sure the api header matches ours
147  if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != assemblyName.Name)
148  throw new InvalidOperationException("Missing API version!");
149 
150  if (!Version.TryParse(apiUserAgent.Product.Version, out var apiVersion))
151  throw new InvalidOperationException("Malformed API version!");
152 
153  ApiVersion = apiVersion;
154  UserAgent = clientUserAgent.Product;
155 
156  if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization))
157  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization));
158  var auth = authorization.First();
159  var splits = new List<string>(auth.Split(' '));
160  var scheme = splits.First();
161  if (String.IsNullOrWhiteSpace(scheme))
162  throw new InvalidOperationException("Missing authentication scheme!");
163 
164  splits.RemoveAt(0);
165  var parameter = String.Concat(splits);
166  if (String.IsNullOrEmpty(parameter))
167  throw new InvalidOperationException("Missing authentication parameter!");
168 
169  if (requestHeaders.Headers.TryGetValue(instanceIdHeader, out var instanceIdValues))
170  {
171  var instanceIdString = instanceIdValues.FirstOrDefault();
172  if (instanceIdString != default && Int64.TryParse(instanceIdString, out var instanceId))
173  InstanceId = instanceId;
174  }
175 
176  switch (scheme)
177  {
178  case jwtAuthenticationScheme:
179  Token = parameter;
180  break;
181  case passwordAuthenticationScheme:
182  Password = parameter;
183  var fail = !requestHeaders.Headers.TryGetValue(usernameHeader, out var values);
184  if (!fail)
185  {
186  Username = values.FirstOrDefault();
187  fail = String.IsNullOrWhiteSpace(Username);
188  }
189  if (fail)
190  throw new InvalidOperationException("Missing Username header!");
191  break;
192  default:
193  throw new InvalidOperationException("Invalid authentication scheme!");
194  }
195  }
196 
204  ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)
205  {
206  UserAgent = userAgent;
207  Token = token;
208  Username = username;
209  Password = password;
210  ApiVersion = Version;
211  }
212 
217  public bool Compatible() => CheckCompatibility(ApiVersion);
218 
224  public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null)
225  {
226  if (headers == null)
227  throw new ArgumentNullException(nameof(headers));
228  if (instanceId.HasValue && InstanceId.HasValue && instanceId != InstanceId)
229  throw new InvalidOperationException("Specified instance ID in constructor and SetRequestHeaders!");
230 
231  headers.Clear();
232  headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
233  if (IsTokenAuthentication)
234  headers.Authorization = new AuthenticationHeaderValue(jwtAuthenticationScheme, Token);
235  else
236  {
237  headers.Authorization = new AuthenticationHeaderValue(passwordAuthenticationScheme, Password);
238  headers.Add(usernameHeader, Username);
239  }
240  headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
241  headers.Add(ApiVersionHeader, new ProductHeaderValue(assemblyName.Name, ApiVersion.ToString()).ToString());
242  instanceId = instanceId ?? InstanceId;
243  if (instanceId.HasValue)
244  headers.Add(instanceIdHeader, instanceId.ToString());
245  }
246  }
247 }
void SetRequestHeaders(HttpRequestHeaders headers, long?instanceId=null)
Set HttpRequestHeaders using the ApiHeaders. This initially clears headers
Definition: ApiHeaders.cs:224
ApiHeaders(ProductHeaderValue userAgent, string username, string password)
Construct ApiHeaders for password authentication
Definition: ApiHeaders.cs:119
ApiHeaders(ProductHeaderValue userAgent, string token)
Construct ApiHeaders for JWT authentication
Definition: ApiHeaders.cs:105
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:16
ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)
Construct ApiHeaders
Definition: ApiHeaders.cs:204
ApiHeaders(RequestHeaders requestHeaders)
Construct and validates ApiHeaders from a set of requestHeaders
Definition: ApiHeaders.cs:133
static Version Version
Get the version of the Api the caller is using
Definition: ApiHeaders.cs:56