mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
made newlines into linux newlines
This commit is contained in:
parent
a0d31a49a0
commit
098de6b050
@ -1,149 +1,149 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Cryptography
|
namespace Emby.Server.Implementations.Cryptography
|
||||||
{
|
{
|
||||||
public class CryptographyProvider : ICryptoProvider
|
public class CryptographyProvider : ICryptoProvider
|
||||||
{
|
{
|
||||||
private HashSet<string> SupportedHashMethods;
|
private HashSet<string> SupportedHashMethods;
|
||||||
public string DefaultHashMethod => "SHA256";
|
public string DefaultHashMethod => "SHA256";
|
||||||
private RandomNumberGenerator rng;
|
private RandomNumberGenerator rng;
|
||||||
private int defaultiterations = 1000;
|
private int defaultiterations = 1000;
|
||||||
public CryptographyProvider()
|
public CryptographyProvider()
|
||||||
{
|
|
||||||
//Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
|
|
||||||
//there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
|
|
||||||
SupportedHashMethods = new HashSet<string>()
|
|
||||||
{
|
|
||||||
"MD5"
|
|
||||||
,"System.Security.Cryptography.MD5"
|
|
||||||
,"SHA"
|
|
||||||
,"SHA1"
|
|
||||||
,"System.Security.Cryptography.SHA1"
|
|
||||||
,"SHA256"
|
|
||||||
,"SHA-256"
|
|
||||||
,"System.Security.Cryptography.SHA256"
|
|
||||||
,"SHA384"
|
|
||||||
,"SHA-384"
|
|
||||||
,"System.Security.Cryptography.SHA384"
|
|
||||||
,"SHA512"
|
|
||||||
,"SHA-512"
|
|
||||||
,"System.Security.Cryptography.SHA512"
|
|
||||||
};
|
|
||||||
rng = RandomNumberGenerator.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Guid GetMD5(string str)
|
|
||||||
{
|
|
||||||
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ComputeSHA1(byte[] bytes)
|
|
||||||
{
|
|
||||||
using (var provider = SHA1.Create())
|
|
||||||
{
|
|
||||||
return provider.ComputeHash(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ComputeMD5(Stream str)
|
|
||||||
{
|
|
||||||
using (var provider = MD5.Create())
|
|
||||||
{
|
|
||||||
return provider.ComputeHash(str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ComputeMD5(byte[] bytes)
|
|
||||||
{
|
|
||||||
using (var provider = MD5.Create())
|
|
||||||
{
|
|
||||||
return provider.ComputeHash(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<string> GetSupportedHashMethods()
|
|
||||||
{
|
|
||||||
return SupportedHashMethods;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
|
|
||||||
{
|
{
|
||||||
//downgrading for now as we need this library to be dotnetstandard compliant
|
//Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
|
||||||
using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
|
//there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
|
||||||
{
|
SupportedHashMethods = new HashSet<string>()
|
||||||
return r.GetBytes(32);
|
{
|
||||||
}
|
"MD5"
|
||||||
}
|
,"System.Security.Cryptography.MD5"
|
||||||
|
,"SHA"
|
||||||
public byte[] ComputeHash(string HashMethod, byte[] bytes)
|
,"SHA1"
|
||||||
{
|
,"System.Security.Cryptography.SHA1"
|
||||||
return ComputeHash(HashMethod, bytes, new byte[0]);
|
,"SHA256"
|
||||||
}
|
,"SHA-256"
|
||||||
|
,"System.Security.Cryptography.SHA256"
|
||||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
|
,"SHA384"
|
||||||
{
|
,"SHA-384"
|
||||||
return ComputeHash(DefaultHashMethod, bytes);
|
,"System.Security.Cryptography.SHA384"
|
||||||
}
|
,"SHA512"
|
||||||
|
,"SHA-512"
|
||||||
public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt)
|
,"System.Security.Cryptography.SHA512"
|
||||||
{
|
};
|
||||||
if (SupportedHashMethods.Contains(HashMethod))
|
rng = RandomNumberGenerator.Create();
|
||||||
{
|
}
|
||||||
if (salt.Length == 0)
|
|
||||||
{
|
public Guid GetMD5(string str)
|
||||||
using (var h = HashAlgorithm.Create(HashMethod))
|
{
|
||||||
{
|
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
|
||||||
return h.ComputeHash(bytes);
|
}
|
||||||
}
|
|
||||||
}
|
public byte[] ComputeSHA1(byte[] bytes)
|
||||||
else
|
{
|
||||||
{
|
using (var provider = SHA1.Create())
|
||||||
return PBKDF2(HashMethod, bytes, salt, defaultiterations);
|
{
|
||||||
}
|
return provider.ComputeHash(bytes);
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
|
||||||
throw new CryptographicException($"Requested hash method is not supported: {HashMethod}");
|
public byte[] ComputeMD5(Stream str)
|
||||||
}
|
{
|
||||||
}
|
using (var provider = MD5.Create())
|
||||||
|
{
|
||||||
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
|
return provider.ComputeHash(str);
|
||||||
{
|
}
|
||||||
return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations);
|
}
|
||||||
}
|
|
||||||
|
public byte[] ComputeMD5(byte[] bytes)
|
||||||
public byte[] ComputeHash(PasswordHash hash)
|
{
|
||||||
{
|
using (var provider = MD5.Create())
|
||||||
int iterations = defaultiterations;
|
{
|
||||||
if (!hash.Parameters.ContainsKey("iterations"))
|
return provider.ComputeHash(bytes);
|
||||||
{
|
}
|
||||||
hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture));
|
}
|
||||||
}
|
|
||||||
else
|
public IEnumerable<string> GetSupportedHashMethods()
|
||||||
{
|
{
|
||||||
try
|
return SupportedHashMethods;
|
||||||
{
|
}
|
||||||
iterations = int.Parse(hash.Parameters["iterations"]);
|
|
||||||
}
|
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
|
||||||
catch (Exception e)
|
{
|
||||||
{
|
//downgrading for now as we need this library to be dotnetstandard compliant
|
||||||
throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
|
using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
|
||||||
}
|
{
|
||||||
}
|
return r.GetBytes(32);
|
||||||
return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GenerateSalt()
|
public byte[] ComputeHash(string HashMethod, byte[] bytes)
|
||||||
{
|
{
|
||||||
byte[] salt = new byte[64];
|
return ComputeHash(HashMethod, bytes, new byte[0]);
|
||||||
rng.GetBytes(salt);
|
}
|
||||||
return salt;
|
|
||||||
}
|
public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
|
||||||
}
|
{
|
||||||
}
|
return ComputeHash(DefaultHashMethod, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt)
|
||||||
|
{
|
||||||
|
if (SupportedHashMethods.Contains(HashMethod))
|
||||||
|
{
|
||||||
|
if (salt.Length == 0)
|
||||||
|
{
|
||||||
|
using (var h = HashAlgorithm.Create(HashMethod))
|
||||||
|
{
|
||||||
|
return h.ComputeHash(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return PBKDF2(HashMethod, bytes, salt, defaultiterations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new CryptographicException($"Requested hash method is not supported: {HashMethod}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
|
||||||
|
{
|
||||||
|
return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ComputeHash(PasswordHash hash)
|
||||||
|
{
|
||||||
|
int iterations = defaultiterations;
|
||||||
|
if (!hash.Parameters.ContainsKey("iterations"))
|
||||||
|
{
|
||||||
|
hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
iterations = int.Parse(hash.Parameters["iterations"]);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GenerateSalt()
|
||||||
|
{
|
||||||
|
byte[] salt = new byte[64];
|
||||||
|
rng.GetBytes(salt);
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,264 +1,264 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SQLitePCL.pretty;
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Data
|
namespace Emby.Server.Implementations.Data
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class SQLiteUserRepository
|
/// Class SQLiteUserRepository
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
|
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
|
||||||
{
|
{
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
|
|
||||||
public SqliteUserRepository(
|
public SqliteUserRepository(
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IJsonSerializer jsonSerializer)
|
IJsonSerializer jsonSerializer)
|
||||||
: base(loggerFactory.CreateLogger(nameof(SqliteUserRepository)))
|
: base(loggerFactory.CreateLogger(nameof(SqliteUserRepository)))
|
||||||
{
|
{
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
|
|
||||||
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
|
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the repository
|
/// Gets the name of the repository
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name.</value>
|
/// <value>The name.</value>
|
||||||
public string Name => "SQLite";
|
public string Name => "SQLite";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the connection to the database
|
/// Opens the connection to the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection())
|
using (var connection = CreateConnection())
|
||||||
{
|
{
|
||||||
RunDefaultInitialization(connection);
|
RunDefaultInitialization(connection);
|
||||||
|
|
||||||
var localUsersTableExists = TableExists(connection, "LocalUsersv2");
|
var localUsersTableExists = TableExists(connection, "LocalUsersv2");
|
||||||
|
|
||||||
connection.RunQueries(new[] {
|
connection.RunQueries(new[] {
|
||||||
"create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)",
|
"create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)",
|
||||||
"drop index if exists idx_users"
|
"drop index if exists idx_users"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!localUsersTableExists && TableExists(connection, "Users"))
|
if (!localUsersTableExists && TableExists(connection, "Users"))
|
||||||
{
|
{
|
||||||
TryMigrateToLocalUsersTable(connection);
|
TryMigrateToLocalUsersTable(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveEmptyPasswordHashes();
|
RemoveEmptyPasswordHashes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryMigrateToLocalUsersTable(ManagedConnection connection)
|
private void TryMigrateToLocalUsersTable(ManagedConnection connection)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
connection.RunQueries(new[]
|
connection.RunQueries(new[]
|
||||||
{
|
{
|
||||||
"INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users"
|
"INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error migrating users database");
|
Logger.LogError(ex, "Error migrating users database");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveEmptyPasswordHashes()
|
private void RemoveEmptyPasswordHashes()
|
||||||
{
|
{
|
||||||
foreach (var user in RetrieveAllUsers())
|
foreach (var user in RetrieveAllUsers())
|
||||||
{
|
{
|
||||||
// If the user password is the sha1 hash of the empty string, remove it
|
// If the user password is the sha1 hash of the empty string, remove it
|
||||||
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
|
if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
|
||||||
|| !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
|
|| !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Password = null;
|
user.Password = null;
|
||||||
var serialized = _jsonSerializer.SerializeToBytes(user);
|
var serialized = _jsonSerializer.SerializeToBytes(user);
|
||||||
|
|
||||||
using (WriteLock.Write())
|
using (WriteLock.Write())
|
||||||
using (var connection = CreateConnection())
|
using (var connection = CreateConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
||||||
{
|
{
|
||||||
statement.TryBind("@InternalId", user.InternalId);
|
statement.TryBind("@InternalId", user.InternalId);
|
||||||
statement.TryBind("@data", serialized);
|
statement.TryBind("@data", serialized);
|
||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
}, TransactionMode);
|
}, TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save a user in the repo
|
/// Save a user in the repo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CreateUser(User user)
|
public void CreateUser(User user)
|
||||||
{
|
{
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(user));
|
throw new ArgumentNullException(nameof(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
var serialized = _jsonSerializer.SerializeToBytes(user);
|
var serialized = _jsonSerializer.SerializeToBytes(user);
|
||||||
|
|
||||||
using (WriteLock.Write())
|
using (WriteLock.Write())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection())
|
using (var connection = CreateConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
|
using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)"))
|
||||||
{
|
{
|
||||||
statement.TryBind("@guid", user.Id.ToGuidBlob());
|
statement.TryBind("@guid", user.Id.ToGuidBlob());
|
||||||
statement.TryBind("@data", serialized);
|
statement.TryBind("@data", serialized);
|
||||||
|
|
||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
var createdUser = GetUser(user.Id, false);
|
var createdUser = GetUser(user.Id, false);
|
||||||
|
|
||||||
if (createdUser == null)
|
if (createdUser == null)
|
||||||
{
|
{
|
||||||
throw new ApplicationException("created user should never be null");
|
throw new ApplicationException("created user should never be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.InternalId = createdUser.InternalId;
|
user.InternalId = createdUser.InternalId;
|
||||||
|
|
||||||
}, TransactionMode);
|
}, TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateUser(User user)
|
public void UpdateUser(User user)
|
||||||
{
|
{
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(user));
|
throw new ArgumentNullException(nameof(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
var serialized = _jsonSerializer.SerializeToBytes(user);
|
var serialized = _jsonSerializer.SerializeToBytes(user);
|
||||||
|
|
||||||
using (WriteLock.Write())
|
using (WriteLock.Write())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection())
|
using (var connection = CreateConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
|
||||||
{
|
{
|
||||||
statement.TryBind("@InternalId", user.InternalId);
|
statement.TryBind("@InternalId", user.InternalId);
|
||||||
statement.TryBind("@data", serialized);
|
statement.TryBind("@data", serialized);
|
||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
}, TransactionMode);
|
}, TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private User GetUser(Guid guid, bool openLock)
|
private User GetUser(Guid guid, bool openLock)
|
||||||
{
|
{
|
||||||
using (openLock ? WriteLock.Read() : null)
|
using (openLock ? WriteLock.Read() : null)
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
{
|
{
|
||||||
using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
|
using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid"))
|
||||||
{
|
{
|
||||||
statement.TryBind("@guid", guid);
|
statement.TryBind("@guid", guid);
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
foreach (var row in statement.ExecuteQuery())
|
||||||
{
|
{
|
||||||
return GetUser(row);
|
return GetUser(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private User GetUser(IReadOnlyList<IResultSetValue> row)
|
private User GetUser(IReadOnlyList<IResultSetValue> row)
|
||||||
{
|
{
|
||||||
var id = row[0].ToInt64();
|
var id = row[0].ToInt64();
|
||||||
var guid = row[1].ReadGuidFromBlob();
|
var guid = row[1].ReadGuidFromBlob();
|
||||||
|
|
||||||
using (var stream = new MemoryStream(row[2].ToBlob()))
|
using (var stream = new MemoryStream(row[2].ToBlob()))
|
||||||
{
|
{
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
var user = _jsonSerializer.DeserializeFromStream<User>(stream);
|
var user = _jsonSerializer.DeserializeFromStream<User>(stream);
|
||||||
user.InternalId = id;
|
user.InternalId = id;
|
||||||
user.Id = guid;
|
user.Id = guid;
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve all users from the database
|
/// Retrieve all users from the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>IEnumerable{User}.</returns>
|
/// <returns>IEnumerable{User}.</returns>
|
||||||
public List<User> RetrieveAllUsers()
|
public List<User> RetrieveAllUsers()
|
||||||
{
|
{
|
||||||
var list = new List<User>();
|
var list = new List<User>();
|
||||||
|
|
||||||
using (WriteLock.Read())
|
using (WriteLock.Read())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(true))
|
using (var connection = CreateConnection(true))
|
||||||
{
|
{
|
||||||
foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
|
foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
|
||||||
{
|
{
|
||||||
list.Add(GetUser(row));
|
list.Add(GetUser(row));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the user.
|
/// Deletes the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The user.</param>
|
/// <param name="user">The user.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
/// <exception cref="ArgumentNullException">user</exception>
|
/// <exception cref="ArgumentNullException">user</exception>
|
||||||
public void DeleteUser(User user)
|
public void DeleteUser(User user)
|
||||||
{
|
{
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(user));
|
throw new ArgumentNullException(nameof(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (WriteLock.Write())
|
using (WriteLock.Write())
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection())
|
using (var connection = CreateConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(db =>
|
connection.RunInTransaction(db =>
|
||||||
{
|
{
|
||||||
using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
|
using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id"))
|
||||||
{
|
{
|
||||||
statement.TryBind("@id", user.InternalId);
|
statement.TryBind("@id", user.InternalId);
|
||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
}, TransactionMode);
|
}, TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,42 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Authentication;
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
|
public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser
|
||||||
{
|
{
|
||||||
private readonly ICryptoProvider _cryptographyProvider;
|
private readonly ICryptoProvider _cryptographyProvider;
|
||||||
public DefaultAuthenticationProvider(ICryptoProvider crypto)
|
public DefaultAuthenticationProvider(ICryptoProvider crypto)
|
||||||
{
|
{
|
||||||
_cryptographyProvider = crypto;
|
_cryptographyProvider = crypto;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Default";
|
public string Name => "Default";
|
||||||
|
|
||||||
public bool IsEnabled => true;
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
|
|
||||||
//This is dumb and an artifact of the backwards way auth providers were designed.
|
//This is dumb and an artifact of the backwards way auth providers were designed.
|
||||||
//This version of authenticate was never meant to be called, but needs to be here for interface compat
|
//This version of authenticate was never meant to be called, but needs to be here for interface compat
|
||||||
//Only the providers that don't provide local user support use this
|
//Only the providers that don't provide local user support use this
|
||||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//This is the verson that we need to use for local users. Because reasons.
|
//This is the verson that we need to use for local users. Because reasons.
|
||||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
|
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
if (resolvedUser == null)
|
if (resolvedUser == null)
|
||||||
{
|
{
|
||||||
throw new Exception("Invalid username or password");
|
throw new Exception("Invalid username or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
//As long as jellyfin supports passwordless users, we need this little block here to accomodate
|
//As long as jellyfin supports passwordless users, we need this little block here to accomodate
|
||||||
@ -47,166 +47,164 @@ namespace Emby.Server.Implementations.Library
|
|||||||
Username = username
|
Username = username
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ConvertPasswordFormat(resolvedUser);
|
ConvertPasswordFormat(resolvedUser);
|
||||||
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
|
byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
|
||||||
|
|
||||||
PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
|
PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
|
||||||
byte[] CalculatedHash;
|
byte[] CalculatedHash;
|
||||||
string CalculatedHashString;
|
string CalculatedHashString;
|
||||||
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id))
|
if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(readyHash.Salt))
|
if (string.IsNullOrEmpty(readyHash.Salt))
|
||||||
{
|
{
|
||||||
CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes);
|
CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes);
|
||||||
CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty);
|
CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes);
|
|
||||||
CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (CalculatedHashString == readyHash.Hash)
|
{
|
||||||
{
|
CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes);
|
||||||
success = true;
|
CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty);
|
||||||
//throw new Exception("Invalid username or password");
|
}
|
||||||
}
|
|
||||||
}
|
if (CalculatedHashString == readyHash.Hash)
|
||||||
else
|
{
|
||||||
{
|
success = true;
|
||||||
throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}"));
|
//throw new Exception("Invalid username or password");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
|
else
|
||||||
|
{
|
||||||
if (!success)
|
throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}"));
|
||||||
{
|
}
|
||||||
throw new Exception("Invalid username or password");
|
|
||||||
}
|
//var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
return Task.FromResult(new ProviderAuthenticationResult
|
if (!success)
|
||||||
{
|
{
|
||||||
Username = username
|
throw new Exception("Invalid username or password");
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
return Task.FromResult(new ProviderAuthenticationResult
|
||||||
//This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change
|
{
|
||||||
//but at least they are in the new format.
|
Username = username
|
||||||
private void ConvertPasswordFormat(User user)
|
});
|
||||||
{
|
}
|
||||||
if (!string.IsNullOrEmpty(user.Password))
|
|
||||||
|
//This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change
|
||||||
|
//but at least they are in the new format.
|
||||||
|
private void ConvertPasswordFormat(User user)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(user.Password))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Password.Contains("$"))
|
if (!user.Password.Contains("$"))
|
||||||
{
|
{
|
||||||
string hash = user.Password;
|
string hash = user.Password;
|
||||||
user.Password = String.Format("$SHA1${0}", hash);
|
user.Password = String.Format("$SHA1${0}", hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
|
if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
|
||||||
{
|
{
|
||||||
string hash = user.EasyPassword;
|
string hash = user.EasyPassword;
|
||||||
user.EasyPassword = string.Format("$SHA1${0}", hash);
|
user.EasyPassword = string.Format("$SHA1${0}", hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> HasPassword(User user)
|
public Task<bool> HasPassword(User user)
|
||||||
{
|
{
|
||||||
var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));
|
var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));
|
||||||
return Task.FromResult(hasConfiguredPassword);
|
return Task.FromResult(hasConfiguredPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsPasswordEmpty(User user, string password)
|
private bool IsPasswordEmpty(User user, string password)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(user.Password))
|
if (string.IsNullOrEmpty(user.Password))
|
||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(password);
|
return string.IsNullOrEmpty(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ChangePassword(User user, string newPassword)
|
public Task ChangePassword(User user, string newPassword)
|
||||||
{
|
{
|
||||||
ConvertPasswordFormat(user);
|
ConvertPasswordFormat(user);
|
||||||
//This is needed to support changing a no password user to a password user
|
//This is needed to support changing a no password user to a password user
|
||||||
if (string.IsNullOrEmpty(user.Password))
|
if (string.IsNullOrEmpty(user.Password))
|
||||||
{
|
{
|
||||||
PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider);
|
PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider);
|
||||||
newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
|
newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
|
||||||
newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes);
|
newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes);
|
||||||
newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
||||||
newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash);
|
newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash);
|
||||||
user.Password = newPasswordHash.ToString();
|
user.Password = newPasswordHash.ToString();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
PasswordHash passwordHash = new PasswordHash(user.Password);
|
PasswordHash passwordHash = new PasswordHash(user.Password);
|
||||||
if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt))
|
if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt))
|
||||||
{
|
{
|
||||||
passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
|
passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
|
||||||
passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes);
|
passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes);
|
||||||
passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
|
||||||
passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash);
|
passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash);
|
||||||
}
|
|
||||||
else if (newPassword != null)
|
|
||||||
{
|
|
||||||
passwordHash.Hash = GetHashedString(user, newPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(passwordHash.Hash))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(passwordHash.Hash));
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Password = passwordHash.ToString();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetPasswordHash(User user)
|
|
||||||
{
|
|
||||||
return user.Password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
|
|
||||||
{
|
|
||||||
passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
|
|
||||||
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the hashed string.
|
|
||||||
/// </summary>
|
|
||||||
public string GetHashedString(User user, string str)
|
|
||||||
{
|
|
||||||
PasswordHash passwordHash;
|
|
||||||
if (String.IsNullOrEmpty(user.Password))
|
|
||||||
{
|
|
||||||
passwordHash = new PasswordHash(_cryptographyProvider);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ConvertPasswordFormat(user);
|
|
||||||
passwordHash = new PasswordHash(user.Password);
|
|
||||||
}
|
}
|
||||||
|
else if (newPassword != null)
|
||||||
if (passwordHash.SaltBytes != null)
|
{
|
||||||
{
|
passwordHash.Hash = GetHashedString(user, newPassword);
|
||||||
//the password is modern format with PBKDF and we should take advantage of that
|
}
|
||||||
passwordHash.HashBytes = Encoding.UTF8.GetBytes(str);
|
|
||||||
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
|
if (string.IsNullOrWhiteSpace(passwordHash.Hash))
|
||||||
}
|
{
|
||||||
else
|
throw new ArgumentNullException(nameof(passwordHash.Hash));
|
||||||
{
|
}
|
||||||
//the password has no salt and should be called with the older method for safety
|
|
||||||
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)));
|
user.Password = passwordHash.ToString();
|
||||||
}
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
|
||||||
}
|
public string GetPasswordHash(User user)
|
||||||
}
|
{
|
||||||
|
return user.Password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
|
||||||
|
{
|
||||||
|
passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
|
||||||
|
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the hashed string.
|
||||||
|
/// </summary>
|
||||||
|
public string GetHashedString(User user, string str)
|
||||||
|
{
|
||||||
|
PasswordHash passwordHash;
|
||||||
|
if (String.IsNullOrEmpty(user.Password))
|
||||||
|
{
|
||||||
|
passwordHash = new PasswordHash(_cryptographyProvider);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConvertPasswordFormat(user);
|
||||||
|
passwordHash = new PasswordHash(user.Password);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordHash.SaltBytes != null)
|
||||||
|
{
|
||||||
|
//the password is modern format with PBKDF and we should take advantage of that
|
||||||
|
passwordHash.HashBytes = Encoding.UTF8.GetBytes(str);
|
||||||
|
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//the password has no salt and should be called with the older method for safety
|
||||||
|
return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user