< Summary

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

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
<Main>$(...)0%342180%

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
 032var builder = WebApplication.CreateBuilder(args);
 033builder.WebHost.ConfigureKestrel(cfg =>
 034{
 035  cfg.ConfigureHttpsDefaults(ctx => ctx.ClientCertificateMode = ClientCertificateMode.AllowCertificate);
 036  cfg.AddServerHeader = false;
 037});
 38
 39/*
 40 * Configuration
 41 */
 042builder.Services.Configure<LgdxRobotCloudConfiguration>(
 043  builder.Configuration.GetSection("LGDXRobotCloud")
 044);
 045builder.Services.Configure<LgdxRobotCloudSecretConfiguration>(
 046  builder.Configuration.GetSection("LGDXRobotCloudSecret")
 047);
 48
 49/*
 50 * Infrastructure
 51 */
 052builder.Services.AddMassTransit(cfg =>
 053{
 054  cfg.UsingRabbitMq((context, cfg) =>
 055  {
 056    cfg.Host(builder.Configuration["RabbitMq:Host"], builder.Configuration["RabbitMq:VirtualHost"], h =>
 057    {
 058      h.Username(builder.Configuration["RabbitMq:Username"] ?? string.Empty);
 059      h.Password(builder.Configuration["RabbitMq:Password"] ?? string.Empty);
 060    });
 061  });
 062});
 063builder.Services.AddMemoryCache();
 064builder.Services.AddControllers();
 065builder.Services.AddOpenApi(options =>
 066{
 067  options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
 068});
 069builder.Services.AddGrpc(cfg => cfg.EnableDetailedErrors = true);
 070builder.Services.AddDbContextPool<LgdxContext>(cfg =>
 071  cfg.UseNpgsql(builder.Configuration["PGSQLConnectionString"])
 072  .LogTo(Console.WriteLine, LogLevel.Information)
 073  .EnableSensitiveDataLogging()
 074  .EnableDetailedErrors()
 075);
 076builder.Services.AddHttpContextAccessor();
 77
 78/*
 79 * Authentication
 80 */
 081builder.Services.AddIdentity<LgdxUser, LgdxRole>()
 082  .AddEntityFrameworkStores<LgdxContext>()
 083  .AddTokenProvider<AuthenticatorTokenProvider<LgdxUser>>(TokenOptions.DefaultAuthenticatorProvider)
 084  .AddTokenProvider<DataProtectorTokenProvider<LgdxUser>>(TokenOptions.DefaultProvider);
 085builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.CertificationScheme)
 086  .AddCertificate(LgdxRobotCloudAuthenticationSchemes.CertificationScheme, cfg =>
 087    {
 088      cfg.AllowedCertificateTypes = CertificateTypes.All;
 089      cfg.RevocationMode = X509RevocationMode.NoCheck;
 090      cfg.Events = new CertificateAuthenticationEvents()
 091      {
 092        OnCertificateValidated = ctx =>
 093        {
 094          if (ctx.ClientCertificate.Thumbprint != builder.Configuration["LGDXRobotCloud:InternalCertificateThumbprint"])
 095          {
 096            ctx.Fail("Invalid certificate.");
 097            return Task.CompletedTask;
 098          }
 099          ctx.Success();
 0100          return Task.CompletedTask;
 0101        }
 0102      };
 0103    }
 0104  );
 0105builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.ApiKeyScheme)
 0106  .AddScheme<ApiKeyAuthenticationSchemeOptions, ApiKeyAuthenticationSchemeHandler>(
 0107    LgdxRobotCloudAuthenticationSchemes.ApiKeyScheme,
 0108    options => {}
 0109  );
 0110builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
 0111  .AddJwtBearer(cfg =>
 0112  {
 0113    cfg.TokenValidationParameters = new TokenValidationParameters
 0114    {
 0115      ValidateIssuer = true,
 0116      ValidateAudience = true,
 0117      ValidateLifetime = true,
 0118      ValidateIssuerSigningKey = true,
 0119      ValidIssuer = builder.Configuration["LGDXRobotCloudSecret:LgdxUserJwtIssuer"],
 0120      ValidAudience = builder.Configuration["LGDXRobotCloudSecret:LgdxUserJwtIssuer"],
 0121      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["LGDXRobotCloudSecret:Lgd
 0122      ClockSkew = TimeSpan.Zero
 0123    };
 0124  });
 0125builder.Services.AddTransient<ValidateRobotClientsCertificate>();
 0126builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme)
 0127  .AddCertificate(LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme, cfg =>
 0128  {
 0129    cfg.AllowedCertificateTypes = CertificateTypes.All;
 0130    cfg.RevocationMode = X509RevocationMode.NoCheck;
 0131    cfg.Events = new CertificateAuthenticationEvents()
 0132    {
 0133      OnCertificateValidated = async ctx =>
 0134      {
 0135        string subject = ctx.ClientCertificate.Subject;
 0136        string guid = subject.Substring(subject.IndexOf("OID.0.9.2342.19200300.100.1.1=") + 30, 36);
 0137        if (guid == string.Empty)
 0138        {
 0139          ctx.Fail("Robot ID not found.");
 0140          return;
 0141        }
 0142        var validatior = ctx.HttpContext.RequestServices.GetService<ValidateRobotClientsCertificate>();
 0143        if (!await validatior!.Validate(ctx.ClientCertificate, Guid.Parse(guid)))
 0144        {
 0145          ctx.Fail("Invalid certificate / Robot not found.");
 0146          return;
 0147        }
 0148        var claims = new[] {
 0149          new Claim(ClaimTypes.NameIdentifier, guid)
 0150        };
 0151        ctx.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, ctx.Scheme.Name));
 0152        ctx.Success();
 0153      }
 0154    };
 0155  });
 0156builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme)
 0157  .AddJwtBearer(LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme, cfg =>
 0158  {
 0159    cfg.TokenValidationParameters = new TokenValidationParameters
 0160    {
 0161      ValidateIssuer = true,
 0162      ValidateAudience = true,
 0163      ValidateLifetime = true,
 0164      ValidateIssuerSigningKey = true,
 0165      ValidIssuer = builder.Configuration["LGDXRobotCloudSecret:RobotClientsJwtIssuer"],
 0166      ValidAudience = builder.Configuration["LGDXRobotCloudSecret:RobotClientsJwtIssuer"],
 0167      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["LGDXRobotCloudSecret:Rob
 0168      ClockSkew = TimeSpan.Zero
 0169    };
 0170  });
 0171builder.Services.AddScoped<IAuthorizationHandler, ValidateLgdxUserAccessHandler>();
 0172builder.Services.AddAuthorizationBuilder()
 0173  .AddPolicy("ValidateLgdxUserAccess", policyBuilder =>
 0174  {
 0175    policyBuilder.RequireAuthenticatedUser();
 0176    policyBuilder.AddRequirements(new ValidateLgdxUserAccessRequirement());
 0177  });
 0178builder.Services.AddScoped<IAuthorizationHandler, RobotClientShouldOnlineHandler>();
 0179builder.Services.AddAuthorizationBuilder()
 0180  .AddPolicy("RobotClientShouldOnline", policyBuilder =>
 0181  {
 0182    policyBuilder.RequireAuthenticatedUser();
 0183    policyBuilder.AddRequirements(new RobotClientShouldOnlineRequirement());
 0184  });
 185
 186/*
 187 * LGDX Depency Injection
 188 */
 189// Administrator
 0190builder.Services.AddScoped<IApiKeyService, ApiKeyService>();
 0191builder.Services.AddScoped<IRobotCertificateService, RobotCertificateService>();
 0192builder.Services.AddScoped<IRoleService, RoleService>();
 0193builder.Services.AddScoped<IUserService, UserService>();
 194
 195// Automation
 0196builder.Services.AddScoped<IAutoTaskService, AutoTaskService>();
 0197builder.Services.AddScoped<IFlowService, FlowService>();
 0198builder.Services.AddScoped<IProgressService, ProgressService>();
 199
 200// Identity
 0201builder.Services.AddScoped<IAuthService, AuthService>();
 0202builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
 203
 204// Automation
 0205builder.Services.AddScoped<IRealmService, RealmService>();
 0206builder.Services.AddScoped<IWaypointService, WaypointService>();
 0207builder.Services.AddScoped<IRobotService, RobotService>();
 208
 209// Custom Services
 0210builder.Services.AddScoped<ITriggerRetryService, TriggerRetryService>();
 0211builder.Services.AddScoped<ITriggerService, TriggerService>();
 0212builder.Services.AddScoped<IEmailService, EmailService>();
 0213builder.Services.AddSingleton<IEventService, EventService>();
 0214builder.Services.AddScoped<IAutoTaskSchedulerService, AutoTaskSchedulerService>();
 0215builder.Services.AddScoped<IOnlineRobotsService, OnlineRobotsService>();
 0216builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
 217
 0218var app = builder.Build();
 219
 220// Configure the HTTP request pipeline.
 0221if (app.Environment.IsDevelopment())
 0222{
 0223  app.MapOpenApi();
 0224  app.MapScalarApiReference();
 0225}
 226
 0227app.UseHttpsRedirection();
 228
 0229app.UseAuthentication();
 0230app.UseAuthorization();
 231
 0232app.MapControllerRoute(
 0233  name: "Area",
 0234  pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
 0235app.MapGrpcService<RobotClientsService>();
 0236app.UseLgdxExpectionHandling();
 237
 0238app.Run();
 239
 240internal sealed class BearerSecuritySchemeTransformer(
 241  IAuthenticationSchemeProvider authenticationSchemeProvider,
 242  IServer server
 243) : IOpenApiDocumentTransformer
 244{
 245  public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToke
 246  {
 247    var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
 248    if (authenticationSchemes.Any(authScheme => authScheme.Name == JwtBearerDefaults.AuthenticationScheme))
 249    {
 250      var requirements = new Dictionary<string, OpenApiSecurityScheme>
 251      {
 252        ["Bearer"] = new OpenApiSecurityScheme
 253        {
 254          Type = SecuritySchemeType.Http,
 255          Scheme = "bearer", // "bearer" refers to the header name here
 256          In = ParameterLocation.Header,
 257          BearerFormat = "Json Web Token"
 258        }
 259      };
 260      document.Components ??= new OpenApiComponents();
 261      document.Components.SecuritySchemes = requirements;
 262    }
 263    document.Info = new()
 264    {
 265      Title = "LGDXRobot Cloud API",
 266      Version = "v1",
 267      Description = "Core API for the LGDXRobot Cloud.",
 268      Contact = new OpenApiContact
 269      {
 270        Name = "LGDXRobot",
 271        Url = new Uri("https://lgdxrobot.bristolgram.uk"),
 272      },
 273      License = new OpenApiLicense
 274      {
 275        Name = "The MIT License",
 276        Url = new Uri("https://opensource.org/license/MIT")
 277      }
 278    };
 279    var address = server.Features.Get<IServerAddressesFeature>()!.Addresses.LastOrDefault(); // HTTPS port must higher
 280    address = address!.Replace("[::]", "localhost");
 281    document.Servers =
 282    [
 283      new()
 284      {
 285        Url = address,
 286        Description = "Default Server"
 287      }
 288    ];
 289  }
 290}

Methods/Properties

<Main>$(System.String[])