< 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: 196
Coverable lines: 196
Total lines: 313
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 20
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%420200%

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.AddLogging(builder => builder.AddConsole());
 53
 054builder.Services.AddMassTransit(cfg =>
 055{
 056  cfg.UsingRabbitMq((context, cfg) =>
 057  {
 058    cfg.Host(builder.Configuration["RabbitMq:Host"], builder.Configuration["RabbitMq:VirtualHost"], h =>
 059    {
 060      h.Username(builder.Configuration["RabbitMq:Username"] ?? string.Empty);
 061      h.Password(builder.Configuration["RabbitMq:Password"] ?? string.Empty);
 062    });
 063  });
 064});
 065builder.Services.AddMemoryCache();
 066builder.Services.AddControllers();
 067builder.Services.AddOpenApi(options =>
 068{
 069  options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
 070});
 071builder.Services.AddGrpc(cfg => cfg.EnableDetailedErrors = true);
 072if (builder.Environment.IsDevelopment())
 073{
 074  builder.Services.AddDbContext<LgdxContext>(cfg =>
 075    cfg.UseNpgsql(builder.Configuration.GetConnectionString("Default"))
 076    .LogTo(Console.WriteLine, LogLevel.Information)
 077    .EnableSensitiveDataLogging()
 078    .EnableDetailedErrors()
 079  );
 080  builder.Services.AddDbContext<ActivityContext>(cfg =>
 081    cfg.UseNpgsql(builder.Configuration.GetConnectionString("Activity"))
 082    .LogTo(Console.WriteLine, LogLevel.Information)
 083    .EnableSensitiveDataLogging()
 084    .EnableDetailedErrors()
 085  );
 086}
 87else
 088{
 089  builder.Services.AddDbContext<LgdxContext>(cfg =>
 090    cfg.UseNpgsql(builder.Configuration.GetConnectionString("Default"))
 091  );
 092  builder.Services.AddDbContext<ActivityContext>(cfg =>
 093    cfg.UseNpgsql(builder.Configuration.GetConnectionString("Activity"))
 094  );
 095}
 096builder.Services.AddHttpContextAccessor();
 97
 98/*
 99 * Authentication
 100 */
 0101builder.Services.AddIdentity<LgdxUser, LgdxRole>()
 0102  .AddEntityFrameworkStores<LgdxContext>()
 0103  .AddTokenProvider<AuthenticatorTokenProvider<LgdxUser>>(TokenOptions.DefaultAuthenticatorProvider)
 0104  .AddTokenProvider<DataProtectorTokenProvider<LgdxUser>>(TokenOptions.DefaultProvider);
 0105builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.CertificationScheme)
 0106  .AddCertificate(LgdxRobotCloudAuthenticationSchemes.CertificationScheme, cfg =>
 0107    {
 0108      cfg.AllowedCertificateTypes = CertificateTypes.All;
 0109      cfg.RevocationMode = X509RevocationMode.NoCheck;
 0110      cfg.Events = new CertificateAuthenticationEvents()
 0111      {
 0112        OnCertificateValidated = ctx =>
 0113        {
 0114          if (ctx.ClientCertificate.Thumbprint != builder.Configuration["LGDXRobotCloud:InternalCertificateThumbprint"])
 0115          {
 0116            ctx.Fail("Invalid certificate.");
 0117            return Task.CompletedTask;
 0118          }
 0119          ctx.Success();
 0120          return Task.CompletedTask;
 0121        }
 0122      };
 0123    }
 0124  );
 0125builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.ApiKeyScheme)
 0126  .AddScheme<ApiKeyAuthenticationSchemeOptions, ApiKeyAuthenticationSchemeHandler>(
 0127    LgdxRobotCloudAuthenticationSchemes.ApiKeyScheme,
 0128    options => {}
 0129  );
 0130builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
 0131  .AddJwtBearer(cfg =>
 0132  {
 0133    cfg.TokenValidationParameters = new TokenValidationParameters
 0134    {
 0135      ValidateIssuer = true,
 0136      ValidateAudience = true,
 0137      ValidateLifetime = true,
 0138      ValidateIssuerSigningKey = true,
 0139      ValidIssuer = builder.Configuration["LGDXRobotCloudSecret:LgdxUserJwtIssuer"],
 0140      ValidAudience = builder.Configuration["LGDXRobotCloudSecret:LgdxUserJwtIssuer"],
 0141      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["LGDXRobotCloudSecret:Lgd
 0142      ClockSkew = TimeSpan.Zero
 0143    };
 0144  });
 0145builder.Services.AddTransient<ValidateRobotClientsCertificate>();
 0146builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme)
 0147  .AddCertificate(LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme, cfg =>
 0148  {
 0149    cfg.AllowedCertificateTypes = CertificateTypes.All;
 0150    cfg.RevocationMode = X509RevocationMode.NoCheck;
 0151    cfg.Events = new CertificateAuthenticationEvents()
 0152    {
 0153      OnCertificateValidated = async ctx =>
 0154      {
 0155        string subject = ctx.ClientCertificate.Subject;
 0156        string guid = subject.Substring(subject.IndexOf("OID.0.9.2342.19200300.100.1.1=") + 30, 36);
 0157        if (guid == string.Empty)
 0158        {
 0159          ctx.Fail("Robot ID not found.");
 0160          return;
 0161        }
 0162        var validatior = ctx.HttpContext.RequestServices.GetService<ValidateRobotClientsCertificate>();
 0163        if (!await validatior!.Validate(ctx.ClientCertificate, Guid.Parse(guid)))
 0164        {
 0165          ctx.Fail("Invalid certificate / Robot not found.");
 0166          return;
 0167        }
 0168        var claims = new[] {
 0169          new Claim(ClaimTypes.NameIdentifier, guid)
 0170        };
 0171        ctx.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, ctx.Scheme.Name));
 0172        ctx.Success();
 0173      }
 0174    };
 0175  });
 0176builder.Services.AddAuthentication(LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme)
 0177  .AddJwtBearer(LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme, cfg =>
 0178  {
 0179    cfg.TokenValidationParameters = new TokenValidationParameters
 0180    {
 0181      ValidateIssuer = true,
 0182      ValidateAudience = true,
 0183      ValidateLifetime = true,
 0184      ValidateIssuerSigningKey = true,
 0185      ValidIssuer = builder.Configuration["LGDXRobotCloudSecret:RobotClientsJwtIssuer"],
 0186      ValidAudience = builder.Configuration["LGDXRobotCloudSecret:RobotClientsJwtIssuer"],
 0187      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["LGDXRobotCloudSecret:Rob
 0188      ClockSkew = TimeSpan.Zero
 0189    };
 0190  });
 0191builder.Services.AddScoped<IAuthorizationHandler, ValidateLgdxUserAccessHandler>();
 0192builder.Services.AddAuthorizationBuilder()
 0193  .AddPolicy("ValidateLgdxUserAccess", policyBuilder =>
 0194  {
 0195    policyBuilder.RequireAuthenticatedUser();
 0196    policyBuilder.AddRequirements(new ValidateLgdxUserAccessRequirement());
 0197  });
 0198builder.Services.AddScoped<IAuthorizationHandler, RobotClientShouldOnlineHandler>();
 0199builder.Services.AddAuthorizationBuilder()
 0200  .AddPolicy("RobotClientShouldOnline", policyBuilder =>
 0201  {
 0202    policyBuilder.RequireAuthenticatedUser();
 0203    policyBuilder.AddRequirements(new RobotClientShouldOnlineRequirement());
 0204  });
 205
 206/*
 207 * LGDX Depency Injection
 208 */
 209// Administrator
 0210builder.Services.AddScoped<IActivityLogService, ActivityLogService>();
 0211builder.Services.AddScoped<IApiKeyService, ApiKeyService>();
 0212builder.Services.AddScoped<IRobotCertificateService, RobotCertificateService>();
 0213builder.Services.AddScoped<IRoleService, RoleService>();
 0214builder.Services.AddScoped<IUserService, UserService>();
 215
 216// Automation
 0217builder.Services.AddScoped<IAutoTaskService, AutoTaskService>();
 0218builder.Services.AddScoped<IFlowService, FlowService>();
 0219builder.Services.AddScoped<IProgressService, ProgressService>();
 0220builder.Services.AddScoped<IAutoTaskPathPlannerService, AutoTaskPathPlannerService>();
 221
 222// Identity
 0223builder.Services.AddScoped<IAuthService, AuthService>();
 0224builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
 225
 226// Navigation
 0227builder.Services.AddScoped<IRealmService, RealmService>();
 0228builder.Services.AddScoped<IWaypointService, WaypointService>();
 0229builder.Services.AddScoped<IRobotService, RobotService>();
 0230builder.Services.AddScoped<IMapEditorService, MapEditorService>();
 231
 232// Custom Services
 0233builder.Services.AddScoped<ITriggerRetryService, TriggerRetryService>();
 0234builder.Services.AddScoped<ITriggerService, TriggerService>();
 0235builder.Services.AddScoped<IEmailService, EmailService>();
 0236builder.Services.AddSingleton<IEventService, EventService>();
 0237builder.Services.AddScoped<IAutoTaskSchedulerService, AutoTaskSchedulerService>();
 0238builder.Services.AddScoped<IOnlineRobotsService, OnlineRobotsService>();
 0239builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
 240
 0241var app = builder.Build();
 242
 243// Configure the HTTP request pipeline.
 0244if (app.Environment.IsDevelopment())
 0245{
 0246  app.MapOpenApi();
 0247  app.MapScalarApiReference();
 0248}
 249
 0250app.UseHttpsRedirection();
 251
 0252app.UseAuthentication();
 0253app.UseAuthorization();
 254
 0255app.MapControllerRoute(
 0256  name: "Area",
 0257  pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
 0258app.MapGrpcService<RobotClientsService>();
 0259app.UseLgdxExpectionHandling();
 260
 0261app.Run();
 262
 263internal sealed class BearerSecuritySchemeTransformer(
 264  IAuthenticationSchemeProvider authenticationSchemeProvider,
 265  IServer server
 266) : IOpenApiDocumentTransformer
 267{
 268  public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToke
 269  {
 270    var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
 271    if (authenticationSchemes.Any(authScheme => authScheme.Name == JwtBearerDefaults.AuthenticationScheme))
 272    {
 273      var requirements = new Dictionary<string, OpenApiSecurityScheme>
 274      {
 275        ["Bearer"] = new OpenApiSecurityScheme
 276        {
 277          Type = SecuritySchemeType.Http,
 278          Scheme = "bearer", // "bearer" refers to the header name here
 279          In = ParameterLocation.Header,
 280          BearerFormat = "Json Web Token"
 281        }
 282      };
 283      document.Components ??= new OpenApiComponents();
 284      document.Components.SecuritySchemes = requirements;
 285    }
 286    document.Info = new()
 287    {
 288      Title = "LGDXRobot Cloud API",
 289      Version = "v1",
 290      Description = "Core API for the LGDXRobot Cloud.",
 291      Contact = new OpenApiContact
 292      {
 293        Name = "LGDXRobot",
 294        Url = new Uri("https://lgdxrobot.bristolgram.uk"),
 295      },
 296      License = new OpenApiLicense
 297      {
 298        Name = "The MIT License",
 299        Url = new Uri("https://opensource.org/license/MIT")
 300      }
 301    };
 302    var address = server.Features.Get<IServerAddressesFeature>()!.Addresses.LastOrDefault(); // HTTPS port must higher
 303    address = address!.Replace("[::]", "localhost");
 304    document.Servers =
 305    [
 306      new()
 307      {
 308        Url = address,
 309        Description = "Default Server"
 310      }
 311    ];
 312  }
 313}

Methods/Properties

<Main>$(System.String[])