< Summary

Information
Class: LGDXRobotCloud.API.Services.Administration.RobotCertificateService
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Administration/RobotCertificateService.cs
Line coverage
100%
Covered lines: 135
Uncovered lines: 0
Coverable lines: 135
Total lines: 187
Line coverage: 100%
Branch coverage
87%
Covered branches: 7
Total branches: 8
Branch coverage: 87.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Administration/RobotCertificateService.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Security.Cryptography.X509Certificates;
 3using LGDXRobotCloud.API.Configurations;
 4using LGDXRobotCloud.API.Exceptions;
 5using LGDXRobotCloud.Data.DbContexts;
 6using LGDXRobotCloud.Data.Entities;
 7using LGDXRobotCloud.Data.Models.Business.Administration;
 8using LGDXRobotCloud.Utilities.Enums;
 9using LGDXRobotCloud.Utilities.Helpers;
 10using Microsoft.EntityFrameworkCore;
 11using Microsoft.Extensions.Options;
 12
 13namespace LGDXRobotCloud.API.Services.Administration;
 14
 15public interface IRobotCertificateService
 16{
 17  Task<(IEnumerable<RobotCertificateListBusinessModel>, PaginationHelper)> GetRobotCertificatesAsync(int pageNumber, int
 18  Task<RobotCertificateBusinessModel> GetRobotCertificateAsync(Guid robotCertificateId);
 19  Task<RobotCertificateIssueBusinessModel> IssueRobotCertificateAsync(Guid robotId);
 20  Task<RobotCertificateRenewBusinessModel> RenewRobotCertificateAsync(RobotCertificateRenewRequestBusinessModel robotCer
 21
 22  RootCertificateBusinessModel? GetRootCertificate();
 23}
 24
 825public class RobotCertificateService(
 826    IActivityLogService activityLogService,
 827    IOptionsSnapshot<LgdxRobotCloudConfiguration> options,
 828    LgdxContext context
 829  ) : IRobotCertificateService
 30{
 831  private readonly IActivityLogService _activityLogService = activityLogService;
 832  private readonly LgdxContext _context = context;
 833  private readonly LgdxRobotCloudConfiguration _lgdxRobotCloudConfiguration = options.Value;
 34
 35  private record CertificateDetail
 36  {
 637    required public string RootCertificate { get; set; }
 638    required public string RobotCertificatePrivateKey { get; set; }
 639    required public string RobotCertificatePublicKey { get; set; }
 640    required public string RobotCertificateThumbprint { get; set; }
 641    required public DateTime RobotCertificateNotBefore { get; set; }
 642    required public DateTime RobotCertificateNotAfter { get; set; }
 43  }
 44
 45  public async Task<(IEnumerable<RobotCertificateListBusinessModel>, PaginationHelper)> GetRobotCertificatesAsync(int pa
 146  {
 147    var query = _context.RobotCertificates as IQueryable<RobotCertificate>;
 148    var itemCount = await query.CountAsync();
 149    var PaginationHelper = new PaginationHelper(itemCount, pageNumber, pageSize);
 150    var robotCertificates = await query.AsNoTracking()
 151      .OrderBy(a => a.Id)
 152      .Skip(pageSize * (pageNumber - 1))
 153      .Take(pageSize)
 154      .Select(a => new RobotCertificateListBusinessModel {
 155        Id = a.Id,
 156        Thumbprint = a.Thumbprint,
 157        ThumbprintBackup = a.ThumbprintBackup,
 158        NotBefore = a.NotBefore,
 159        NotAfter = a.NotAfter
 160      })
 161      .ToListAsync();
 162    return (robotCertificates, PaginationHelper);
 163  }
 64
 65  public async Task<RobotCertificateBusinessModel> GetRobotCertificateAsync(Guid robotCertificateId)
 266  {
 267    return await _context.RobotCertificates.AsNoTracking()
 268      .Where(a => a.Id == robotCertificateId)
 269      .Include(a => a.Robot)
 270      .Select(a => new RobotCertificateBusinessModel {
 271        Id = a.Id,
 272        RobotId = a.Robot.Id,
 273        RobotName = a.Robot.Name,
 274        Thumbprint = a.Thumbprint,
 275        ThumbprintBackup = a.ThumbprintBackup,
 276        NotBefore = a.NotBefore,
 277        NotAfter = a.NotAfter
 278      })
 279      .FirstOrDefaultAsync()
 280        ?? throw new LgdxNotFound404Exception();
 181  }
 82
 83  private CertificateDetail GenerateRobotCertificate(Guid robotId)
 384  {
 385    X509Store store = new(StoreName.My, StoreLocation.CurrentUser);
 386    store.Open(OpenFlags.OpenExistingOnly);
 1187    X509Certificate2 rootCertificate = store.Certificates.First(c => c.SerialNumber == _lgdxRobotCloudConfiguration.Root
 88
 389    var certificateNotBefore = DateTime.UtcNow;
 390    var certificateNotAfter = DateTimeOffset.UtcNow.AddDays(_lgdxRobotCloudConfiguration.RobotCertificateValidDay);
 91
 392    var rsa = RSA.Create();
 393    var certificateRequest = new CertificateRequest("CN=LGDXRobot Cloud Robot Certificate for " + robotId.ToString() + "
 394    var certificate = certificateRequest.Create(rootCertificate, certificateNotBefore, certificateNotAfter, RandomNumber
 95
 396    return new CertificateDetail
 397    {
 398      RootCertificate = rootCertificate.ExportCertificatePem(),
 399      RobotCertificatePrivateKey = new string(PemEncoding.Write("PRIVATE KEY", rsa.ExportPkcs8PrivateKey())),
 3100      RobotCertificatePublicKey = certificate.ExportCertificatePem(),
 3101      RobotCertificateThumbprint = certificate.Thumbprint,
 3102      RobotCertificateNotBefore = certificateNotBefore,
 3103      RobotCertificateNotAfter = certificateNotAfter.DateTime
 3104    };
 3105  }
 106
 107  public async Task<RobotCertificateIssueBusinessModel> IssueRobotCertificateAsync(Guid robotId)
 1108  {
 1109    var certificate = GenerateRobotCertificate(robotId);
 110
 1111    await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel
 1112    {
 1113      EntityName = nameof(Robot),
 1114      EntityId = robotId.ToString(),
 1115      Action = ActivityAction.RobotCertificateCreate
 1116    });
 117
 1118    return new RobotCertificateIssueBusinessModel
 1119    {
 1120      RootCertificate = certificate.RootCertificate,
 1121      RobotCertificatePrivateKey = certificate.RobotCertificatePrivateKey,
 1122      RobotCertificatePublicKey = certificate.RobotCertificatePublicKey,
 1123      RobotCertificateThumbprint = certificate.RobotCertificateThumbprint,
 1124      RobotCertificateNotBefore = certificate.RobotCertificateNotBefore,
 1125      RobotCertificateNotAfter = certificate.RobotCertificateNotAfter
 1126    };
 1127  }
 128
 129  public async Task<RobotCertificateRenewBusinessModel> RenewRobotCertificateAsync(RobotCertificateRenewRequestBusinessM
 3130  {
 3131    var certificate = _context.RobotCertificates
 3132      .Where(c => c.Id == robotCertificateRenewRequestBusinessModel.CertificateId)
 3133      .FirstOrDefault()
 3134        ?? throw new LgdxNotFound404Exception();
 135
 2136    var newCertificate = GenerateRobotCertificate(certificate.RobotId);
 2137    if (robotCertificateRenewRequestBusinessModel.RevokeOldCertificate)
 1138    {
 1139      certificate.ThumbprintBackup = null;
 1140    }
 141    else
 1142    {
 1143      certificate.ThumbprintBackup = certificate.Thumbprint;
 1144    }
 2145    certificate.Thumbprint = newCertificate.RobotCertificateThumbprint;
 2146    certificate.NotBefore = DateTime.SpecifyKind(newCertificate.RobotCertificateNotBefore, DateTimeKind.Utc);
 2147    certificate.NotAfter = DateTime.SpecifyKind(newCertificate.RobotCertificateNotAfter, DateTimeKind.Utc);
 2148    await _context.SaveChangesAsync();
 149
 2150    var robot = await _context.Robots.AsNoTracking()
 2151      .Where(r => r.Id == certificate.RobotId)
 2152      .Select(r => new {
 2153        r.Id,
 2154        r.Name
 2155      })
 2156      .FirstOrDefaultAsync()
 2157        ?? throw new LgdxNotFound404Exception();
 158
 2159    await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel
 2160    {
 2161      EntityName = nameof(Robot),
 2162      EntityId = certificate.RobotId.ToString(),
 2163      Action = ActivityAction.RobotCertificateRenew
 2164    });
 165
 2166    return new RobotCertificateRenewBusinessModel
 2167    {
 2168      RobotId = robot.Id,
 2169      RobotName = robot.Name,
 2170      RootCertificate = newCertificate.RootCertificate,
 2171      RobotCertificatePrivateKey = newCertificate.RobotCertificatePrivateKey,
 2172      RobotCertificatePublicKey = newCertificate.RobotCertificatePublicKey
 2173    };
 2174  }
 175
 176  public RootCertificateBusinessModel? GetRootCertificate()
 1177  {
 1178    X509Store store = new(StoreName.My, StoreLocation.CurrentUser);
 1179    store.Open(OpenFlags.OpenExistingOnly);
 3180    X509Certificate2 rootCertificate = store.Certificates.First(c => c.SerialNumber == _lgdxRobotCloudConfiguration.Root
 1181    return new RootCertificateBusinessModel {
 1182      NotBefore = rootCertificate.NotBefore.ToUniversalTime(),
 1183      NotAfter = rootCertificate.NotAfter.ToUniversalTime(),
 1184      PublicKey = rootCertificate.ExportCertificatePem()
 1185    };
 1186  }
 187}