|
|
@ -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 @@ namespace NzbDrone.Core.Authentication
|
|
|
|
|
|
|
|
|
|
|
|
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 @@ namespace NzbDrone.Core.Authentication
|
|
|
|
|
|
|
|
|
|
|
|
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 @@ namespace NzbDrone.Core.Authentication
|
|
|
|
|
|
|
|
|
|
|
|
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 @@ namespace NzbDrone.Core.Authentication
|
|
|
|
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 @@ namespace NzbDrone.Core.Authentication
|
|
|
|
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())
|
|
|
|