247 lines
6.5 KiB
C#
247 lines
6.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MediaBrowser.Common;
|
|
using MediaBrowser.Common.Configuration;
|
|
using MediaBrowser.Common.Extensions;
|
|
using MediaBrowser.Common.Net;
|
|
using MediaBrowser.Common.Security;
|
|
using MediaBrowser.Controller;
|
|
using MediaBrowser.Controller.Plugins;
|
|
using MediaBrowser.Controller.Security;
|
|
using MediaBrowser.Model.Cryptography;
|
|
using MediaBrowser.Model.IO;
|
|
using MediaBrowser.Model.Logging;
|
|
using MediaBrowser.Model.Serialization;
|
|
|
|
namespace Emby.Security;
|
|
|
|
public class PluginSecurityManager : IServerEntryPoint, IDisposable
|
|
{
|
|
private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate";
|
|
|
|
private readonly IHttpClient _httpClient;
|
|
|
|
private readonly IJsonSerializer _jsonSerializer;
|
|
|
|
private readonly IServerApplicationHost _appHost;
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
private readonly IApplicationPaths _appPaths;
|
|
|
|
private readonly IFileSystem _fileSystem;
|
|
|
|
private readonly ICryptoProvider _cryptographyProvider;
|
|
|
|
private readonly ISecurityManager _securityManager;
|
|
|
|
private readonly IEncryptionManager _encryption;
|
|
|
|
public static PluginSecurityManager Current;
|
|
|
|
private readonly Dictionary<string, RegRecord> _regRecordCache = new Dictionary<string, RegRecord>();
|
|
|
|
private SemaphoreSlim _regLock = new SemaphoreSlim(1, 1);
|
|
|
|
public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, ISecurityManager securityManager, IEncryptionManager encryption)
|
|
{
|
|
if (httpClient == null)
|
|
{
|
|
throw new ArgumentNullException("httpClient");
|
|
}
|
|
_appHost = appHost;
|
|
_httpClient = httpClient;
|
|
_jsonSerializer = jsonSerializer;
|
|
_appPaths = appPaths;
|
|
_fileSystem = fileSystem;
|
|
_cryptographyProvider = cryptographyProvider;
|
|
_securityManager = securityManager;
|
|
_encryption = encryption;
|
|
_logger = logManager.GetLogger("Plugin");
|
|
Current = this;
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
private string GetCacheFilePath(string feature)
|
|
{
|
|
string text = GetType().FullName + GetCacheKey(feature);
|
|
text = BaseExtensions.GetMD5(text).ToString("N");
|
|
return Path.Combine(_appPaths.PluginConfigurationsPath, text);
|
|
}
|
|
|
|
private RegRecord GetCachedRegRecord(string feature)
|
|
{
|
|
lock (_regRecordCache)
|
|
{
|
|
if (_regRecordCache.TryGetValue(GetCacheKey(feature), out var value))
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
string cacheFilePath = GetCacheFilePath(feature);
|
|
try
|
|
{
|
|
string text = _encryption.DecryptString(_fileSystem.ReadAllText(cacheFilePath));
|
|
RegRecord value = _jsonSerializer.DeserializeFromString<RegRecord>(text);
|
|
if (value != null)
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private string GetCacheKey(string feature)
|
|
{
|
|
string text = _securityManager.SupporterKey;
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
{
|
|
text = "none";
|
|
}
|
|
return text + "-" + feature;
|
|
}
|
|
|
|
private void DeleteCachedRecord(string feature)
|
|
{
|
|
lock (_regRecordCache)
|
|
{
|
|
_regRecordCache.Remove(GetCacheKey(feature));
|
|
}
|
|
try
|
|
{
|
|
_fileSystem.DeleteFile(GetCacheFilePath(feature));
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private void SaveCachedRecord(string feature, RegRecord record)
|
|
{
|
|
lock (_regRecordCache)
|
|
{
|
|
_regRecordCache[GetCacheKey(feature)] = record;
|
|
}
|
|
string cacheFilePath = GetCacheFilePath(feature);
|
|
string text = _jsonSerializer.SerializeToString((object)record);
|
|
string text2 = _encryption.EncryptString(text);
|
|
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath));
|
|
_fileSystem.WriteAllText(cacheFilePath, text2);
|
|
}
|
|
|
|
public async Task<RegRecord> GetRegistrationStatus(string feature, CancellationToken cancellationToken)
|
|
{
|
|
await _regLock.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
|
|
try
|
|
{
|
|
return await GetRegistrationStatusInternal(feature, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
|
|
}
|
|
finally
|
|
{
|
|
_regLock.Release();
|
|
}
|
|
}
|
|
|
|
private bool CheckRegistrationWithServer(DateTime lastChecked, bool registered)
|
|
{
|
|
if (registered || string.IsNullOrWhiteSpace(_securityManager.SupporterKey))
|
|
{
|
|
if (DateTime.UtcNow.AddDays(-1.0) > lastChecked)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (DateTime.UtcNow.AddHours(-1.0) > lastChecked)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private async Task<RegRecord> GetRegistrationStatusInternal(string feature, CancellationToken cancellationToken)
|
|
{
|
|
RegRecord regInfo = GetCachedRegRecord(feature);
|
|
DateTime lastChecked = regInfo?.lastChecked ?? DateTime.MinValue;
|
|
DateTime expDate = regInfo?.expDate ?? DateTime.MinValue;
|
|
bool registered = regInfo?.registered ?? false;
|
|
string supporterKey = _securityManager.SupporterKey;
|
|
if (CheckRegistrationWithServer(lastChecked, registered))
|
|
{
|
|
Dictionary<string, string> postData = new Dictionary<string, string>
|
|
{
|
|
{ "feature", feature },
|
|
{ "key", supporterKey },
|
|
{
|
|
"mac",
|
|
((IApplicationHost)_appHost).SystemId
|
|
},
|
|
{
|
|
"systemid",
|
|
((IApplicationHost)_appHost).SystemId
|
|
},
|
|
{
|
|
"platform",
|
|
((IApplicationHost)_appHost).OperatingSystemDisplayName
|
|
}
|
|
};
|
|
try
|
|
{
|
|
HttpRequestOptions val = new HttpRequestOptions
|
|
{
|
|
Url = "https://mb3admin.com/admin/service/registration/validate",
|
|
EnableHttpCompression = false,
|
|
BufferContent = false,
|
|
CancellationToken = cancellationToken
|
|
};
|
|
val.SetPostData((IDictionary<string, string>)postData);
|
|
HttpResponseInfo val2 = await _httpClient.SendAsync(val, "POST").ConfigureAwait(continueOnCapturedContext: false);
|
|
try
|
|
{
|
|
using Stream stream = val2.Content;
|
|
regInfo = _jsonSerializer.DeserializeFromStream<RegRecord>(stream);
|
|
regInfo.lastChecked = DateTime.UtcNow;
|
|
SaveCachedRecord(feature, regInfo);
|
|
}
|
|
finally
|
|
{
|
|
((IDisposable)val2)?.Dispose();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.ErrorException("Error checking registration status of {0}", ex, new object[1] { feature });
|
|
int num = 14;
|
|
DateTime dateTime = new DateTime[2]
|
|
{
|
|
expDate,
|
|
lastChecked.AddDays(num)
|
|
}.Min();
|
|
if (dateTime > DateTime.UtcNow.AddDays(num))
|
|
{
|
|
dateTime = DateTime.MinValue;
|
|
}
|
|
regInfo = new RegRecord
|
|
{
|
|
registered = (regInfo != null && regInfo.registered && dateTime >= DateTime.UtcNow && expDate >= DateTime.UtcNow),
|
|
expDate = expDate
|
|
};
|
|
}
|
|
}
|
|
return regInfo;
|
|
}
|
|
}
|