DefaultAuthenticationProvider.cs 3.7 KB

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