1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

Turn on file scoped namespaces (#2225)

This commit is contained in:
Justin Baur
2022-08-29 14:53:16 -04:00
committed by GitHub
parent 7c4521e0b4
commit 34fb4cca2a
1206 changed files with 73816 additions and 75022 deletions

View File

@ -1,13 +1,12 @@
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Bit.Api.Utilities
namespace Bit.Api.Utilities;
public class ApiExplorerGroupConvention : IControllerModelConvention
{
public class ApiExplorerGroupConvention : IControllerModelConvention
public void Apply(ControllerModel controller)
{
public void Apply(ControllerModel controller)
{
var controllerNamespace = controller.ControllerType.Namespace;
controller.ApiExplorer.GroupName = controllerNamespace.Contains(".Public.") ? "public" : "internal";
}
var controllerNamespace = controller.ControllerType.Namespace;
controller.ApiExplorer.GroupName = controllerNamespace.Contains(".Public.") ? "public" : "internal";
}
}

View File

@ -4,70 +4,69 @@ using Azure.Messaging.EventGrid.SystemEvents;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Utilities
namespace Bit.Api.Utilities;
public static class ApiHelpers
{
public static class ApiHelpers
public static string EventGridKey { get; set; }
public async static Task<T> ReadJsonFileFromBody<T>(HttpContext httpContext, IFormFile file, long maxSize = 51200)
{
public static string EventGridKey { get; set; }
public async static Task<T> ReadJsonFileFromBody<T>(HttpContext httpContext, IFormFile file, long maxSize = 51200)
T obj = default(T);
if (file != null && httpContext.Request.ContentLength.HasValue && httpContext.Request.ContentLength.Value <= maxSize)
{
T obj = default(T);
if (file != null && httpContext.Request.ContentLength.HasValue && httpContext.Request.ContentLength.Value <= maxSize)
try
{
try
{
using var stream = file.OpenReadStream();
obj = await JsonSerializer.DeserializeAsync<T>(stream, JsonHelpers.IgnoreCase);
}
catch { }
using var stream = file.OpenReadStream();
obj = await JsonSerializer.DeserializeAsync<T>(stream, JsonHelpers.IgnoreCase);
}
return obj;
catch { }
}
/// <summary>
/// Validates Azure event subscription and calls the appropriate event handler. Responds HttpOk.
/// </summary>
/// <param name="request">HttpRequest received from Azure</param>
/// <param name="eventTypeHandlers">Dictionary of eventType strings and their associated handlers.</param>
/// <returns>OkObjectResult</returns>
/// <remarks>Reference https://docs.microsoft.com/en-us/azure/event-grid/receive-events</remarks>
public async static Task<ObjectResult> HandleAzureEvents(HttpRequest request,
Dictionary<string, Func<EventGridEvent, Task>> eventTypeHandlers)
return obj;
}
/// <summary>
/// Validates Azure event subscription and calls the appropriate event handler. Responds HttpOk.
/// </summary>
/// <param name="request">HttpRequest received from Azure</param>
/// <param name="eventTypeHandlers">Dictionary of eventType strings and their associated handlers.</param>
/// <returns>OkObjectResult</returns>
/// <remarks>Reference https://docs.microsoft.com/en-us/azure/event-grid/receive-events</remarks>
public async static Task<ObjectResult> HandleAzureEvents(HttpRequest request,
Dictionary<string, Func<EventGridEvent, Task>> eventTypeHandlers)
{
var queryKey = request.Query["key"];
if (!CoreHelpers.FixedTimeEquals(queryKey, EventGridKey))
{
var queryKey = request.Query["key"];
return new UnauthorizedObjectResult("Authentication failed. Please use a valid key.");
}
if (!CoreHelpers.FixedTimeEquals(queryKey, EventGridKey))
var response = string.Empty;
var requestData = await BinaryData.FromStreamAsync(request.Body);
var eventGridEvents = EventGridEvent.ParseMany(requestData);
foreach (var eventGridEvent in eventGridEvents)
{
if (eventGridEvent.TryGetSystemEventData(out object systemEvent))
{
return new UnauthorizedObjectResult("Authentication failed. Please use a valid key.");
}
var response = string.Empty;
var requestData = await BinaryData.FromStreamAsync(request.Body);
var eventGridEvents = EventGridEvent.ParseMany(requestData);
foreach (var eventGridEvent in eventGridEvents)
{
if (eventGridEvent.TryGetSystemEventData(out object systemEvent))
if (systemEvent is SubscriptionValidationEventData eventData)
{
if (systemEvent is SubscriptionValidationEventData eventData)
// Might want to enable additional validation: subject, topic etc.
var responseData = new SubscriptionValidationResponse()
{
// Might want to enable additional validation: subject, topic etc.
var responseData = new SubscriptionValidationResponse()
{
ValidationResponse = eventData.ValidationCode
};
ValidationResponse = eventData.ValidationCode
};
return new OkObjectResult(responseData);
}
}
if (eventTypeHandlers.ContainsKey(eventGridEvent.EventType))
{
await eventTypeHandlers[eventGridEvent.EventType](eventGridEvent);
return new OkObjectResult(responseData);
}
}
return new OkObjectResult(response);
if (eventTypeHandlers.ContainsKey(eventGridEvent.EventType))
{
await eventTypeHandlers[eventGridEvent.EventType](eventGridEvent);
}
}
return new OkObjectResult(response);
}
}

