< Summary

Information
Class: LGDXRobotCloud.API.Services.Administration.ApiKeyService
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Administration/ApiKeyService.cs
Line coverage
65%
Covered lines: 118
Uncovered lines: 63
Coverable lines: 181
Total lines: 247
Line coverage: 65.1%
Branch coverage
52%
Covered branches: 21
Total branches: 40
Branch coverage: 52.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%66100%
RemoveApiKeyCache(...)100%210%
GetApiKeysAsync()100%22100%
GetApiKeyAsync()100%22100%
GetApiKeySecretAsync()100%22100%
GenerateApiKeys()100%11100%
AddApiKeyAsync()100%22100%
UpdateApiKeyAsync()0%620%
UpdateApiKeySecretAsync()90%101087.5%
DeleteApiKeyAsync()0%4260%
SearchApiKeysAsync()50%22100%
ValidateApiKeyAsync()0%4260%

File(s)

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

#LineLine coverage
 1using System.Security.Cryptography;
 2using LGDXRobotCloud.API.Exceptions;
 3using LGDXRobotCloud.Data.DbContexts;
 4using LGDXRobotCloud.Data.Entities;
 5using LGDXRobotCloud.Data.Models.Business.Administration;
 6using LGDXRobotCloud.Utilities.Enums;
 7using LGDXRobotCloud.Utilities.Helpers;
 8using Microsoft.EntityFrameworkCore;
 9using Microsoft.Extensions.Caching.Memory;
 10
 11namespace LGDXRobotCloud.API.Services.Administration;
 12
 13public interface IApiKeyService
 14{
 15  Task<(IEnumerable<ApiKeyBusinessModel>, PaginationHelper)> GetApiKeysAsync(string? name, bool isThirdParty, int pageNu
 16  Task<ApiKeyBusinessModel> GetApiKeyAsync(int apiKeyId);
 17  Task<ApiKeySecretBusinessModel> GetApiKeySecretAsync(int apiKeyId);
 18  Task<ApiKeyBusinessModel> AddApiKeyAsync(ApiKeyCreateBusinessModel apiKeyCreateBusinessModel);
 19  Task<bool> UpdateApiKeyAsync(int apiKeyId, ApiKeyUpdateBusinessModel apiKeyUpdateBusinessModel);
 20  Task<bool> UpdateApiKeySecretAsync(int apiKeyId, ApiKeySecretUpdateBusinessModel apiKeySecretUpdateBusinessModel);
 21  Task<bool> DeleteApiKeyAsync(int apiKeyId);
 22
 23  Task<IEnumerable<ApiKeySearchBusinessModel>> SearchApiKeysAsync(string? name);
 24  Task<int?> ValidateApiKeyAsync(string? apiKey);
 25}
 26
 2727public class ApiKeyService(
 2728    IActivityLogService activityLogService,
 2729    IMemoryCache memoryCache,
 2730    LgdxContext context
 2731  ) : IApiKeyService
 32{
 2733  private readonly IActivityLogService _activityLogService = activityLogService ?? throw new ArgumentNullException(nameo
 2734  private readonly LgdxContext _context = context ?? throw new ArgumentNullException(nameof(context));
 2735  private readonly IMemoryCache _memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
 36
 37  private void RemoveApiKeyCache(string apiKeySecret)
 038  {
 039    string hashed = LgdxHelper.GenerateSha256Hash(apiKeySecret);
 040    _memoryCache.Remove($"ValidateApiKeyAsync_{hashed}");
 041  }
 42
 43  public async Task<(IEnumerable<ApiKeyBusinessModel>, PaginationHelper)> GetApiKeysAsync(string? name, bool isThirdPart
 644  {
 645    var query = _context.ApiKeys as IQueryable<ApiKey>;
 646    query = query.Where(a => a.IsThirdParty == isThirdParty);
 647    if (!string.IsNullOrWhiteSpace(name))
 348    {
 349      name = name.Trim();
 350      query = query.Where(a => a.Name.ToLower().Contains(name.ToLower()));
 351    }
 652    var itemCount = await query.CountAsync();
 653    var PaginationHelper = new PaginationHelper(itemCount, pageNumber, pageSize);
 654    var apiKeys = await query.AsNoTracking()
 655      .OrderBy(a => a.Id)
 656      .Skip(pageSize * (pageNumber - 1))
 657      .Take(pageSize)
 658      .Select(a => new ApiKeyBusinessModel
 659      {
 660        Id = a.Id,
 661        Name = a.Name,
 662        IsThirdParty = a.IsThirdParty,
 663      })
 664      .ToListAsync();
 665    return (apiKeys, PaginationHelper);
 666  }
 67
 68  public async Task<ApiKeyBusinessModel> GetApiKeyAsync(int apiKeyId)
 569  {
 570    return await _context.ApiKeys.AsNoTracking()
 571      .Where(a => a.Id == apiKeyId)
 572      .Select(a => new ApiKeyBusinessModel {
 573        Id = a.Id,
 574        Name = a.Name,
 575        IsThirdParty = a.IsThirdParty,
 576      })
 577      .FirstOrDefaultAsync()
 578        ?? throw new LgdxNotFound404Exception();
 479  }
 80
 81  public async Task<ApiKeySecretBusinessModel> GetApiKeySecretAsync(int apiKeyId)
 582  {
 583    var apiKeySecret = await _context.ApiKeys.AsNoTracking()
 584      .Where(a => a.Id == apiKeyId)
 585      .Select(a => new ApiKeySecretBusinessModel
 586      {
 587        Secret = a.Secret,
 588      })
 589      .FirstOrDefaultAsync()
 590        ?? throw new LgdxNotFound404Exception();
 91
 492    await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel
 493    {
 494      EntityName = nameof(ApiKey),
 495      EntityId = apiKeyId.ToString(),
 496      Action = ActivityAction.ApiKeySecretRead,
 497    });
 98
 499    return apiKeySecret;
 4100  }
 101
 102  private static string GenerateApiKeys()
 1103  {
 1104    var bytes = RandomNumberGenerator.GetBytes(32);
 1105    string base64String = Convert.ToBase64String(bytes)
 1106      .Replace("+", "-")
 1107      .Replace("/", "_");
 1108    return "LGDX" + base64String;
 1109  }
 110
 111  public async Task<ApiKeyBusinessModel> AddApiKeyAsync(ApiKeyCreateBusinessModel apiKeyCreateBusinessModel)
 2112  {
 2113    if (!apiKeyCreateBusinessModel.IsThirdParty)
 1114      apiKeyCreateBusinessModel.Secret = GenerateApiKeys();
 2115    var apikey = apiKeyCreateBusinessModel.ToEntity();
 2116    await _context.ApiKeys.AddAsync(apikey);
 2117    await _context.SaveChangesAsync();
 118
 2119    await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel
 2120    {
 2121      EntityName = nameof(ApiKey),
 2122      EntityId = apikey.Id.ToString(),
 2123      Action = ActivityAction.Create,
 2124    });
 125
 2126    return new ApiKeyBusinessModel
 2127    {
 2128      Id = apikey.Id,
 2129      Name = apikey.Name,
 2130      IsThirdParty = apikey.IsThirdParty
 2131    };
 2132  }
 133
 134  public async Task<bool> UpdateApiKeyAsync(int apiKeyId, ApiKeyUpdateBusinessModel apiKeyUpdateBusinessModel)
 0135  {
 0136    bool result = await _context.ApiKeys
 0137      .Where(a => a.Id == apiKeyId)
 0138      .ExecuteUpdateAsync(setters => setters
 0139        .SetProperty(a => a.Name, apiKeyUpdateBusinessModel.Name)) == 1;
 140
 0141    if (result)
 0142    {
 0143      await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel
 0144      {
 0145        EntityName = nameof(ApiKey),
 0146        EntityId = apiKeyId.ToString(),
 0147        Action = ActivityAction.Update,
 0148      });
 0149    }
 0150    return result;
 0151  }
 152
 153  public async Task<bool> UpdateApiKeySecretAsync(int apiKeyId, ApiKeySecretUpdateBusinessModel apiKeySecretUpdateBusine
 4154  {
 4155    var apiKey = await _context.ApiKeys.Where(a => a.Id == apiKeyId).FirstOrDefaultAsync()
 4156      ?? throw new LgdxNotFound404Exception();
 3157    if (!apiKey.IsThirdParty && !string.IsNullOrEmpty(apiKeySecretUpdateBusinessModel.Secret))
 1158    {
 1159      throw new LgdxValidation400Expection(nameof(apiKeySecretUpdateBusinessModel.Secret), "The LGDXRobot Cloud API Key 
 160    }
 161
 2162    var oldSecret = apiKey.Secret;
 2163    apiKey.Secret = apiKeySecretUpdateBusinessModel.Secret;
 2164    bool result = await _context.SaveChangesAsync() == 1;
 165
 2166    if (result)
 2167    {
 2168      if (!apiKey.IsThirdParty)
 0169      {
 0170        RemoveApiKeyCache(oldSecret);
 0171      }
 2172      await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel
 2173      {
 2174        EntityName = nameof(ApiKey),
 2175        EntityId = apiKeyId.ToString(),
 2176        Action = ActivityAction.ApiKeySecretUpdate,
 2177      });
 2178    }
 2179    return result;
 2180  }
 181
 182  public async Task<bool> DeleteApiKeyAsync(int apiKeyId)
 0183  {
 0184    var apiKey = await _context.ApiKeys.Where(a => a.Id == apiKeyId).FirstOrDefaultAsync()
 0185      ?? throw new LgdxNotFound404Exception();
 186
 0187    var oldSecret = apiKey.Secret;
 0188    bool result = await _context.ApiKeys.Where(a => a.Id == apiKeyId)
 0189      .ExecuteDeleteAsync() == 1;
 190
 0191    if (result)
 0192    {
 0193      if (!apiKey.IsThirdParty)
 0194      {
 0195        RemoveApiKeyCache(oldSecret);
 0196      }
 0197      await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel
 0198      {
 0199        EntityName = nameof(ApiKey),
 0200        EntityId = apiKeyId.ToString(),
 0201        Action = ActivityAction.Delete,
 0202      });
 0203    }
 0204    return result;
 0205  }
 206
 207  public async Task<IEnumerable<ApiKeySearchBusinessModel>> SearchApiKeysAsync(string? name)
 5208  {
 5209    var n = name ?? string.Empty;
 5210    return await _context.ApiKeys.AsNoTracking()
 5211      .Where(w => w.Name.ToLower().Contains(n.ToLower()))
 5212      .Where(w => w.IsThirdParty == true)
 5213      .Take(10)
 5214      .Select(t => new ApiKeySearchBusinessModel {
 5215        Id = t.Id,
 5216        Name = t.Name,
 5217      })
 5218      .ToListAsync();
 5219  }
 220
 221  public async Task<int?> ValidateApiKeyAsync(string? apiKey)
 0222  {
 0223    if (string.IsNullOrWhiteSpace(apiKey))
 0224    {
 0225      return null;
 226    }
 227
 0228    string hashed = LgdxHelper.GenerateSha256Hash(apiKey);
 0229    if (_memoryCache.TryGetValue($"ValidateApiKeyAsync_{hashed}", out int exist))
 0230    {
 0231      return exist;
 232    }
 233
 0234    int? apiKeyId = await _context.ApiKeys.AsNoTracking()
 0235      .Where(a => a.Secret == apiKey)
 0236      .Where(a => !a.IsThirdParty)
 0237      .Select(a => a.Id)
 0238      .FirstOrDefaultAsync();
 239
 0240    if (apiKeyId != null)
 0241    {
 0242      _memoryCache.Set($"ValidateApiKeyAsync_{hashed}", true, TimeSpan.FromMinutes(5));
 0243    }
 244
 0245    return apiKeyId;
 0246  }
 247}