mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 12:02:35 +02:00
New: Migrate user passwords to Pbkdf2
(cherry picked from commit 269e72a2193b584476bec338ef41e6fb2e5cbea6)
This commit is contained in:
parent
8911386ed0
commit
104aadfdb7
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Authentication
|
namespace NzbDrone.Core.Authentication
|
||||||
@ -8,5 +8,7 @@ public class User : ModelBase
|
|||||||
public Guid Identifier { get; set; }
|
public Guid Identifier { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
public string Salt { get; set; }
|
||||||
|
public int Iterations { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
@ -21,15 +23,16 @@ public interface IUserService
|
|||||||
|
|
||||||
public class UserService : IUserService, IHandle<ApplicationStartedEvent>
|
public class UserService : IUserService, IHandle<ApplicationStartedEvent>
|
||||||
{
|
{
|
||||||
|
private const int ITERATIONS = 10000;
|
||||||
|
private const int SALT_SIZE = 128 / 8;
|
||||||
|
private const int NUMBER_OF_BYTES = 256 / 8;
|
||||||
|
|
||||||
private readonly IUserRepository _repo;
|
private readonly IUserRepository _repo;
|
||||||
private readonly IAppFolderInfo _appFolderInfo;
|
private readonly IAppFolderInfo _appFolderInfo;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
|
||||||
public UserService(IUserRepository repo,
|
public UserService(IUserRepository repo, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider)
|
||||||
IAppFolderInfo appFolderInfo,
|
|
||||||
IDiskProvider diskProvider,
|
|
||||||
IConfigFileProvider configFileProvider)
|
|
||||||
{
|
{
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
@ -39,12 +42,15 @@ public UserService(IUserRepository repo,
|
|||||||
|
|
||||||
public User Add(string username, string password)
|
public User Add(string username, string password)
|
||||||
{
|
{
|
||||||
return _repo.Insert(new User
|
var user = new User
|
||||||
{
|
{
|
||||||
Identifier = Guid.NewGuid(),
|
Identifier = Guid.NewGuid(),
|
||||||
Username = username.ToLowerInvariant(),
|
Username = username.ToLowerInvariant()
|
||||||
Password = password.SHA256Hash()
|
};
|
||||||
});
|
|
||||||
|
SetUserHashedPassword(user, password);
|
||||||
|
|
||||||
|
return _repo.Insert(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public User Update(User user)
|
public User Update(User user)
|
||||||
@ -63,7 +69,7 @@ public User Upsert(string username, string password)
|
|||||||
|
|
||||||
if (user.Password != password)
|
if (user.Password != password)
|
||||||
{
|
{
|
||||||
user.Password = password.SHA256Hash();
|
SetUserHashedPassword(user, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Username = username.ToLowerInvariant();
|
user.Username = username.ToLowerInvariant();
|
||||||
@ -90,7 +96,20 @@ public User FindUser(string username, string password)
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.Salt.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
// If password matches stored SHA256 hash, update to salted hash and verify.
|
||||||
if (user.Password == password.SHA256Hash())
|
if (user.Password == password.SHA256Hash())
|
||||||
|
{
|
||||||
|
SetUserHashedPassword(user, password);
|
||||||
|
|
||||||
|
return Update(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VerifyHashedPassword(user, password))
|
||||||
{
|
{
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@ -103,6 +122,43 @@ public User FindUser(Guid identifier)
|
|||||||
return _repo.FindUser(identifier);
|
return _repo.FindUser(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private User SetUserHashedPassword(User user, string password)
|
||||||
|
{
|
||||||
|
var salt = GenerateSalt();
|
||||||
|
|
||||||
|
user.Iterations = ITERATIONS;
|
||||||
|
user.Salt = Convert.ToBase64String(salt);
|
||||||
|
user.Password = GetHashedPassword(password, salt, ITERATIONS);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] GenerateSalt()
|
||||||
|
{
|
||||||
|
var salt = new byte[SALT_SIZE];
|
||||||
|
RandomNumberGenerator.Create().GetBytes(salt);
|
||||||
|
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetHashedPassword(string password, byte[] salt, int iterations)
|
||||||
|
{
|
||||||
|
return Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||||
|
password: password,
|
||||||
|
salt: salt,
|
||||||
|
prf: KeyDerivationPrf.HMACSHA512,
|
||||||
|
iterationCount: iterations,
|
||||||
|
numBytesRequested: NUMBER_OF_BYTES));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool VerifyHashedPassword(User user, string password)
|
||||||
|
{
|
||||||
|
var salt = Convert.FromBase64String(user.Salt);
|
||||||
|
var hashedPassword = GetHashedPassword(password, salt, user.Iterations);
|
||||||
|
|
||||||
|
return user.Password == hashedPassword;
|
||||||
|
}
|
||||||
|
|
||||||
public void Handle(ApplicationStartedEvent message)
|
public void Handle(ApplicationStartedEvent message)
|
||||||
{
|
{
|
||||||
if (_repo.All().Any())
|
if (_repo.All().Any())
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(215)]
|
||||||
|
public class add_salt_to_users : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("Users")
|
||||||
|
.AddColumn("Salt").AsString().Nullable()
|
||||||
|
.AddColumn("Iterations").AsInt32().Nullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
<PackageReference Include="Servarr.FFprobe" Version="5.1.2.106" />
|
<PackageReference Include="Servarr.FFprobe" Version="5.1.2.106" />
|
||||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||||
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
<PackageReference Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
|
||||||
|
Loading…
Reference in New Issue
Block a user