DefaultAuthenticationProvider.cs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. using System;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Threading.Tasks;
  4. using Jellyfin.Data.Entities;
  5. using MediaBrowser.Controller.Authentication;
  6. using MediaBrowser.Model.Cryptography;
  7. namespace Jellyfin.Server.Implementations.Users
  8. {
  9. /// <summary>
  10. /// The default authentication provider.
  11. /// </summary>
  12. public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
  13. {
  14. private readonly ICryptoProvider _cryptographyProvider;
  15. /// <summary>
  16. /// Initializes a new instance of the <see cref="DefaultAuthenticationProvider"/> class.
  17. /// </summary>
  18. /// <param name="cryptographyProvider">The cryptography provider.</param>
  19. public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider)
  20. {
  21. _cryptographyProvider = cryptographyProvider;
  22. }
  23. /// <inheritdoc />
  24. public string Name => "Default";
  25. /// <inheritdoc />
  26. public bool IsEnabled => true;
  27. /// <inheritdoc />
  28. // This is dumb and an artifact of the backwards way auth providers were designed.
  29. // This version of authenticate was never meant to be called, but needs to be here for interface compat
  30. // Only the providers that don't provide local user support use this
  31. public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
  32. {
  33. throw new NotImplementedException();
  34. }
  35. /// <inheritdoc />
  36. // This is the version that we need to use for local users. Because reasons.
  37. public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User? resolvedUser)
  38. {
  39. [DoesNotReturn]
  40. static void ThrowAuthenticationException()
  41. {
  42. throw new AuthenticationException("Invalid username or password");
  43. }
  44. if (resolvedUser is null)
  45. {
  46. ThrowAuthenticationException();
  47. }
  48. // As long as jellyfin supports password-less users, we need this little block here to accommodate
  49. if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password))
  50. {
  51. return Task.FromResult(new ProviderAuthenticationResult
  52. {
  53. Username = username
  54. });
  55. }
  56. // Handle the case when the stored password is null, but the user tried to login with a password
  57. if (resolvedUser.Password is null)
  58. {
  59. ThrowAuthenticationException();
  60. }
  61. PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password);
  62. if (!_cryptographyProvider.Verify(readyHash, password))
  63. {
  64. ThrowAuthenticationException();
  65. }
  66. // Migrate old hashes to the new default
  67. if (!string.Equals(readyHash.Id, _cryptographyProvider.DefaultHashMethod, StringComparison.Ordinal))
  68. {
  69. ChangePassword(resolvedUser, password);
  70. }
  71. return Task.FromResult(new ProviderAuthenticationResult
  72. {
  73. Username = username
  74. });
  75. }
  76. /// <inheritdoc />
  77. public bool HasPassword(User user)
  78. => !string.IsNullOrEmpty(user?.Password);
  79. /// <inheritdoc />
  80. public Task ChangePassword(User user, string newPassword)
  81. {
  82. if (string.IsNullOrEmpty(newPassword))
  83. {
  84. user.Password = null;
  85. return Task.CompletedTask;
  86. }
  87. PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword);
  88. user.Password = newPasswordHash.ToString();
  89. return Task.CompletedTask;
  90. }
  91. }
  92. }