View File

@ -1,21 +1,20 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Bit.Api.Utilities
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
namespace Bit.Api.Utilities;
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}

View File

@ -6,117 +6,116 @@ using Microsoft.IdentityModel.Tokens;
using Stripe;
using InternalApi = Bit.Core.Models.Api;
namespace Bit.Api.Utilities
{
public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute
{
private readonly bool _publicApi;
namespace Bit.Api.Utilities;
public ExceptionHandlerFilterAttribute(bool publicApi)
public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute
{
private readonly bool _publicApi;
public ExceptionHandlerFilterAttribute(bool publicApi)
{
_publicApi = publicApi;
}
public override void OnException(ExceptionContext context)
{
var errorMessage = "An error has occurred.";
var exception = context.Exception;
if (exception == null)
{
_publicApi = publicApi;
// Should never happen.
return;
}
public override void OnException(ExceptionContext context)
ErrorResponseModel publicErrorModel = null;
InternalApi.ErrorResponseModel internalErrorModel = null;
if (exception is BadRequestException badRequestException)
{
var errorMessage = "An error has occurred.";
var exception = context.Exception;
if (exception == null)
context.HttpContext.Response.StatusCode = 400;
if (badRequestException.ModelState != null)
{
// Should never happen.
return;
}
ErrorResponseModel publicErrorModel = null;
InternalApi.ErrorResponseModel internalErrorModel = null;
if (exception is BadRequestException badRequestException)
{
context.HttpContext.Response.StatusCode = 400;
if (badRequestException.ModelState != null)
{
if (_publicApi)
{
publicErrorModel = new ErrorResponseModel(badRequestException.ModelState);
}
else
{
internalErrorModel = new InternalApi.ErrorResponseModel(badRequestException.ModelState);
}
}
else
{
errorMessage = badRequestException.Message;
}
}
else if (exception is StripeException stripeException && stripeException?.StripeError?.Type == "card_error")
{
context.HttpContext.Response.StatusCode = 400;
if (_publicApi)
{
publicErrorModel = new ErrorResponseModel(stripeException.StripeError.Param,
stripeException.Message);
publicErrorModel = new ErrorResponseModel(badRequestException.ModelState);
}
else
{
internalErrorModel = new InternalApi.ErrorResponseModel(stripeException.StripeError.Param,
stripeException.Message);
internalErrorModel = new InternalApi.ErrorResponseModel(badRequestException.ModelState);
}
}
else if (exception is GatewayException)
{
errorMessage = exception.Message;
context.HttpContext.Response.StatusCode = 400;
}
else if (exception is NotSupportedException && !string.IsNullOrWhiteSpace(exception.Message))
{
errorMessage = exception.Message;
context.HttpContext.Response.StatusCode = 400;
}
else if (exception is ApplicationException)
{
context.HttpContext.Response.StatusCode = 402;
}
else if (exception is NotFoundException)
{
errorMessage = "Resource not found.";
context.HttpContext.Response.StatusCode = 404;
}
else if (exception is SecurityTokenValidationException)
{
errorMessage = "Invalid token.";
context.HttpContext.Response.StatusCode = 403;
}
else if (exception is UnauthorizedAccessException)
{
errorMessage = "Unauthorized.";
context.HttpContext.Response.StatusCode = 401;
}
else
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ExceptionHandlerFilterAttribute>>();
logger.LogError(0, exception, exception.Message);
errorMessage = "An unhandled server error has occurred.";
context.HttpContext.Response.StatusCode = 500;
errorMessage = badRequestException.Message;
}
}
else if (exception is StripeException stripeException && stripeException?.StripeError?.Type == "card_error")
{
context.HttpContext.Response.StatusCode = 400;
if (_publicApi)
{
var errorModel = publicErrorModel ?? new ErrorResponseModel(errorMessage);
context.Result = new ObjectResult(errorModel);
publicErrorModel = new ErrorResponseModel(stripeException.StripeError.Param,
stripeException.Message);
}
else
{
var errorModel = internalErrorModel ?? new InternalApi.ErrorResponseModel(errorMessage);
var env = context.HttpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
if (env.IsDevelopment())
{
errorModel.ExceptionMessage = exception.Message;
errorModel.ExceptionStackTrace = exception.StackTrace;
errorModel.InnerExceptionMessage = exception?.InnerException?.Message;
}
context.Result = new ObjectResult(errorModel);
internalErrorModel = new InternalApi.ErrorResponseModel(stripeException.StripeError.Param,
stripeException.Message);
}
}
else if (exception is GatewayException)
{
errorMessage = exception.Message;
context.HttpContext.Response.StatusCode = 400;
}
else if (exception is NotSupportedException && !string.IsNullOrWhiteSpace(exception.Message))
{
errorMessage = exception.Message;
context.HttpContext.Response.StatusCode = 400;
}
else if (exception is ApplicationException)
{
context.HttpContext.Response.StatusCode = 402;
}
else if (exception is NotFoundException)
{
errorMessage = "Resource not found.";
context.HttpContext.Response.StatusCode = 404;
}
else if (exception is SecurityTokenValidationException)
{
errorMessage = "Invalid token.";
context.HttpContext.Response.StatusCode = 403;
}
else if (exception is UnauthorizedAccessException)
{
errorMessage = "Unauthorized.";
context.HttpContext.Response.StatusCode = 401;
}
else
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ExceptionHandlerFilterAttribute>>();
logger.LogError(0, exception, exception.Message);
errorMessage = "An unhandled server error has occurred.";
context.HttpContext.Response.StatusCode = 500;
}
if (_publicApi)
{
var errorModel = publicErrorModel ?? new ErrorResponseModel(errorMessage);
context.Result = new ObjectResult(errorModel);
}
else
{
var errorModel = internalErrorModel ?? new InternalApi.ErrorResponseModel(errorMessage);
var env = context.HttpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
if (env.IsDevelopment())
{
errorModel.ExceptionMessage = exception.Message;
errorModel.ExceptionStackTrace = exception.StackTrace;
errorModel.InnerExceptionMessage = exception?.InnerException?.Message;
}
context.Result = new ObjectResult(errorModel);
}
}
}

