Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/src/Serilog.Ui.Web…
Browse files Browse the repository at this point in the history
…/babel/traverse-7.23.2
  • Loading branch information
mo-esmp authored Jan 4, 2024
2 parents dd4e43a + 8f0b021 commit 8391508
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 2 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
A simple Serilog log viewer for the following sinks:

- Serilog.Sinks.**MSSqlServer** ([Nuget](https://github.com/serilog/serilog-sinks-mssqlserver))
- Serilog.Sinks.**MySql** ([Nuget](https://github.com/TeleSoftas/serilog-sinks-mariadb))
- Serilog.Sinks.**MySql** ([Nuget](https://github.com/TeleSoftas/serilog-sinks-mariadb)) and Serilog.Sinks.**MariaDB** [Nuget](https://github.com/TeleSoftas/serilog-sinks-mariadb)
- Serilog.Sinks.**Postgresql** ([Nuget](https://github.com/b00ted/serilog-sinks-postgresql))
- Serilog.Sinks.**MongoDB** ([Nuget](https://github.com/serilog/serilog-sinks-mongodb))
- Serilog.Sinks.**ElasticSearch** ([Nuget](https://github.com/serilog/serilog-sinks-elasticsearch))
Expand Down Expand Up @@ -97,3 +97,8 @@ See [LICENSE](https://github.com/serilog-contrib/serilog-ui/blob/master/LICENSE)
## Issues and Contribution

Everything is welcome! :trophy: See the [contribution guidelines](https://github.com/serilog-contrib/serilog-ui/blob/master/CONTRIBUTING.md) for details.

## Support
Thank you [JetBrains](https://www.jetbrains.com/community/opensource/#support) for supporting this project.

![JetBrains Logo (Main) logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)
88 changes: 88 additions & 0 deletions src/Serilog.Ui.Web/Authorization/BasicAuthenticationFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;

namespace Serilog.Ui.Web.Authorization;

public class BasicAuthenticationFilter : IUiAuthorizationFilter
{
private const string AuthenticationScheme = "Basic";
internal const string AuthenticationCookieName = "SerilogAuth";

public string UserName { get; set; }

public string Password { get; set; }

public bool Authorize(HttpContext httpContext)
{
var header = httpContext.Request.Headers["Authorization"];
var isAuthenticated = false;

if (header == "null" || string.IsNullOrEmpty(header))
{
var authCookie = httpContext.Request.Cookies[AuthenticationCookieName];
if (!string.IsNullOrWhiteSpace(authCookie))
{
var hashedCredentials = EncryptCredentials(UserName, Password);
isAuthenticated = authCookie.Equals(hashedCredentials, StringComparison.OrdinalIgnoreCase);
}
}
else
{
var authValues = AuthenticationHeaderValue.Parse(header);

if (IsBasicAuthentication(authValues))
{
var tokens = ExtractAuthenticationTokens(authValues);

if (CredentialsMatch(tokens))
{
isAuthenticated = true;
var hashedCredentials = EncryptCredentials(UserName, Password);
httpContext.Response.Cookies.Append(AuthenticationCookieName, hashedCredentials);
}
}
}

if (!isAuthenticated)
{
SetChallengeResponse(httpContext);
}

return isAuthenticated;
}

private string EncryptCredentials(string user, string pass)
{
using var sha256 = SHA256.Create();
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes($"{user}:{pass}"));
var hashedCredentials = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
return hashedCredentials;
}

private static bool IsBasicAuthentication(AuthenticationHeaderValue authValues)
{
return AuthenticationScheme.Equals(authValues.Scheme, StringComparison.InvariantCultureIgnoreCase);
}

private static (string, string) ExtractAuthenticationTokens(AuthenticationHeaderValue authValues)
{
var parameter = Encoding.UTF8.GetString(Convert.FromBase64String(authValues.Parameter));
var parts = parameter.Split(':');
return (parts[0], parts[1]);
}

private bool CredentialsMatch((string Username, string Password) tokens)
{
return tokens.Username == UserName && tokens.Password == Password;
}

private void SetChallengeResponse(HttpContext httpContext)
{
httpContext.Response.StatusCode = 401;
httpContext.Response.Headers.Append("WWW-Authenticate", "Basic realm=\"Serilog UI\"");
httpContext.Response.WriteAsync("Authentication is required.");
}
}
2 changes: 1 addition & 1 deletion src/Serilog.Ui.Web/Serilog.Ui.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Version>2.5.0</Version>
<Version>2.5.1</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Linq;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using System.Threading.Tasks;
using Xunit;

namespace Serilog.Ui.Web.Authorization.Tests;

public class BasicAuthenticationFilterTests
{
[Fact]
public async Task Authorize_WithValidCredentials_ShouldReturnTrue()
{
// Arrange
var filter = new BasicAuthenticationFilter
{
UserName = "User",
Password = "P@ss"
};

var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Authorization"] = "Basic VXNlcjpQQHNz"; // Base64 encoded "User:P@ss"

// Act
var result = filter.Authorize(httpContext);
var authCookie = httpContext.Response.GetTypedHeaders().SetCookie.FirstOrDefault(sc => sc.Name == BasicAuthenticationFilter.AuthenticationCookieName);

// Assert
result.Should().BeTrue();
authCookie.Should().NotBeNull();
}

[Fact]
public async Task Authorize_WithInvalidCredentials_ShouldReturnFalse()
{
// Arrange
var filter = new BasicAuthenticationFilter
{
UserName = "User",
Password = "P@ss"
};

var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Authorization"] = "Basic QWRtaW46dXNlcg=="; // Base64 encoded "Admin:user"

// Act
var result = filter.Authorize(httpContext);

// Assert
result.Should().BeFalse();
}

[Fact]
public async Task Authorize_WithMissingAuthorizationHeader_ShouldSetChallengeResponse()
{
// Arrange
var filter = new BasicAuthenticationFilter
{
UserName = "User",
Password = "P@ss"
};

var httpContext = new DefaultHttpContext();

// Act
var result = filter.Authorize(httpContext);

// Assert
result.Should().BeFalse();
httpContext.Response.StatusCode.Should().Be(401);
httpContext.Response.Headers[HeaderNames.WWWAuthenticate].Should().Contain("Basic realm=\"Serilog UI\"");
}
}

0 comments on commit 8391508

Please sign in to comment.