MdpSourcePasswordResolver.cs 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. using System.Text.RegularExpressions;
  2. using Admin.NET.Core;
  3. using Microsoft.Extensions.Logging;
  4. namespace Admin.NET.Plugin.AiDOP.DataPlatform;
  5. public interface IMdpSourcePasswordResolver
  6. {
  7. /// <summary>解析数据源口令。返回值不得写入日志、实体或数据库。</summary>
  8. string Resolve(string sourceCode, string? storedSecret);
  9. }
  10. public sealed class MdpSourceSecretException : Exception
  11. {
  12. public string Code { get; }
  13. public MdpSourceSecretException(string code, string message) : base(message)
  14. {
  15. Code = code;
  16. }
  17. }
  18. /// <summary>
  19. /// 口令只在建连时解析:环境变量优先,其次系统密文,最后才是迁移期明文。
  20. /// </summary>
  21. public sealed class MdpSourcePasswordResolver : IMdpSourcePasswordResolver, ITransient
  22. {
  23. private readonly ILogger<MdpSourcePasswordResolver>? _logger;
  24. public MdpSourcePasswordResolver(ILogger<MdpSourcePasswordResolver>? logger = null)
  25. {
  26. _logger = logger;
  27. }
  28. public static string EnvironmentVariableName(string sourceCode)
  29. {
  30. if (string.IsNullOrWhiteSpace(sourceCode))
  31. throw new ArgumentException("sourceCode 不能为空", nameof(sourceCode));
  32. var token = Regex.Replace(sourceCode.Trim().ToUpperInvariant(), "[^A-Z0-9]", "_");
  33. return "AIDOP_MDP_SOURCE_" + token + "_PASSWORD";
  34. }
  35. public string Resolve(string sourceCode, string? storedSecret)
  36. => ResolveCore(sourceCode, storedSecret, DecryptStored, _logger);
  37. internal static string ResolveCore(
  38. string sourceCode,
  39. string? storedSecret,
  40. Func<string, string?> decrypt,
  41. ILogger? logger)
  42. {
  43. var envName = EnvironmentVariableName(sourceCode);
  44. var fromEnv = Environment.GetEnvironmentVariable(envName);
  45. if (!string.IsNullOrEmpty(fromEnv))
  46. return fromEnv;
  47. if (!string.IsNullOrEmpty(storedSecret))
  48. {
  49. if (TryDecrypt(storedSecret, decrypt, out var plain))
  50. return plain;
  51. logger?.LogWarning(
  52. "mdp source {SourceCode} still uses a legacy plaintext secret. Set {EnvName} and clear the stored value.",
  53. sourceCode, envName);
  54. return storedSecret;
  55. }
  56. throw new MdpSourceSecretException(
  57. "SECRET_MISSING",
  58. $"源 {sourceCode} 未配置口令。请设置环境变量 {envName}。");
  59. }
  60. private static string? DecryptStored(string stored)
  61. {
  62. var decrypted = CryptogramUtil.Decrypt(stored);
  63. return decrypted;
  64. }
  65. private static bool TryDecrypt(string stored, Func<string, string?> decrypt, out string plain)
  66. {
  67. plain = "";
  68. try
  69. {
  70. var decrypted = decrypt(stored);
  71. // 原样返回入参不是解密成功,按迁移期明文处理。
  72. if (string.IsNullOrEmpty(decrypted) || string.Equals(decrypted, stored, StringComparison.Ordinal))
  73. return false;
  74. plain = decrypted;
  75. return true;
  76. }
  77. catch
  78. {
  79. return false;
  80. }
  81. }
  82. }