View File

@ -3,27 +3,26 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using InternalApi = Bit.Core.Models.Api;
namespace Bit.Api.Utilities
namespace Bit.Api.Utilities;
public class ModelStateValidationFilterAttribute : SharedWeb.Utilities.ModelStateValidationFilterAttribute
{
public class ModelStateValidationFilterAttribute : SharedWeb.Utilities.ModelStateValidationFilterAttribute
private readonly bool _publicApi;
public ModelStateValidationFilterAttribute(bool publicApi)
{
private readonly bool _publicApi;
_publicApi = publicApi;
}
public ModelStateValidationFilterAttribute(bool publicApi)
protected override void OnModelStateInvalid(ActionExecutingContext context)
{
if (_publicApi)
{
_publicApi = publicApi;
context.Result = new BadRequestObjectResult(new ErrorResponseModel(context.ModelState));
}
protected override void OnModelStateInvalid(ActionExecutingContext context)
else
{
if (_publicApi)
{
context.Result = new BadRequestObjectResult(new ErrorResponseModel(context.ModelState));
}
else
{
context.Result = new BadRequestObjectResult(new InternalApi.ErrorResponseModel(context.ModelState));
}
context.Result = new BadRequestObjectResult(new InternalApi.ErrorResponseModel(context.ModelState));
}
}
}

