Files
Torsten Brendgen dfd9c16f61 Add migration to update ApiClients with centralized authorization scopes
This migration updates the ScopesJson for the ApiClient with Id 91000000-0000-0000-0000-000000000001 to include additional authorization scopes for improved API access control. The Down method reverts the changes if necessary.
2026-07-09 23:49:01 +02:00

171 lines
5.7 KiB
C#

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Interfaces;
using Microsoft.SelfService.Portal.Core.API.Repository;
using Microsoft.SelfService.Portal.Core.API.Services;
using Microsoft.Extensions.FileProviders;
using System.IdentityModel.Tokens.Jwt;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
builder.Services.AddScoped<IDomainInterface,DomainRepository>();
builder.Services.AddScoped<IEnvironmentInterface, EnvironmentRepository>();
builder.Services.AddScoped<ITargetInterface, TargetRepository>();
builder.Services.AddScoped<IServiceInterface, ServiceRepository>();
builder.Services.AddScoped<IDeploymentBatchInterface, DeploymentBatchRepository>();
builder.Services.AddScoped<IDeploymentInterface, DeploymentRepository>();
builder.Services.AddScoped<ITemplateInterface, TemplateRepository>();
builder.Services.AddScoped<ITemplateCategoryInterface, TemplateCategoryRepository>();
builder.Services.AddScoped<IQueueJobService, QueueJobService>();
builder.Services.AddScoped<ApiTokenService>();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAutoMapper(_ => { }, AppDomain.CurrentDomain.GetAssemblies());
builder.Services.AddDbContext<DataContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Context") ?? throw new InvalidOperationException("Connection string 'Context' not found.")));
var tokenService = new ApiTokenService(builder.Configuration);
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Smart";
options.DefaultChallengeScheme = "Smart";
})
.AddPolicyScheme("Smart", "Negotiate or Bearer", options =>
{
options.ForwardDefaultSelector = context =>
{
var authorization = context.Request.Headers.Authorization.ToString();
return authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
? JwtBearerDefaults.AuthenticationScheme
: NegotiateDefaults.AuthenticationScheme;
};
})
.AddNegotiate()
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = tokenService.Issuer,
ValidateAudience = true,
ValidAudience = tokenService.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = tokenService.GetSigningKey(),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(2)
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var jti = context.Principal?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
if (string.IsNullOrWhiteSpace(jti))
{
context.Fail("Token does not contain a token id.");
return Task.CompletedTask;
}
var dataContext = context.HttpContext.RequestServices.GetRequiredService<DataContext>();
var token = dataContext.ApiTokens.FirstOrDefault(existing => existing.Jti == jti);
if (token == null)
{
context.Fail("Token is not registered.");
return Task.CompletedTask;
}
if (token.RevokedAt.HasValue || token.ExpiresAt <= DateTime.UtcNow)
{
context.Fail("Token is revoked or expired.");
return Task.CompletedTask;
}
token.LastUsedAt = DateTime.UtcNow;
token.Modified = DateTime.UtcNow;
token.ModifiedBy = "BearerToken";
dataContext.SaveChanges();
return Task.CompletedTask;
}
};
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddSelfServicePortalAuthorization();
var app = builder.Build();
var frontendDistPath = Path.GetFullPath(Path.Combine(
app.Environment.ContentRootPath,
"..",
"Microsoft.SelfService.Portal.Web",
"dist"));
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
app.UseHttpsRedirection();
if (Directory.Exists(frontendDistPath))
{
var frontendDistProvider = new PhysicalFileProvider(frontendDistPath);
app.UseDefaultFiles(new DefaultFilesOptions
{
FileProvider = frontendDistProvider
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = frontendDistProvider
});
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
if (Directory.Exists(frontendDistPath))
{
app.MapFallback(async context =>
{
if (context.Request.Path.StartsWithSegments("/api"))
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
await context.Response.WriteAsJsonAsync(new { message = "API endpoint not found." });
return;
}
context.Response.ContentType = "text/html";
await context.Response.SendFileAsync(Path.Combine(frontendDistPath, "index.html"));
}).AllowAnonymous();
}
app.Run();