summaryrefslogtreecommitdiffstats
path: root/src/server/Utilities
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-01-22 22:43:38 +0100
committerivarlovlie <git@ivarlovlie.no>2022-01-22 22:43:38 +0100
commit88110f536f9c3843ecf5016122e101f8a424af77 (patch)
treee8be4e77ccfb5ad37f49f89adad59ff12b4c85ea /src/server/Utilities
downloadbookmark-thing-88110f536f9c3843ecf5016122e101f8a424af77.tar.xz
bookmark-thing-88110f536f9c3843ecf5016122e101f8a424af77.zip
Initial commit
Diffstat (limited to 'src/server/Utilities')
-rw-r--r--src/server/Utilities/BasicAuthenticationHandler.cs49
-rw-r--r--src/server/Utilities/ConfigurationExtensions.cs19
-rw-r--r--src/server/Utilities/SwaggerDefaultValues.cs55
-rw-r--r--src/server/Utilities/SwaggerGenOptionsExtensions.cs37
4 files changed, 160 insertions, 0 deletions
diff --git a/src/server/Utilities/BasicAuthenticationHandler.cs b/src/server/Utilities/BasicAuthenticationHandler.cs
new file mode 100644
index 0000000..7961b82
--- /dev/null
+++ b/src/server/Utilities/BasicAuthenticationHandler.cs
@@ -0,0 +1,49 @@
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.Encodings.Web;
+using Microsoft.Extensions.Options;
+
+namespace IOL.BookmarkThing.Server.Utilities;
+
+public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
+{
+ private readonly AppDbContext _context;
+
+ public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, AppDbContext context) :
+ base(options, logger, encoder, clock) {
+ _context = context;
+ }
+
+ protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
+ var endpoint = Context.GetEndpoint();
+ if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
+ return Task.FromResult(AuthenticateResult.NoResult());
+
+ if (!Request.Headers.ContainsKey("Authorization"))
+ return Task.FromResult(AuthenticateResult.Fail("Missing Authorization Header"));
+
+ try {
+ var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
+ if (authHeader.Parameter == null) return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
+ var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
+ var token_is_guid = Guid.TryParse(Encoding.UTF8.GetString(credentialBytes), out var token_id);
+ if (token_is_guid) {
+ var token = _context.AccessTokens.Include(c => c.User).SingleOrDefault(c => c.Id == token_id);
+ if (token == default) {
+ return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
+ }
+
+ var claims = token.User.DefaultClaims();
+ var identity = new ClaimsIdentity(claims, Scheme.Name);
+ var principal = new ClaimsPrincipal(identity);
+ var ticket = new AuthenticationTicket(principal, Scheme.Name);
+
+ return Task.FromResult(AuthenticateResult.Success(ticket));
+ }
+
+ return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
+ } catch {
+ return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
+ }
+ }
+}
diff --git a/src/server/Utilities/ConfigurationExtensions.cs b/src/server/Utilities/ConfigurationExtensions.cs
new file mode 100644
index 0000000..d42b117
--- /dev/null
+++ b/src/server/Utilities/ConfigurationExtensions.cs
@@ -0,0 +1,19 @@
+namespace IOL.BookmarkThing.Server.Utilities;
+
+public static class ConfigurationExtensions
+{
+ /// <summary>
+ /// Get the contents of AppData/version.txt.
+ /// </summary>
+ /// <param name="configuration"></param>
+ /// <returns></returns>
+ public static string GetVersion(this IConfiguration configuration) {
+ var versionFilePath = Path.Combine(AppPaths.AppData.HostPath, "version.txt");
+ if (File.Exists(versionFilePath)) {
+ var versionText = File.ReadAllText(versionFilePath);
+ return versionText + "-" + configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT");
+ }
+
+ return "unknown-" + configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT");
+ }
+}
diff --git a/src/server/Utilities/SwaggerDefaultValues.cs b/src/server/Utilities/SwaggerDefaultValues.cs
new file mode 100644
index 0000000..24855ae
--- /dev/null
+++ b/src/server/Utilities/SwaggerDefaultValues.cs
@@ -0,0 +1,55 @@
+namespace IOL.BookmarkThing.Server.Utilities;
+
+/// <summary>
+/// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter.
+/// </summary>
+/// <remarks>This <see cref="IOperationFilter"/> is only required due to bugs in the <see cref="SwaggerGenerator"/>.
+/// Once they are fixed and published, this class can be removed.</remarks>
+public class SwaggerDefaultValues : IOperationFilter
+{
+ /// <summary>
+ /// Applies the filter to the specified operation using the given context.
+ /// </summary>
+ /// <param name="operation">The operation to apply the filter to.</param>
+ /// <param name="context">The current operation filter context.</param>
+ public void Apply(OpenApiOperation operation, OperationFilterContext context) {
+ var apiDescription = context.ApiDescription;
+
+ operation.Deprecated |= apiDescription.IsDeprecated();
+
+ // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077
+ foreach (var responseType in context.ApiDescription.SupportedResponseTypes) {
+ // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387
+ var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
+ var response = operation.Responses[responseKey];
+
+ foreach (var contentType in response.Content.Keys) {
+ if (!responseType.ApiResponseFormats.Any(x => x.MediaType == contentType)) {
+ response.Content.Remove(contentType);
+ }
+ }
+ }
+
+ if (operation.Parameters == null) {
+ return;
+ }
+
+ // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412
+ // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413
+ foreach (var parameter in operation.Parameters) {
+ var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
+
+ if (parameter.Description == null) {
+ parameter.Description = description.ModelMetadata?.Description;
+ }
+
+ if (parameter.Schema.Default == null && description.DefaultValue != null) {
+ // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330
+ var json = JsonSerializer.Serialize(description.DefaultValue, description.ModelMetadata.ModelType);
+ parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
+ }
+
+ parameter.Required |= description.IsRequired;
+ }
+ }
+}
diff --git a/src/server/Utilities/SwaggerGenOptionsExtensions.cs b/src/server/Utilities/SwaggerGenOptionsExtensions.cs
new file mode 100644
index 0000000..0203f77
--- /dev/null
+++ b/src/server/Utilities/SwaggerGenOptionsExtensions.cs
@@ -0,0 +1,37 @@
+namespace IOL.BookmarkThing.Server.Utilities;
+
+public static class SwaggerGenOptionsExtensions
+{
+ /// <summary>
+ /// Updates Swagger document to support ApiEndpoints.<br/><br/>
+ /// For controllers inherited from <see cref="BaseRoute"/>:<br/>
+ /// - Replaces action Tag with <c>[namespace]</c><br/>
+ /// </summary>
+ public static void UseApiEndpoints(this SwaggerGenOptions options) {
+ options.TagActionsBy(EndpointNamespaceOrDefault);
+ }
+
+ private static IEnumerable<Type> GetBaseTypesAndThis(this Type type) {
+ var current = type;
+ while (current != null) {
+ yield return current;
+ current = current.BaseType;
+ }
+ }
+
+ private static IList<string> EndpointNamespaceOrDefault(ApiDescription api) {
+ if (api.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) {
+ throw new InvalidOperationException($"Unable to determine tag for endpoint: {api.ActionDescriptor.DisplayName}");
+ }
+
+ if (actionDescriptor.ControllerTypeInfo.GetBaseTypesAndThis().Any(t => t == typeof(BaseV1Route))) {
+ return new[] {
+ actionDescriptor.ControllerTypeInfo.Namespace?.Split('.').Last(),
+ };
+ }
+
+ return new[] {
+ actionDescriptor.ControllerName,
+ };
+ }
+}