View File

@ -5,75 +5,41 @@ using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Bit.Api.Utilities
namespace Bit.Api.Utilities;
public static class MultipartFormDataHelper
{
public static class MultipartFormDataHelper
private static readonly FormOptions _defaultFormOptions = new FormOptions();
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, string, string, Task> callback)
{
private static readonly FormOptions _defaultFormOptions = new FormOptions();
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, string, string, Task> callback)
var firstSection = await reader.ReadNextSectionAsync();
if (firstSection != null)
{
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var firstSection = await reader.ReadNextSectionAsync();
if (firstSection != null)
if (ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out var firstContent))
{
if (ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out var firstContent))
if (HasFileContentDisposition(firstContent))
{
if (HasFileContentDisposition(firstContent))
// Old style with just data
var fileName = HeaderUtilities.RemoveQuotes(firstContent.FileName).ToString();
using (firstSection.Body)
{
// Old style with just data
var fileName = HeaderUtilities.RemoveQuotes(firstContent.FileName).ToString();
using (firstSection.Body)
{
await callback(firstSection.Body, fileName, null);
}
}
else if (HasDispositionName(firstContent, "key"))
{
// New style with key, then data
string key = null;
using (var sr = new StreamReader(firstSection.Body))
{
key = await sr.ReadToEndAsync();
}
var secondSection = await reader.ReadNextSectionAsync();
if (secondSection != null)
{
if (ContentDispositionHeaderValue.TryParse(secondSection.ContentDisposition,
out var secondContent) && HasFileContentDisposition(secondContent))
{
var fileName = HeaderUtilities.RemoveQuotes(secondContent.FileName).ToString();
using (secondSection.Body)
{
await callback(secondSection.Body, fileName, key);
}
}
secondSection = null;
}
await callback(firstSection.Body, fileName, null);
}
}
firstSection = null;
}
}
public static async Task GetSendFileAsync(this HttpRequest request, Func<Stream, string,
SendRequestModel, Task> callback)
{
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var firstSection = await reader.ReadNextSectionAsync();
if (firstSection != null)
{
if (ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out _))
else if (HasDispositionName(firstContent, "key"))
{
// New style with key, then data
string key = null;
using (var sr = new StreamReader(firstSection.Body))
{
key = await sr.ReadToEndAsync();
}
var secondSection = await reader.ReadNextSectionAsync();
if (secondSection != null)
{
@ -83,69 +49,102 @@ namespace Bit.Api.Utilities
var fileName = HeaderUtilities.RemoveQuotes(secondContent.FileName).ToString();
using (secondSection.Body)
{
var model = await JsonSerializer.DeserializeAsync<SendRequestModel>(firstSection.Body);
await callback(secondSection.Body, fileName, model);
await callback(secondSection.Body, fileName, key);
}
}
secondSection = null;
}
}
firstSection = null;
}
}
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, Task> callback)
{
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var dataSection = await reader.ReadNextSectionAsync();
if (dataSection != null)
{
if (ContentDispositionHeaderValue.TryParse(dataSection.ContentDisposition, out var dataContent)
&& HasFileContentDisposition(dataContent))
{
using (dataSection.Body)
{
await callback(dataSection.Body);
}
}
dataSection = null;
}
}
private static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
if (StringSegment.IsNullOrEmpty(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException($"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary.ToString();
}
private static bool HasFileContentDisposition(ContentDispositionHeaderValue content)
{
// Content-Disposition: form-data; name="data"; filename="Misc 002.jpg"
return content != null && content.DispositionType.Equals("form-data") &&
(!StringSegment.IsNullOrEmpty(content.FileName) || !StringSegment.IsNullOrEmpty(content.FileNameStar));
}
private static bool HasDispositionName(ContentDispositionHeaderValue content, string name)
{
// Content-Disposition: form-data; name="key";
return content != null && content.DispositionType.Equals("form-data") && content.Name == name;
firstSection = null;
}
}
public static async Task GetSendFileAsync(this HttpRequest request, Func<Stream, string,
SendRequestModel, Task> callback)
{
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var firstSection = await reader.ReadNextSectionAsync();
if (firstSection != null)
{
if (ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out _))
{
var secondSection = await reader.ReadNextSectionAsync();
if (secondSection != null)
{
if (ContentDispositionHeaderValue.TryParse(secondSection.ContentDisposition,
out var secondContent) && HasFileContentDisposition(secondContent))
{
var fileName = HeaderUtilities.RemoveQuotes(secondContent.FileName).ToString();
using (secondSection.Body)
{
var model = await JsonSerializer.DeserializeAsync<SendRequestModel>(firstSection.Body);
await callback(secondSection.Body, fileName, model);
}
}
secondSection = null;
}
}
firstSection = null;
}
}
public static async Task GetFileAsync(this HttpRequest request, Func<Stream, Task> callback)
{
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);
var dataSection = await reader.ReadNextSectionAsync();
if (dataSection != null)
{
if (ContentDispositionHeaderValue.TryParse(dataSection.ContentDisposition, out var dataContent)
&& HasFileContentDisposition(dataContent))
{
using (dataSection.Body)
{
await callback(dataSection.Body);
}
}
dataSection = null;
}
}
private static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
if (StringSegment.IsNullOrEmpty(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException($"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary.ToString();
}
private static bool HasFileContentDisposition(ContentDispositionHeaderValue content)
{
// Content-Disposition: form-data; name="data"; filename="Misc 002.jpg"
return content != null && content.DispositionType.Equals("form-data") &&
(!StringSegment.IsNullOrEmpty(content.FileName) || !StringSegment.IsNullOrEmpty(content.FileNameStar));
}
private static bool HasDispositionName(ContentDispositionHeaderValue content, string name)
{
// Content-Disposition: form-data; name="key";
return content != null && content.DispositionType.Equals("form-data") && content.Name == name;
}
}

View File

@ -1,15 +1,14 @@
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Bit.Api.Utilities
namespace Bit.Api.Utilities;
public class PublicApiControllersModelConvention : IControllerModelConvention
{
public class PublicApiControllersModelConvention : IControllerModelConvention
public void Apply(ControllerModel controller)
{
public void Apply(ControllerModel controller)
{
var controllerNamespace = controller.ControllerType.Namespace;
var publicApi = controllerNamespace.Contains(".Public.");
controller.Filters.Add(new ExceptionHandlerFilterAttribute(publicApi));
controller.Filters.Add(new ModelStateValidationFilterAttribute(publicApi));
}
var controllerNamespace = controller.ControllerType.Namespace;
var publicApi = controllerNamespace.Contains(".Public.");
controller.Filters.Add(new ExceptionHandlerFilterAttribute(publicApi));
controller.Filters.Add(new ModelStateValidationFilterAttribute(publicApi));
}
}

View File

@ -1,22 +1,20 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Bit.Api.Utilities
namespace Bit.Api.Utilities;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SecretsManagerAttribute : Attribute, IResourceFilter
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SecretsManagerAttribute : Attribute, IResourceFilter
public void OnResourceExecuting(ResourceExecutingContext context)
{
public void OnResourceExecuting(ResourceExecutingContext context)
var env = context.HttpContext.RequestServices.GetService<IHostEnvironment>();
if (!env.IsDevelopment())
{
var env = context.HttpContext.RequestServices.GetService<IHostEnvironment>();
if (!env.IsDevelopment())
{
context.Result = new NotFoundResult();
}
context.Result = new NotFoundResult();
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}

View File

@ -1,72 +1,71 @@
using Bit.Core.Settings;
using Microsoft.OpenApi.Models;
namespace Bit.Api.Utilities
namespace Bit.Api.Utilities;
public static class ServiceCollectionExtensions
{
public static class ServiceCollectionExtensions
public static void AddSwagger(this IServiceCollection services, GlobalSettings globalSettings)
{
public static void AddSwagger(this IServiceCollection services, GlobalSettings globalSettings)
services.AddSwaggerGen(config =>
{
services.AddSwaggerGen(config =>
config.SwaggerDoc("public", new OpenApiInfo
{
config.SwaggerDoc("public", new OpenApiInfo
Title = "Bitwarden Public API",
Version = "latest",
Contact = new OpenApiContact
{
Title = "Bitwarden Public API",
Version = "latest",
Contact = new OpenApiContact
{
Name = "Bitwarden Support",
Url = new Uri("https://bitwarden.com"),
Email = "support@bitwarden.com"
},
Description = "The Bitwarden public APIs.",
License = new OpenApiLicense
{
Name = "GNU Affero General Public License v3.0",
Url = new Uri("https://github.com/bitwarden/server/blob/master/LICENSE.txt")
}
});
config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" });
config.AddSecurityDefinition("OAuth2 Client Credentials", new OpenApiSecurityScheme
Name = "Bitwarden Support",
Url = new Uri("https://bitwarden.com"),
Email = "support@bitwarden.com"
},
Description = "The Bitwarden public APIs.",
License = new OpenApiLicense
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
TokenUrl = new Uri($"{globalSettings.BaseServiceUri.Identity}/connect/token"),
Scopes = new Dictionary<string, string>
{
{ "api.organization", "Organization APIs" },
},
}
},
});
config.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "OAuth2 Client Credentials"
},
},
new[] { "api.organization" }
}
});
config.DescribeAllParametersInCamelCase();
// config.UseReferencedDefinitionsForEnums();
var apiFilePath = Path.Combine(AppContext.BaseDirectory, "Api.xml");
config.IncludeXmlComments(apiFilePath, true);
var coreFilePath = Path.Combine(AppContext.BaseDirectory, "Core.xml");
config.IncludeXmlComments(coreFilePath);
Name = "GNU Affero General Public License v3.0",
Url = new Uri("https://github.com/bitwarden/server/blob/master/LICENSE.txt")
}
});
}
config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" });
config.AddSecurityDefinition("OAuth2 Client Credentials", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
TokenUrl = new Uri($"{globalSettings.BaseServiceUri.Identity}/connect/token"),
Scopes = new Dictionary<string, string>
{
{ "api.organization", "Organization APIs" },
},
}
},
});
config.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "OAuth2 Client Credentials"
},
},
new[] { "api.organization" }
}
});
config.DescribeAllParametersInCamelCase();
// config.UseReferencedDefinitionsForEnums();
var apiFilePath = Path.Combine(AppContext.BaseDirectory, "Api.xml");
config.IncludeXmlComments(apiFilePath, true);
var coreFilePath = Path.Combine(AppContext.BaseDirectory, "Core.xml");
config.IncludeXmlComments(coreFilePath);
});
}
}