Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ services:
environment:
PORT: ${WEB_PORT:-3000}
SEED_DEMO: ${SEED_DEMO:-true}
DEMO_ADMIN_USERNAME: ${DEMO_ADMIN_USERNAME:-}
Jwt__Key: ${JWT_KEY}
Jwt__Issuer: ${JWT_ISSUER}
Jwt__Audience: ${JWT_AUDIENCE}
Expand Down
45 changes: 45 additions & 0 deletions web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Linq;
using System.Threading.Tasks;
using Atlas_Web.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;

namespace web.Tests.FunctionTests.Authorization;

public class DemoAuthHandlerTests
{
[Fact]
public async Task AuthenticateAsync_UsesConfiguredDemoAdminUsername()
{
var options = new DemoSchemeOptions { Username = "local-admin" };
var optionsMonitor = new Mock<IOptionsMonitor<DemoSchemeOptions>>();
optionsMonitor.Setup(x => x.CurrentValue).Returns(options);
optionsMonitor.Setup(x => x.Get(It.IsAny<string>())).Returns(options);

var handler = new DemoAuthHandler(
optionsMonitor.Object,
NullLoggerFactory.Instance,
UrlEncoder.Default,
new SystemClock()

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / function tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / function tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'
);

await handler.InitializeAsync(
new AuthenticationScheme("Demo", "Demo", typeof(DemoAuthHandler)),
new DefaultHttpContext()
);

var result = await handler.AuthenticateAsync();

Assert.True(result.Succeeded);
Assert.Equal(
"local-admin",
result.Principal?.Claims.Single(c => c.Type == ClaimTypes.Name).Value
);
}
}
72 changes: 72 additions & 0 deletions web.Tests/FunctionTests/Controllers/AuthApiController.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Atlas_Web.Controllers.Api;
using Atlas_Web.Models;
using Atlas_Web.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Xunit;

namespace web.Tests.FunctionTests.Controllers;

