< Summary

Information
Class: BearerSecuritySchemeTransformer
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Program.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 48
Coverable lines: 48
Total lines: 290
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 2
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
TransformAsync()0%2040%

File(s)

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Program.cs

#LineLine coverage
 1using LGDXRobotCloud.API.Authentication;
 2using LGDXRobotCloud.API.Authorisation;
 3using LGDXRobotCloud.API.Configurations;
 4using LGDXRobotCloud.API.Middleware;
 5using LGDXRobotCloud.API.Services;
 6using LGDXRobotCloud.API.Services.Administration;
 7using LGDXRobotCloud.API.Services.Automation;
 8using LGDXRobotCloud.API.Services.Common;
 9using LGDXRobotCloud.API.Services.Identity;
 10using LGDXRobotCloud.API.Services.Navigation;
 11using LGDXRobotCloud.Data.DbContexts;
 12using LGDXRobotCloud.Data.Entities;
 13using LGDXRobotCloud.Utilities.Constants;
 14using MassTransit;
 15using Microsoft.AspNetCore.Authentication;
 16using Microsoft.AspNetCore.Authentication.Certificate;
 17using Microsoft.AspNetCore.Authentication.JwtBearer;
 18using Microsoft.AspNetCore.Authorization;
 19using Microsoft.AspNetCore.Hosting.Server;
 20using Microsoft.AspNetCore.Hosting.Server.Features;
 21using Microsoft.AspNetCore.Identity;
 22using Microsoft.AspNetCore.OpenApi;
 23using Microsoft.AspNetCore.Server.Kestrel.Https;
 24using Microsoft.EntityFrameworkCore;
 25using Microsoft.IdentityModel.Tokens;
 26using Microsoft.OpenApi.Models;
 27using Scalar.AspNetCore;
 28using System.Security.Claims;
 29using System.Security.Cryptography.X509Certificates;
 30using System.Text;
 31
 32var builder = WebApplication.CreateBuilder(args);
 33builder.WebHost.ConfigureKestrel(cfg =>
 34{
 35  cfg.ConfigureHttpsDefaults(ctx => ctx.ClientCertificateMode = ClientCertificateMode.AllowCertificate);
 36  cfg.AddServerHeader = false;
 37});
 38
 39/*
 40 * Configuration
 41 */
 42builder.Services.Configure<LgdxRobotCloudConfiguration>(
 43  builder.Configuration.GetSection("LGDXRobotCloud")
 44);
 45builder.Services.Configure<LgdxRobotCloudSecretConfiguration>(
 46  builder.Configuration.GetSection("LGDXRobotCloudSecret")
 47);
 48
 49/*
 50 * Infrastructure
 51 */
 52builder.Services.AddMassTransit(cfg =>
 53{
 54  cfg.UsingRabbitMq((context, cfg) =>
 55  {
 56    cfg.Host(builder.Configuration["RabbitMq:Host"], builder.Configuration["RabbitMq:VirtualHost"], h =>
 57    {
 58      h.Username(builder.Configuration["RabbitMq:Username"] ?? string.Empty);
 59      h.Password(builder.Configuration["RabbitMq:Password"] ?? string.Empty);
 60    });
 61  });
 62});
 63builder.Services.AddMemoryCache();
 64builder.Services.AddControllers();
 65builder.Services.AddOpenApi(options =>
 66{
 67  options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
 68});
 69builder.Services.AddGrpc(cfg => cfg.EnableDetailedErrors = true);
 70builder.Services.AddDbContextPool<LgdxContext>(cfg =>
 71  cfg.UseNpgsql(builder.Configuration["PGSQLConnectionString"])
 72  .LogTo(Console.WriteLine, LogLevel.Information)
 73  .EnableSensitiveDataLogging()
 74  .EnableDetailedErrors()
 75);
 76builder.Services.AddHttpContextAccessor();
 77
 78/*
 79 * Authentication
 80 */
 81builder.Services.AddIdentity<LgdxUser, LgdxRole>()
 82  .AddEntityFrameworkStores<LgdxContext>()
 83  .AddTokenProvider<AuthenticatorTokenProvider<LgdxUser>>(TokenOptions.DefaultAuthenticatorProvider)
 84  .AddTokenProvider<DataProtectorTokenProvider<LgdxUser>>(TokenOptions.DefaultProvider);
 85builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.CertificationScheme)
 86  .AddCertificate(LgdxRobotCloudAuthenticationSchemes.CertificationScheme, cfg =>
 87    {
 88      cfg.AllowedCertificateTypes = CertificateTypes.All;
 89      cfg.RevocationMode = X509RevocationMode.NoCheck;
 90      cfg.Events = new CertificateAuthenticationEvents()
 91      {
 92        OnCertificateValidated = ctx =>
 93        {
 94          if (ctx.ClientCertificate.Thumbprint != builder.Configuration["LGDXRobotCloud:InternalCertificateThumbprint"])
 95          {
 96            ctx.Fail("Invalid certificate.");
 97            return Task.CompletedTask;
 98          }
 99          ctx.Success();
 100          return Task.CompletedTask;
 101        }
 102      };
 103    }
 104  );
 105builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.ApiKeyScheme)
 106  .AddScheme<ApiKeyAuthenticationSchemeOptions, ApiKeyAuthenticationSchemeHandler>(
 107    LgdxRobotCloudAuthenticationSchemes.ApiKeyScheme,
 108    options => {}
 109  );
 110builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
 111  .AddJwtBearer(cfg =>
 112  {
 113    cfg.TokenValidationParameters = new TokenValidationParameters
 114    {
 115      ValidateIssuer = true,
 116      ValidateAudience = true,
 117      ValidateLifetime = true,
 118      ValidateIssuerSigningKey = true,
 119      ValidIssuer = builder.Configuration["LGDXRobotCloudSecret:LgdxUserJwtIssuer"],
 120      ValidAudience = builder.Configuration["LGDXRobotCloudSecret:LgdxUserJwtIssuer"],
 121      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["LGDXRobotCloudSecret:Lgd
 122      ClockSkew = TimeSpan.Zero
 123    };
 124  });
 125builder.Services.AddTransient<ValidateRobotClientsCertificate>();
 126builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme)
 127  .AddCertificate(LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme, cfg =>
 128  {
 129    cfg.AllowedCertificateTypes = CertificateTypes.All;
 130    cfg.RevocationMode = X509RevocationMode.NoCheck;
 131    cfg.Events = new CertificateAuthenticationEvents()
 132    {
 133      OnCertificateValidated = async ctx =>
 134      {
 135        string subject = ctx.ClientCertificate.Subject;
 136        string guid = subject.Substring(subject.IndexOf("OID.0.9.2342.19200300.100.1.1=") + 30, 36);
 137        if (guid == string.Empty)
 138        {
 139          ctx.Fail("Robot ID not found.");
 140          return;
 141        }
 142        var validatior = ctx.HttpContext.RequestServices.GetService<ValidateRobotClientsCertificate>();
 143        if (!await validatior!.Validate(ctx.ClientCertificate, Guid.Parse(guid)))
 144        {
 145          ctx.Fail("Invalid certificate / Robot not found.");
 146          return;
 147        }
 148        var claims = new[] {
 149          new Claim(ClaimTypes.NameIdentifier, guid)
 150        };
 151        ctx.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, ctx.Scheme.Name));
 152        ctx.Success();
 153      }
 154    };
 155  });
 156builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme)
 157  .AddJwtBearer(LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme, cfg =>
 158  {
 159    cfg.TokenValidationParameters = new TokenValidationParameters
 160    {
 161      ValidateIssuer = true,
 162      ValidateAudience = true,
 163      ValidateLifetime = true,
 164      ValidateIssuerSigningKey = true,
 165      ValidIssuer = builder.Configuration["LGDXRobotCloudSecret:RobotClientsJwtIssuer"],
 166      ValidAudience = builder.Configuration["LGDXRobotCloudSecret:RobotClientsJwtIssuer"],
 167      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["LGDXRobotCloudSecret:Rob
 168      ClockSkew = TimeSpan.Zero
 169    };
 170  });
 171builder.Services.AddScoped<IAuthorizationHandler, ValidateLgdxUserAccessHandler>();
 172builder.Services.AddAuthorizationBuilder()
 173  .AddPolicy("ValidateLgdxUserAccess", policyBuilder =>
 174  {
 175    policyBuilder.RequireAuthenticatedUser();
 176    policyBuilder.AddRequirements(new ValidateLgdxUserAccessRequirement());
 177  });
 178builder.Services.AddScoped<IAuthorizationHandler, RobotClientShouldOnlineHandler>();
 179builder.Services.AddAuthorizationBuilder()
 180  .AddPolicy("RobotClientShouldOnline", policyBuilder =>
 181  {
 182    policyBuilder.RequireAuthenticatedUser();
 183    policyBuilder.AddRequirements(new RobotClientShouldOnlineRequirement());
 184  });
 185
 186/*
 187 * LGDX Depency Injection
 188 */
 189// Administrator
 190builder.Services.AddScoped<IApiKeyService, ApiKeyService>();
 191builder.Services.AddScoped<IRobotCertificateService, RobotCertificateService>();
 192builder.Services.AddScoped<IRoleService, RoleService>();
 193builder.Services.AddScoped<IUserService, UserService>();
 194
 195// Automation
 196builder.Services.AddScoped<IAutoTaskService, AutoTaskService>();
 197builder.Services.AddScoped<IFlowService, FlowService>();
 198builder.Services.AddScoped<IProgressService, ProgressService>();
 199
 200// Identity
 201builder.Services.AddScoped<IAuthService, AuthService>();
 202builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
 203
 204// Automation
 205builder.Services.AddScoped<IRealmService, RealmService>();
 206builder.Services.AddScoped<IWaypointService, WaypointService>();
 207builder.Services.AddScoped<IRobotService, RobotService>();
 208
 209// Custom Services
 210builder.Services.AddScoped<ITriggerRetryService, TriggerRetryService>();
 211builder.Services.AddScoped<ITriggerService, TriggerService>();
 212builder.Services.AddScoped<IEmailService, EmailService>();
 213builder.Services.AddSingleton<IEventService, EventService>();
 214builder.Services.AddScoped<IAutoTaskSchedulerService, AutoTaskSchedulerService>();
 215builder.Services.AddScoped<IOnlineRobotsService, OnlineRobotsService>();
 216builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
 217
 218var app = builder.Build();
 219
 220// Configure the HTTP request pipeline.
 221if (app.Environment.IsDevelopment())
 222{
 223  app.MapOpenApi();
 224  app.MapScalarApiReference();
 225}
 226
 227app.UseHttpsRedirection();
 228
 229app.UseAuthentication();
 230app.UseAuthorization();
 231
 232app.MapControllerRoute(
 233  name: "Area",
 234  pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
 235app.MapGrpcService<RobotClientsService>();
 236app.UseLgdxExpectionHandling();
 237
 238app.Run();
 239
 0240internal sealed class BearerSecuritySchemeTransformer(
 0241  IAuthenticationSchemeProvider authenticationSchemeProvider,
 0242  IServer server
 0243) : IOpenApiDocumentTransformer
 244{
 245  public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToke
 0246  {
 0247    var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
 0248    if (authenticationSchemes.Any(authScheme => authScheme.Name == JwtBearerDefaults.AuthenticationScheme))
 0249    {
 0250      var requirements = new Dictionary<string, OpenApiSecurityScheme>
 0251      {
 0252        ["Bearer"] = new OpenApiSecurityScheme
 0253        {
 0254          Type = SecuritySchemeType.Http,
 0255          Scheme = "bearer", // "bearer" refers to the header name here
 0256          In = ParameterLocation.Header,
 0257          BearerFormat = "Json Web Token"
 0258        }
 0259      };
 0260      document.Components ??= new OpenApiComponents();
 0261      document.Components.SecuritySchemes = requirements;
 0262    }
 0263    document.Info = new()
 0264    {
 0265      Title = "LGDXRobot Cloud API",
 0266      Version = "v1",
 0267      Description = "Core API for the LGDXRobot Cloud.",
 0268      Contact = new OpenApiContact
 0269      {
 0270        Name = "LGDXRobot",
 0271        Url = new Uri("https://lgdxrobot.bristolgram.uk"),
 0272      },
 0273      License = new OpenApiLicense
 0274      {
 0275        Name = "The MIT License",
 0276        Url = new Uri("https://opensource.org/license/MIT")
 0277      }
 0278    };
 0279    var address = server.Features.Get<IServerAddressesFeature>()!.Addresses.LastOrDefault(); // HTTPS port must higher
 0280    address = address!.Replace("[::]", "localhost");
 0281    document.Servers =
 0282    [
 0283      new()
 0284      {
 0285        Url = address,
 0286        Description = "Default Server"
 0287      }
 0288    ];
 0289  }
 290}