tgstation-server  4.3.2
The /tg/station 13 server suite
SwaggerConfiguration.cs
Go to the documentation of this file.
1 using Microsoft.Extensions.DependencyInjection;
2 using Microsoft.Net.Http.Headers;
3 using Microsoft.OpenApi.Any;
4 using Microsoft.OpenApi.Models;
5 using Swashbuckle.AspNetCore.SwaggerGen;
6 using System;
7 using System.Collections.Generic;
8 using System.Linq;
9 using System.Net;
10 using Tgstation.Server.Api;
14 
15 namespace Tgstation.Server.Host.Core
16 {
21  {
25  const string PasswordSecuritySchemeId = "Password_Login_Scheme";
26 
30  const string TokenSecuritySchemeId = "Token_Authorization_Scheme";
31 
32  static void AddDefaultResponses(OpenApiDocument document)
33  {
34  var errorMessageContent = new Dictionary<string, OpenApiMediaType>
35  {
36  {
38  new OpenApiMediaType
39  {
40  Schema = new OpenApiSchema
41  {
42  Reference = new OpenApiReference
43  {
44  Id = nameof(ErrorMessage),
45  Type = ReferenceType.Schema
46  }
47  }
48  }
49  }
50  };
51 
52  void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete)
53  {
54  string responseKey = $"{(int)code}";
55 
56  document.Components.Responses.Add(responseKey, concrete);
57 
58  var referenceResponse = new OpenApiResponse
59  {
60  Reference = new OpenApiReference
61  {
62  Type = ReferenceType.Response,
63  Id = responseKey
64  }
65  };
66 
67  foreach (var operation in document.Paths.SelectMany(path => path.Value.Operations))
68  operation.Value.Responses.TryAdd(responseKey, referenceResponse);
69  }
70 
71  AddDefaultResponse(HttpStatusCode.BadRequest, new OpenApiResponse
72  {
73  Description = "A badly formatted request was made. See error message for details.",
74  Content = errorMessageContent,
75  });
76 
77  AddDefaultResponse(HttpStatusCode.Unauthorized, new OpenApiResponse
78  {
79  Description = "No/invalid token provided."
80  });
81 
82  AddDefaultResponse(HttpStatusCode.Forbidden, new OpenApiResponse
83  {
84  Description = "User lacks sufficient permissions for the operation."
85  });
86 
87  AddDefaultResponse(HttpStatusCode.Conflict, new OpenApiResponse
88  {
89  Description = "A data integrity check failed while performing the operation. See error message for details.",
90  Content = errorMessageContent
91  });
92 
93  AddDefaultResponse(HttpStatusCode.InternalServerError, new OpenApiResponse
94  {
95  Description = "The server encountered an unhandled error. See error message for details.",
96  Content = errorMessageContent
97  });
98 
99  AddDefaultResponse(HttpStatusCode.ServiceUnavailable, new OpenApiResponse
100  {
101  Description = "The server may be starting up or shutting down."
102  });
103 
104  AddDefaultResponse(HttpStatusCode.NotImplemented, new OpenApiResponse
105  {
106  Description = "This operation requires POSIX system identites to be implemented. See https://github.com/tgstation/tgstation-server/issues/709",
107  Content = errorMessageContent
108  });
109  }
110 
117  public static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
118  {
119  swaggerGenOptions.SwaggerDoc(
120  "v1",
121  new OpenApiInfo
122  {
123  Title = "TGS API",
124  Version = ApiHeaders.Version.Semver().ToString()
125  });
126 
127  // Important to do this before applying our own filters
128  // Otherwise we'll get NullReferenceExceptions on parameters to be setup in our document filter
129  swaggerGenOptions.IncludeXmlComments(assemblyDocumentationPath);
130  swaggerGenOptions.IncludeXmlComments(apiDocumentationPath);
131 
132  swaggerGenOptions.OperationFilter<SwaggerConfiguration>();
133  swaggerGenOptions.DocumentFilter<SwaggerConfiguration>();
134  swaggerGenOptions.SchemaFilter<SwaggerConfiguration>();
135 
136  swaggerGenOptions.CustomSchemaIds(type =>
137  {
138  if (type == typeof(Api.Models.Internal.User))
139  return "ShallowUser";
140 
141  return type.Name;
142  });
143 
144  swaggerGenOptions.AddSecurityDefinition(PasswordSecuritySchemeId, new OpenApiSecurityScheme
145  {
146  In = ParameterLocation.Header,
147  Type = SecuritySchemeType.Http,
148  Name = HeaderNames.Authorization,
150  });
151 
152  swaggerGenOptions.AddSecurityDefinition(TokenSecuritySchemeId, new OpenApiSecurityScheme
153  {
154  BearerFormat = "JWT",
155  In = ParameterLocation.Header,
156  Type = SecuritySchemeType.Http,
157  Name = HeaderNames.Authorization,
159  });
160  }
161 
163  public void Apply(OpenApiOperation operation, OperationFilterContext context)
164  {
165  if (operation == null)
166  throw new ArgumentNullException(nameof(operation));
167  if (context == null)
168  throw new ArgumentNullException(nameof(context));
169 
170  operation.OperationId = $"{context.MethodInfo.DeclaringType.Name}.{context.MethodInfo.Name}";
171 
172  var authAttributes = context
173  .MethodInfo
174  .DeclaringType
175  .GetCustomAttributes(true)
176  .Union(
177  context
178  .MethodInfo
179  .GetCustomAttributes(true))
180  .OfType<TgsAuthorizeAttribute>();
181 
182  if (authAttributes.Any())
183  {
184  var tokenScheme = new OpenApiSecurityScheme
185  {
186  Reference = new OpenApiReference
187  {
188  Type = ReferenceType.SecurityScheme,
189  Id = TokenSecuritySchemeId
190  }
191  };
192 
193  operation.Security = new List<OpenApiSecurityRequirement>
194  {
195  new OpenApiSecurityRequirement
196  {
197  {
198  tokenScheme,
199  new List<string>()
200  }
201  }
202  };
203 
204  if (authAttributes.Any(attr => attr.RightsType.HasValue && RightsHelper.IsInstanceRight(attr.RightsType.Value)))
205  operation.Parameters.Add(new OpenApiParameter
206  {
207  Reference = new OpenApiReference
208  {
209  Type = ReferenceType.Parameter,
211  }
212  });
213  }
214  else
215  {
216  // HomeController.CreateToken
217  var passwordScheme = new OpenApiSecurityScheme
218  {
219  Reference = new OpenApiReference
220  {
221  Type = ReferenceType.SecurityScheme,
222  Id = PasswordSecuritySchemeId
223  }
224  };
225 
226  operation.Security = new List<OpenApiSecurityRequirement>
227  {
228  new OpenApiSecurityRequirement
229  {
230  {
231  passwordScheme,
232  new List<string>()
233  }
234  }
235  };
236  }
237  }
238 
240  public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
241  {
242  if (swaggerDoc == null)
243  throw new ArgumentNullException(nameof(swaggerDoc));
244  if (context == null)
245  throw new ArgumentNullException(nameof(context));
246 
247  swaggerDoc.Components.Parameters.Add(ApiHeaders.InstanceIdHeader, new OpenApiParameter
248  {
249  In = ParameterLocation.Header,
250  Name = ApiHeaders.InstanceIdHeader,
251  Description = "The instance ID being accessed",
252  Required = true,
253  Style = ParameterStyle.Simple,
254  Schema = new OpenApiSchema
255  {
256  Type = "integer"
257  }
258  });
259 
260  var productHeaderSchema = new OpenApiSchema
261  {
262  Type = "string",
263  Format = "productheader"
264  };
265 
266  swaggerDoc.Components.Parameters.Add(ApiHeaders.ApiVersionHeader, new OpenApiParameter
267  {
268  In = ParameterLocation.Header,
269  Name = ApiHeaders.ApiVersionHeader,
270  Description = "The API version being used in the form \"Tgstation.Server.Api/[API version]\"",
271  Required = true,
272  Style = ParameterStyle.Simple,
273  Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}"),
274  Schema = productHeaderSchema
275  });
276 
277  swaggerDoc.Components.Parameters.Add(HeaderNames.UserAgent, new OpenApiParameter
278  {
279  In = ParameterLocation.Header,
280  Name = HeaderNames.UserAgent,
281  Description = "The user agent of the calling client.",
282  Required = true,
283  Style = ParameterStyle.Simple,
284  Example = new OpenApiString("Your-user-agent/1.0.0.0"),
285  Schema = productHeaderSchema
286  });
287 
288  string bridgeOperationPath = null;
289  foreach (var path in swaggerDoc.Paths)
290  foreach (var operation in path.Value.Operations.Select(x => x.Value))
291  {
292  if (operation.OperationId.Equals("BridgeController.Process", StringComparison.Ordinal))
293  {
294  bridgeOperationPath = path.Key;
295  continue;
296  }
297 
298  operation.Parameters.Add(new OpenApiParameter
299  {
300  Reference = new OpenApiReference
301  {
302  Type = ReferenceType.Parameter,
304  },
305  });
306 
307  operation.Parameters.Add(new OpenApiParameter
308  {
309  Reference = new OpenApiReference
310  {
311  Type = ReferenceType.Parameter,
312  Id = HeaderNames.UserAgent
313  }
314  });
315  }
316 
317  swaggerDoc.Paths.Remove(bridgeOperationPath);
318 
319  AddDefaultResponses(swaggerDoc);
320  }
321 
323  public void Apply(OpenApiSchema schema, SchemaFilterContext context)
324  {
325  if (schema == null)
326  throw new ArgumentNullException(nameof(schema));
327  if (context == null)
328  throw new ArgumentNullException(nameof(context));
329 
330  // Nothing is required
331  schema.Required.Clear();
332 
333  if (!schema.Enum?.Any() ?? false)
334  return;
335 
336  // Could be nullable type, make sure to get the right one
337  Type enumType = context.Type.IsConstructedGenericType
338  ? context.Type.GenericTypeArguments.First()
339  : context.Type;
340 
341  OpenApiEnumVarNamesExtension.Apply(schema, enumType);
342  }
343  }
344 }
static bool IsInstanceRight(RightsType rightsType)
Check if a given rightsType is meant for an Models.Instance
void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
void Apply(OpenApiSchema schema, SchemaFilterContext context)
const string ApplicationJson
TODO: Remove this when we upgrade to .NET Standard 2.1
Definition: ApiHeaders.cs:22
Helper for using the AuthorizeAttribute with the Api.Rights system
void Apply(OpenApiOperation operation, OperationFilterContext context)
Implements various filters for Swashbuckle.
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
static readonly Version Version
Get the version of the Api the caller is using
Definition: ApiHeaders.cs:62
static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
Configure the swagger settings.
const string JwtAuthenticationScheme
The JWT authentication header scheme
Definition: ApiHeaders.cs:37
static void Apply(OpenApiSchema openApiSchema, Type enumType)
Applies the extension to a give openApiSchema .
const string ApiVersionHeader
The ApiVersion header key
Definition: ApiHeaders.cs:27
const string InstanceIdHeader
The InstanceId header key
Definition: ApiHeaders.cs:32
static void AddDefaultResponses(OpenApiDocument document)
Implements the "x-enum-varnames" OpenAPI 3.0 extension.
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
const string BasicAuthenticationScheme
The JWT authentication header scheme
Definition: ApiHeaders.cs:42