public class AuthApiControllerTests
{
[Fact]
public async Task Login_UsesConfiguredDemoAdminUsername_WhenDemoModeIsEnabled()
{
var options = new DbContextOptionsBuilder<Atlas_WebContext>()
.UseInMemoryDatabase(databaseName: "auth-api-demo-admin")
.Options;

await using var context = new Atlas_WebContext(options);
context.Users.Add(
new User
{
UserId = 99,
Username = "local-admin",
FullnameCalc = "Local Admin",
}
);
await context.SaveChangesAsync();

var config = new ConfigurationBuilder()
.AddInMemoryCollection(
new Dictionary<string, string>
{
["Demo"] = "True",
["DEMO_ADMIN_USERNAME"] = "local-admin",
["Cors:AllowedOrigins:0"] = "http://localhost:3000",
["Auth:DefaultCallbackPath"] = "/auth/callback",
["Jwt:Issuer"] = "atlas-test-issuer",
["Jwt:Audience"] = "atlas-test-audience",
}
)
.Build();

var signingKey = new SymmetricSecurityKey(
System.Text.Encoding.UTF8.GetBytes(
"test-jwt-secret-key-for-function-tests-32-chars-minimum"
)
);
var jwt = new JwtTokenService(signingKey, "atlas-test-issuer", "atlas-test-audience");
var controller = new AuthApiController(jwt, context, config);

var result = await controller.Login("http://localhost:3000/auth/callback");

var redirect = Assert.IsType<RedirectResult>(result);
var target = new System.Uri(redirect.Url!, System.UriKind.Absolute);
var token = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(target.Query)["token"]
.Single();
var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token);

Assert.Equal(
"local-admin",
jwtToken.Claims.Single(c => c.Type == System.Security.Claims.ClaimTypes.Name).Value
);
Assert.Equal("99", jwtToken.Claims.Single(c => c.Type == "UserId").Value);
}
}
14 changes: 10 additions & 4 deletions web/Authorization/DemoAuthHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
namespace Atlas_Web.Authentication
{
#pragma warning disable S2094
public class DemoSchemeOptions : AuthenticationSchemeOptions { }
public class DemoSchemeOptions : AuthenticationSchemeOptions
{
public string Username { get; set; } = "Default";
}

public class DemoAuthHandler : AuthenticationHandler<DemoSchemeOptions>
{
Expand All @@ -16,18 +19,21 @@
UrlEncoder encoder,
ISystemClock clock
)
: base(options, logger, encoder, clock) { }

Check warning on line 22 in web/Authorization/DemoAuthHandler.cs

View workflow job for this annotation

GitHub Actions / function tests

'AuthenticationHandler<DemoSchemeOptions>.AuthenticationHandler(IOptionsMonitor<DemoSchemeOptions>, ILoggerFactory, UrlEncoder, ISystemClock)' is obsolete: 'ISystemClock is obsolete, use TimeProvider on AuthenticationSchemeOptions instead.'

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var username = string.IsNullOrWhiteSpace(Options.Username)
? "Default"
: Options.Username.Trim();
var claims = new[]
{
new Claim(ClaimTypes.Name, "Default"),
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
};
var identity = new ClaimsIdentity(claims, "Default");
var identity = new ClaimsIdentity(claims, "Demo");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Default");
var ticket = new AuthenticationTicket(principal, "Demo");

var result = AuthenticateResult.Success(ticket);

Expand Down
11 changes: 9 additions & 2 deletions web/Controllers/Api/AuthApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public AuthApiController(JwtTokenService jwt, Atlas_WebContext context, IConfigu
public async Task<IActionResult> Login([FromQuery] string? returnUrl = null)
#pragma warning restore CS8632
{
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username == "Default");
var safeReturnUrlResult = GetSafeRedirectUrl(returnUrl);
if (safeReturnUrlResult is BadRequestObjectResult)
{
Expand All @@ -43,9 +42,17 @@ public async Task<IActionResult> Login([FromQuery] string? returnUrl = null)

if (_config["Demo"] == "True")
{
var demoUsername = _config["DEMO_ADMIN_USERNAME"];
var selectedDemoUsername = string.IsNullOrWhiteSpace(demoUsername)
? "Default"
: demoUsername.Trim();
var user = await _context.Users.FirstOrDefaultAsync(
x => x.Username == selectedDemoUsername
);

if (user == null)
{
return NotFound("Demo user not found.");
return NotFound($"Demo user '{selectedDemoUsername}' not found.");
}

var demoToken = _jwt.IssueToken(
Expand Down
2 changes: 1 addition & 1 deletion web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@
"Saml2",
(serviceProvider, saml2Configuration) =>
{
//saml2Configuration.SignAuthnRequest = true;

Check warning on line 222 in web/Program.cs

View workflow job for this annotation

GitHub Actions / install test

Remove this commented out code.

Check warning on line 222 in web/Program.cs

View workflow job for this annotation

GitHub Actions / function tests

Remove this commented out code.

Check warning on line 222 in web/Program.cs

View workflow job for this annotation

GitHub Actions / integration tests

Remove this commented out code.

Check warning on line 222 in web/Program.cs

View workflow job for this annotation

GitHub Actions / Lighthouse

Remove this commented out code.
saml2Configuration.SigningCertificate = CertificateUtil.Load(
builder.Environment.MapToPhysicalFilePath(
builder.Configuration["Saml2:SigningCertificateFile"]
Expand Down Expand Up @@ -355,7 +355,7 @@
app.Use(
async (context, next) =>
{
context.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'self' *;");
context.Response.Headers["Content-Security-Policy"] = "frame-ancestors 'self';";

Check warning on line 358 in web/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The header 'Content-Security-Policy' can be accessed using the ContentSecurityPolicy property

See more on https://sonarcloud.io/project/issues?id=atlas-bi_atlas-bi-library&issues=AZ5Bo8uQC9VF5YTe7FVP&open=AZ5Bo8uQC9VF5YTe7FVP&pullRequest=706
await next();
}
);
Expand All @@ -374,12 +374,12 @@
{
context.Database.Migrate();
}
catch (SqlException ex) when (ex.Number == 4060)

Check warning on line 377 in web/Program.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SqlException' is obsolete: 'Use the Microsoft.Data.SqlClient package instead.'
{
var connStr = app.Configuration.GetConnectionString("AtlasDatabase");
var csb = new SqlConnectionStringBuilder(connStr) { InitialCatalog = "master" };

Check warning on line 380 in web/Program.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SqlConnectionStringBuilder' is obsolete: 'Use the Microsoft.Data.SqlClient package instead.'

using (var conn = new SqlConnection(csb.ConnectionString))

Check warning on line 382 in web/Program.cs

View workflow job for this annotation

GitHub Actions / function tests

'SqlConnection' is obsolete: 'Use the Microsoft.Data.SqlClient package instead.'

Check warning on line 382 in web/Program.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SqlConnection' is obsolete: 'Use the Microsoft.Data.SqlClient package instead.'
{
conn.Open();
using var cmd = conn.CreateCommand();
Expand Down Expand Up @@ -422,7 +422,7 @@
TimeSpan.FromSeconds(5)
);

using var connection = new SqlConnection(

Check warning on line 425 in web/Program.cs

View workflow job for this annotation

GitHub Actions / function tests

'SqlConnection' is obsolete: 'Use the Microsoft.Data.SqlClient package instead.'

Check warning on line 425 in web/Program.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SqlConnection' is obsolete: 'Use the Microsoft.Data.SqlClient package instead.'
app.Configuration.GetConnectionString("AtlasDatabase")
);
connection.Open();
Expand Down
5 changes: 4 additions & 1 deletion web/ProgramConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ private static void ConfigureDemoAuthentication(
#pragma warning disable S1116
builder
.Services.AddAuthentication(options => options.DefaultScheme = "Demo")
.AddScheme<DemoSchemeOptions, DemoAuthHandler>("Demo", options => { })
.AddScheme<DemoSchemeOptions, DemoAuthHandler>("Demo", options =>
{
options.Username = builder.Configuration["DEMO_ADMIN_USERNAME"] ?? "Default";
})
.AddJwtBearer("Bearer", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
Expand Down
Loading