From 6d250c4050ceb0bda33aad6a484fd05e508c4e27 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 4 Nov 2016 04:31:05 -0400 Subject: make dlna project portable --- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 ++++ MediaBrowser.Model/Net/ISocketFactory.cs | 26 +++++++++++++++++++++++++ MediaBrowser.Model/Net/IUdpSocket.cs | 27 ++++++++++++++++++++++++++ MediaBrowser.Model/Net/IpEndPointInfo.cs | 18 +++++++++++++++++ MediaBrowser.Model/Net/ReceivedUdpData.cs | 24 +++++++++++++++++++++++ MediaBrowser.Model/Reflection/IAssemblyInfo.cs | 1 + 6 files changed, 100 insertions(+) create mode 100644 MediaBrowser.Model/Net/ISocketFactory.cs create mode 100644 MediaBrowser.Model/Net/IUdpSocket.cs create mode 100644 MediaBrowser.Model/Net/IpEndPointInfo.cs create mode 100644 MediaBrowser.Model/Net/ReceivedUdpData.cs (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 5999f02db2..03bbafe60a 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -137,6 +137,10 @@ + + + + diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs new file mode 100644 index 0000000000..c0e0440c25 --- /dev/null +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -0,0 +1,26 @@ + +namespace MediaBrowser.Model.Net +{ + /// + /// Implemented by components that can create a platform specific UDP socket implementation, and wrap it in the cross platform interface. + /// + public interface ISocketFactory + { + + /// + /// Createa a new unicast socket using the specified local port number. + /// + /// The local port to bind to. + /// A implementation. + IUdpSocket CreateUdpSocket(int localPort); + + /// + /// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port. + /// + /// The multicast IP address to bind to. + /// The multicast time to live value. Actually a maximum number of network hops for UDP packets. + /// The local port to bind to. + /// A implementation. + IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort); + } +} diff --git a/MediaBrowser.Model/Net/IUdpSocket.cs b/MediaBrowser.Model/Net/IUdpSocket.cs new file mode 100644 index 0000000000..cbeb8a995f --- /dev/null +++ b/MediaBrowser.Model/Net/IUdpSocket.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Model.Net +{ + /// + /// Provides a common interface across platforms for UDP sockets used by this SSDP implementation. + /// + public interface IUdpSocket : IDisposable + { + /// + /// Waits for and returns the next UDP message sent to this socket (uni or multicast). + /// + /// + Task ReceiveAsync(); + + /// + /// Sends a UDP message to a particular end point (uni or multicast). + /// + /// The data to send. + /// The providing the address and port to send to. + Task SendTo(byte[] messageData, IpEndPointInfo endPoint); + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Net/IpEndPointInfo.cs b/MediaBrowser.Model/Net/IpEndPointInfo.cs new file mode 100644 index 0000000000..5fd331a166 --- /dev/null +++ b/MediaBrowser.Model/Net/IpEndPointInfo.cs @@ -0,0 +1,18 @@ +using System; + +namespace MediaBrowser.Model.Net +{ + public class IpEndPointInfo + { + public IpAddressInfo IpAddress { get; set; } + + public int Port { get; set; } + + public override string ToString() + { + var ipAddresString = IpAddress == null ? string.Empty : IpAddress.ToString(); + + return ipAddresString + ":" + this.Port.ToString(); + } + } +} diff --git a/MediaBrowser.Model/Net/ReceivedUdpData.cs b/MediaBrowser.Model/Net/ReceivedUdpData.cs new file mode 100644 index 0000000000..1fdb22c930 --- /dev/null +++ b/MediaBrowser.Model/Net/ReceivedUdpData.cs @@ -0,0 +1,24 @@ + +namespace MediaBrowser.Model.Net +{ + /// + /// Used by the sockets wrapper to hold raw data received from a UDP socket. + /// + public sealed class ReceivedUdpData + { + /// + /// The buffer to place received data into. + /// + public byte[] Buffer { get; set; } + + /// + /// The number of bytes received. + /// + public int ReceivedBytes { get; set; } + + /// + /// The the data was received from. + /// + public IpEndPointInfo ReceivedFrom { get; set; } + } +} diff --git a/MediaBrowser.Model/Reflection/IAssemblyInfo.cs b/MediaBrowser.Model/Reflection/IAssemblyInfo.cs index 1c65985cb2..634fadc1b4 100644 --- a/MediaBrowser.Model/Reflection/IAssemblyInfo.cs +++ b/MediaBrowser.Model/Reflection/IAssemblyInfo.cs @@ -6,5 +6,6 @@ namespace MediaBrowser.Model.Reflection public interface IAssemblyInfo { Stream GetManifestResourceStream(Type type, string resource); + string[] GetManifestResourceNames(Type type); } } -- cgit v1.2.3 From 67ffbed93e1e4c5d33ed5e12d5d5aaa587261493 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 4 Nov 2016 04:43:59 -0400 Subject: move classes --- .../IO/ManagedFileSystem.cs | 10 + .../Emby.Server.Implementations.csproj | 3 + .../Security/MBLicenseFile.cs | 160 ++++++++++ .../Security/PluginSecurityManager.cs | 344 +++++++++++++++++++++ Emby.Server.Implementations/Security/RegRecord.cs | 12 + MediaBrowser.Model/IO/IFileSystem.cs | 4 + .../MediaBrowser.Server.Implementations.csproj | 3 - .../Security/MBLicenseFile.cs | 157 ---------- .../Security/PluginSecurityManager.cs | 342 -------------------- .../Security/RegRecord.cs | 12 - .../ApplicationHost.cs | 3 +- 11 files changed, 535 insertions(+), 515 deletions(-) create mode 100644 Emby.Server.Implementations/Security/MBLicenseFile.cs create mode 100644 Emby.Server.Implementations/Security/PluginSecurityManager.cs create mode 100644 Emby.Server.Implementations/Security/RegRecord.cs delete mode 100644 MediaBrowser.Server.Implementations/Security/MBLicenseFile.cs delete mode 100644 MediaBrowser.Server.Implementations/Security/PluginSecurityManager.cs delete mode 100644 MediaBrowser.Server.Implementations/Security/RegRecord.cs (limited to 'MediaBrowser.Model') diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index a8aa1a3cdf..5b965efdc7 100644 --- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs @@ -641,6 +641,16 @@ namespace Emby.Common.Implementations.IO }).Where(i => i != null); } + public string[] ReadAllLines(string path) + { + return File.ReadAllLines(path); + } + + public void WriteAllLines(string path, IEnumerable lines) + { + File.WriteAllLines(path, lines); + } + public Stream OpenRead(string path) { return File.OpenRead(path); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 5d27e84dd1..4a27ddb745 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -159,6 +159,9 @@ + + + diff --git a/Emby.Server.Implementations/Security/MBLicenseFile.cs b/Emby.Server.Implementations/Security/MBLicenseFile.cs new file mode 100644 index 0000000000..454ee6026b --- /dev/null +++ b/Emby.Server.Implementations/Security/MBLicenseFile.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.Security +{ + internal class MBLicenseFile + { + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + private readonly ICryptographyProvider _cryptographyProvider; + + public string RegKey + { + get { return _regKey; } + set + { + if (value != _regKey) + { + //if key is changed - clear out our saved validations + _updateRecords.Clear(); + _regKey = value; + } + } + } + + private string Filename + { + get + { + return Path.Combine(_appPaths.ConfigurationDirectoryPath, "mb.lic"); + } + } + + private readonly ConcurrentDictionary _updateRecords = new ConcurrentDictionary(); + private readonly object _fileLock = new object(); + private string _regKey; + + public MBLicenseFile(IApplicationPaths appPaths, IFileSystem fileSystem, ICryptographyProvider cryptographyProvider) + { + _appPaths = appPaths; + _fileSystem = fileSystem; + _cryptographyProvider = cryptographyProvider; + + Load(); + } + + private void SetUpdateRecord(Guid key, DateTime value) + { + _updateRecords.AddOrUpdate(key, value, (k, v) => value); + } + + public void AddRegCheck(string featureId) + { + var key = new Guid(_cryptographyProvider.GetMD5Bytes(Encoding.Unicode.GetBytes(featureId))); + var value = DateTime.UtcNow; + + SetUpdateRecord(key, value); + Save(); + } + + public void RemoveRegCheck(string featureId) + { + var key = new Guid(_cryptographyProvider.GetMD5Bytes(Encoding.Unicode.GetBytes(featureId))); + DateTime val; + + _updateRecords.TryRemove(key, out val); + + Save(); + } + + public DateTime LastChecked(string featureId) + { + DateTime last; + _updateRecords.TryGetValue(new Guid(_cryptographyProvider.GetMD5Bytes(Encoding.Unicode.GetBytes(featureId))), out last); + + // guard agains people just putting a large number in the file + return last < DateTime.UtcNow ? last : DateTime.MinValue; + } + + private void Load() + { + string[] contents = null; + var licenseFile = Filename; + lock (_fileLock) + { + try + { + contents = _fileSystem.ReadAllLines(licenseFile); + } + catch (FileNotFoundException) + { + lock (_fileLock) + { + _fileSystem.WriteAllBytes(licenseFile, new byte[] {}); + } + } + catch (IOException) + { + lock (_fileLock) + { + _fileSystem.WriteAllBytes(licenseFile, new byte[] { }); + } + } + } + if (contents != null && contents.Length > 0) + { + //first line is reg key + RegKey = contents[0]; + + //next is legacy key + if (contents.Length > 1) + { + // Don't need this anymore + } + + //the rest of the lines should be pairs of features and timestamps + for (var i = 2; i < contents.Length; i = i + 2) + { + var feat = Guid.Parse(contents[i]); + + SetUpdateRecord(feat, new DateTime(Convert.ToInt64(contents[i + 1]))); + } + } + } + + public void Save() + { + //build our array + var lines = new List + { + RegKey, + + // Legacy key + string.Empty + }; + + foreach (var pair in _updateRecords + .ToList()) + { + lines.Add(pair.Key.ToString()); + lines.Add(pair.Value.Ticks.ToString(CultureInfo.InvariantCulture)); + } + + var licenseFile = Filename; + _fileSystem.CreateDirectory(Path.GetDirectoryName(licenseFile)); + lock (_fileLock) + { + _fileSystem.WriteAllLines(licenseFile, lines); + } + } + } +} diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs new file mode 100644 index 0000000000..c3a7e9450e --- /dev/null +++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Security; +using MediaBrowser.Controller; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Serialization; + +namespace Emby.Server.Implementations.Security +{ + /// + /// Class PluginSecurityManager + /// + public class PluginSecurityManager : ISecurityManager + { + private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate"; + private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "https://mb3admin.com/admin/service/appstore/register"; + + /// + /// The _is MB supporter + /// + private bool? _isMbSupporter; + /// + /// The _is MB supporter initialized + /// + private bool _isMbSupporterInitialized; + /// + /// The _is MB supporter sync lock + /// + private object _isMbSupporterSyncLock = new object(); + + /// + /// Gets a value indicating whether this instance is MB supporter. + /// + /// true if this instance is MB supporter; otherwise, false. + public bool IsMBSupporter + { + get + { + LazyInitializer.EnsureInitialized(ref _isMbSupporter, ref _isMbSupporterInitialized, ref _isMbSupporterSyncLock, () => GetSupporterRegistrationStatus().Result.IsRegistered); + return _isMbSupporter.Value; + } + } + + private MBLicenseFile _licenseFile; + private MBLicenseFile LicenseFile + { + get { return _licenseFile ?? (_licenseFile = new MBLicenseFile(_appPaths, _fileSystem, _cryptographyProvider)); } + } + + 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 ICryptographyProvider _cryptographyProvider; + + private IEnumerable _registeredEntities; + protected IEnumerable RegisteredEntities + { + get + { + return _registeredEntities ?? (_registeredEntities = _appHost.GetExports()); + } + } + + /// + /// Initializes a new instance of the class. + /// + public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, + IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem, ICryptographyProvider cryptographyProvider) + { + if (httpClient == null) + { + throw new ArgumentNullException("httpClient"); + } + + _appHost = appHost; + _httpClient = httpClient; + _jsonSerializer = jsonSerializer; + _appPaths = appPaths; + _fileSystem = fileSystem; + _cryptographyProvider = cryptographyProvider; + _logger = logManager.GetLogger("SecurityManager"); + } + + /// + /// Load all registration info for all entities that require registration + /// + /// + public async Task LoadAllRegistrationInfo() + { + var tasks = new List(); + + ResetSupporterInfo(); + tasks.AddRange(RegisteredEntities.Select(i => i.LoadRegistrationInfoAsync())); + await Task.WhenAll(tasks); + } + + /// + /// Gets the registration status. + /// This overload supports existing plug-ins. + /// + /// The feature. + /// The MB2 equivalent. + /// Task{MBRegistrationRecord}. + public Task GetRegistrationStatus(string feature, string mb2Equivalent = null) + { + return GetRegistrationStatusInternal(feature, mb2Equivalent); + } + + /// + /// Gets the registration status. + /// + /// The feature. + /// The MB2 equivalent. + /// The version of this feature + /// Task{MBRegistrationRecord}. + public Task GetRegistrationStatus(string feature, string mb2Equivalent, string version) + { + return GetRegistrationStatusInternal(feature, mb2Equivalent, version); + } + + private Task GetSupporterRegistrationStatus() + { + return GetRegistrationStatusInternal("MBSupporter", null, _appHost.ApplicationVersion.ToString()); + } + + /// + /// Gets or sets the supporter key. + /// + /// The supporter key. + public string SupporterKey + { + get + { + return LicenseFile.RegKey; + } + set + { + var newValue = value; + if (newValue != null) + { + newValue = newValue.Trim(); + } + + if (newValue != LicenseFile.RegKey) + { + LicenseFile.RegKey = newValue; + LicenseFile.Save(); + + // re-load registration info + Task.Run(() => LoadAllRegistrationInfo()); + } + } + } + + /// + /// Register an app store sale with our back-end. It will validate the transaction with the store + /// and then register the proper feature and then fill in the supporter key on success. + /// + /// Json parameters to send to admin server + public async Task RegisterAppStoreSale(string parameters) + { + var options = new HttpRequestOptions() + { + Url = AppstoreRegUrl, + CancellationToken = CancellationToken.None, + BufferContent = false + }; + options.RequestHeaders.Add("X-Emby-Token", _appHost.SystemId); + options.RequestContent = parameters; + options.RequestContentType = "application/json"; + + try + { + using (var response = await _httpClient.Post(options).ConfigureAwait(false)) + { + var reg = _jsonSerializer.DeserializeFromStream(response.Content); + + if (reg == null) + { + var msg = "Result from appstore registration was null."; + _logger.Error(msg); + throw new ArgumentException(msg); + } + if (!String.IsNullOrEmpty(reg.key)) + { + SupporterKey = reg.key; + } + } + + } + catch (ArgumentException) + { + SaveAppStoreInfo(parameters); + throw; + } + catch (HttpException e) + { + _logger.ErrorException("Error registering appstore purchase {0}", e, parameters ?? "NO PARMS SENT"); + + if (e.StatusCode.HasValue && e.StatusCode.Value == HttpStatusCode.PaymentRequired) + { + throw new PaymentRequiredException(); + } + throw new Exception("Error registering store sale"); + } + catch (Exception e) + { + _logger.ErrorException("Error registering appstore purchase {0}", e, parameters ?? "NO PARMS SENT"); + SaveAppStoreInfo(parameters); + //TODO - could create a re-try routine on start-up if this file is there. For now we can handle manually. + throw new Exception("Error registering store sale"); + } + + } + + private void SaveAppStoreInfo(string info) + { + // Save all transaction information to a file + + try + { + _fileSystem.WriteAllText(Path.Combine(_appPaths.ProgramDataPath, "apptrans-error.txt"), info); + } + catch (IOException) + { + + } + } + + private async Task GetRegistrationStatusInternal(string feature, + string mb2Equivalent = null, + string version = null) + { + var lastChecked = LicenseFile.LastChecked(feature); + + //check the reg file first to alleviate strain on the MB admin server - must actually check in every 30 days tho + var reg = new RegRecord + { + // Cache the result for up to a week + registered = lastChecked > DateTime.UtcNow.AddDays(-7) + }; + + var success = reg.registered; + + if (!(lastChecked > DateTime.UtcNow.AddDays(-1))) + { + var data = new Dictionary + { + { "feature", feature }, + { "key", SupporterKey }, + { "mac", _appHost.SystemId }, + { "systemid", _appHost.SystemId }, + { "mb2equiv", mb2Equivalent }, + { "ver", version }, + { "platform", _appHost.OperatingSystemDisplayName }, + { "isservice", _appHost.IsRunningAsService.ToString().ToLower() } + }; + + try + { + var options = new HttpRequestOptions + { + Url = MBValidateUrl, + + // Seeing block length errors + EnableHttpCompression = false, + BufferContent = false + }; + + options.SetPostData(data); + + using (var json = (await _httpClient.Post(options).ConfigureAwait(false)).Content) + { + reg = _jsonSerializer.DeserializeFromStream(json); + success = true; + } + + if (reg.registered) + { + LicenseFile.AddRegCheck(feature); + } + else + { + LicenseFile.RemoveRegCheck(feature); + } + + } + catch (Exception e) + { + _logger.ErrorException("Error checking registration status of {0}", e, feature); + } + } + + var record = new MBRegistrationRecord + { + IsRegistered = reg.registered, + ExpirationDate = reg.expDate, + RegChecked = true, + RegError = !success + }; + + record.TrialVersion = IsInTrial(reg.expDate, record.RegChecked, record.IsRegistered); + record.IsValid = !record.RegChecked || record.IsRegistered || record.TrialVersion; + + return record; + } + + private bool IsInTrial(DateTime expirationDate, bool regChecked, bool isRegistered) + { + //don't set this until we've successfully obtained exp date + if (!regChecked) + { + return false; + } + + var isInTrial = expirationDate > DateTime.UtcNow; + + return isInTrial && !isRegistered; + } + + /// + /// Resets the supporter info. + /// + private void ResetSupporterInfo() + { + _isMbSupporter = null; + _isMbSupporterInitialized = false; + } + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/Security/RegRecord.cs b/Emby.Server.Implementations/Security/RegRecord.cs new file mode 100644 index 0000000000..d484085d3f --- /dev/null +++ b/Emby.Server.Implementations/Security/RegRecord.cs @@ -0,0 +1,12 @@ +using System; + +namespace Emby.Server.Implementations.Security +{ + class RegRecord + { + public string featId { get; set; } + public bool registered { get; set; } + public DateTime expDate { get; set; } + public string key { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index 50e32572da..ca537752aa 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -276,6 +276,10 @@ namespace MediaBrowser.Model.IO /// System.String. string ReadAllText(string path, Encoding encoding); + string[] ReadAllLines(string path); + + void WriteAllLines(string path, IEnumerable lines); + /// /// Gets the directory paths. /// diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 9af765c23e..d6223c4655 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -171,9 +171,6 @@ - - - diff --git a/MediaBrowser.Server.Implementations/Security/MBLicenseFile.cs b/MediaBrowser.Server.Implementations/Security/MBLicenseFile.cs deleted file mode 100644 index 7b37925ba5..0000000000 --- a/MediaBrowser.Server.Implementations/Security/MBLicenseFile.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Common.Configuration; - -namespace MediaBrowser.Server.Implementations.Security -{ - internal class MBLicenseFile - { - private readonly IApplicationPaths _appPaths; - - public string RegKey - { - get { return _regKey; } - set - { - if (value != _regKey) - { - //if key is changed - clear out our saved validations - _updateRecords.Clear(); - _regKey = value; - } - } - } - - private string Filename - { - get - { - return Path.Combine(_appPaths.ConfigurationDirectoryPath, "mb.lic"); - } - } - - private readonly ConcurrentDictionary _updateRecords = new ConcurrentDictionary(); - private readonly object _fileLock = new object(); - private string _regKey; - - public MBLicenseFile(IApplicationPaths appPaths) - { - _appPaths = appPaths; - - Load(); - } - - private void SetUpdateRecord(Guid key, DateTime value) - { - _updateRecords.AddOrUpdate(key, value, (k, v) => value); - } - - public void AddRegCheck(string featureId) - { - using (var provider = new MD5CryptoServiceProvider()) - { - var key = new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(featureId))); - var value = DateTime.UtcNow; - - SetUpdateRecord(key, value); - Save(); - } - - } - - public void RemoveRegCheck(string featureId) - { - using (var provider = new MD5CryptoServiceProvider()) - { - var key = new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(featureId))); - DateTime val; - - _updateRecords.TryRemove(key, out val); - - Save(); - } - - } - - public DateTime LastChecked(string featureId) - { - using (var provider = new MD5CryptoServiceProvider()) - { - DateTime last; - _updateRecords.TryGetValue(new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(featureId))), out last); - - // guard agains people just putting a large number in the file - return last < DateTime.UtcNow ? last : DateTime.MinValue; - } - } - - private void Load() - { - string[] contents = null; - var licenseFile = Filename; - lock (_fileLock) - { - try - { - contents = File.ReadAllLines(licenseFile); - } - catch (DirectoryNotFoundException) - { - File.Create(licenseFile).Close(); - } - catch (FileNotFoundException) - { - File.Create(licenseFile).Close(); - } - } - if (contents != null && contents.Length > 0) - { - //first line is reg key - RegKey = contents[0]; - - //next is legacy key - if (contents.Length > 1) - { - // Don't need this anymore - } - - //the rest of the lines should be pairs of features and timestamps - for (var i = 2; i < contents.Length; i = i + 2) - { - var feat = Guid.Parse(contents[i]); - - SetUpdateRecord(feat, new DateTime(Convert.ToInt64(contents[i + 1]))); - } - } - } - - public void Save() - { - //build our array - var lines = new List - { - RegKey, - - // Legacy key - string.Empty - }; - - foreach (var pair in _updateRecords - .ToList()) - { - lines.Add(pair.Key.ToString()); - lines.Add(pair.Value.Ticks.ToString(CultureInfo.InvariantCulture)); - } - - var licenseFile = Filename; - Directory.CreateDirectory(Path.GetDirectoryName(licenseFile)); - lock (_fileLock) File.WriteAllLines(licenseFile, lines); - } - } -} diff --git a/MediaBrowser.Server.Implementations/Security/PluginSecurityManager.cs b/MediaBrowser.Server.Implementations/Security/PluginSecurityManager.cs deleted file mode 100644 index 7dc78a3afd..0000000000 --- a/MediaBrowser.Server.Implementations/Security/PluginSecurityManager.cs +++ /dev/null @@ -1,342 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Security; -using MediaBrowser.Controller; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Server.Implementations.Security -{ - /// - /// Class PluginSecurityManager - /// - public class PluginSecurityManager : ISecurityManager - { - private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate"; - private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "https://mb3admin.com/admin/service/appstore/register"; - - /// - /// The _is MB supporter - /// - private bool? _isMbSupporter; - /// - /// The _is MB supporter initialized - /// - private bool _isMbSupporterInitialized; - /// - /// The _is MB supporter sync lock - /// - private object _isMbSupporterSyncLock = new object(); - - /// - /// Gets a value indicating whether this instance is MB supporter. - /// - /// true if this instance is MB supporter; otherwise, false. - public bool IsMBSupporter - { - get - { - LazyInitializer.EnsureInitialized(ref _isMbSupporter, ref _isMbSupporterInitialized, ref _isMbSupporterSyncLock, () => GetSupporterRegistrationStatus().Result.IsRegistered); - return _isMbSupporter.Value; - } - } - - private MBLicenseFile _licenseFile; - private MBLicenseFile LicenseFile - { - get { return _licenseFile ?? (_licenseFile = new MBLicenseFile(_appPaths)); } - } - - 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 IEnumerable _registeredEntities; - protected IEnumerable RegisteredEntities - { - get - { - return _registeredEntities ?? (_registeredEntities = _appHost.GetExports()); - } - } - - /// - /// Initializes a new instance of the class. - /// - public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, - IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem) - { - if (httpClient == null) - { - throw new ArgumentNullException("httpClient"); - } - - _appHost = appHost; - _httpClient = httpClient; - _jsonSerializer = jsonSerializer; - _appPaths = appPaths; - _fileSystem = fileSystem; - _logger = logManager.GetLogger("SecurityManager"); - } - - /// - /// Load all registration info for all entities that require registration - /// - /// - public async Task LoadAllRegistrationInfo() - { - var tasks = new List(); - - ResetSupporterInfo(); - tasks.AddRange(RegisteredEntities.Select(i => i.LoadRegistrationInfoAsync())); - await Task.WhenAll(tasks); - } - - /// - /// Gets the registration status. - /// This overload supports existing plug-ins. - /// - /// The feature. - /// The MB2 equivalent. - /// Task{MBRegistrationRecord}. - public Task GetRegistrationStatus(string feature, string mb2Equivalent = null) - { - return GetRegistrationStatusInternal(feature, mb2Equivalent); - } - - /// - /// Gets the registration status. - /// - /// The feature. - /// The MB2 equivalent. - /// The version of this feature - /// Task{MBRegistrationRecord}. - public Task GetRegistrationStatus(string feature, string mb2Equivalent, string version) - { - return GetRegistrationStatusInternal(feature, mb2Equivalent, version); - } - - private Task GetSupporterRegistrationStatus() - { - return GetRegistrationStatusInternal("MBSupporter", null, _appHost.ApplicationVersion.ToString()); - } - - /// - /// Gets or sets the supporter key. - /// - /// The supporter key. - public string SupporterKey - { - get - { - return LicenseFile.RegKey; - } - set - { - var newValue = value; - if (newValue != null) - { - newValue = newValue.Trim(); - } - - if (newValue != LicenseFile.RegKey) - { - LicenseFile.RegKey = newValue; - LicenseFile.Save(); - - // re-load registration info - Task.Run(() => LoadAllRegistrationInfo()); - } - } - } - - /// - /// Register an app store sale with our back-end. It will validate the transaction with the store - /// and then register the proper feature and then fill in the supporter key on success. - /// - /// Json parameters to send to admin server - public async Task RegisterAppStoreSale(string parameters) - { - var options = new HttpRequestOptions() - { - Url = AppstoreRegUrl, - CancellationToken = CancellationToken.None, - BufferContent = false - }; - options.RequestHeaders.Add("X-Emby-Token", _appHost.SystemId); - options.RequestContent = parameters; - options.RequestContentType = "application/json"; - - try - { - using (var response = await _httpClient.Post(options).ConfigureAwait(false)) - { - var reg = _jsonSerializer.DeserializeFromStream(response.Content); - - if (reg == null) - { - var msg = "Result from appstore registration was null."; - _logger.Error(msg); - throw new ApplicationException(msg); - } - if (!String.IsNullOrEmpty(reg.key)) - { - SupporterKey = reg.key; - } - } - - } - catch (ApplicationException) - { - SaveAppStoreInfo(parameters); - throw; - } - catch (HttpException e) - { - _logger.ErrorException("Error registering appstore purchase {0}", e, parameters ?? "NO PARMS SENT"); - - if (e.StatusCode.HasValue && e.StatusCode.Value == HttpStatusCode.PaymentRequired) - { - throw new PaymentRequiredException(); - } - throw new ApplicationException("Error registering store sale"); - } - catch (Exception e) - { - _logger.ErrorException("Error registering appstore purchase {0}", e, parameters ?? "NO PARMS SENT"); - SaveAppStoreInfo(parameters); - //TODO - could create a re-try routine on start-up if this file is there. For now we can handle manually. - throw new ApplicationException("Error registering store sale"); - } - - } - - private void SaveAppStoreInfo(string info) - { - // Save all transaction information to a file - - try - { - _fileSystem.WriteAllText(Path.Combine(_appPaths.ProgramDataPath, "apptrans-error.txt"), info); - } - catch (IOException) - { - - } - } - - private async Task GetRegistrationStatusInternal(string feature, - string mb2Equivalent = null, - string version = null) - { - var lastChecked = LicenseFile.LastChecked(feature); - - //check the reg file first to alleviate strain on the MB admin server - must actually check in every 30 days tho - var reg = new RegRecord - { - // Cache the result for up to a week - registered = lastChecked > DateTime.UtcNow.AddDays(-7) - }; - - var success = reg.registered; - - if (!(lastChecked > DateTime.UtcNow.AddDays(-1))) - { - var data = new Dictionary - { - { "feature", feature }, - { "key", SupporterKey }, - { "mac", _appHost.SystemId }, - { "systemid", _appHost.SystemId }, - { "mb2equiv", mb2Equivalent }, - { "ver", version }, - { "platform", _appHost.OperatingSystemDisplayName }, - { "isservice", _appHost.IsRunningAsService.ToString().ToLower() } - }; - - try - { - var options = new HttpRequestOptions - { - Url = MBValidateUrl, - - // Seeing block length errors - EnableHttpCompression = false, - BufferContent = false - }; - - options.SetPostData(data); - - using (var json = (await _httpClient.Post(options).ConfigureAwait(false)).Content) - { - reg = _jsonSerializer.DeserializeFromStream(json); - success = true; - } - - if (reg.registered) - { - LicenseFile.AddRegCheck(feature); - } - else - { - LicenseFile.RemoveRegCheck(feature); - } - - } - catch (Exception e) - { - _logger.ErrorException("Error checking registration status of {0}", e, feature); - } - } - - var record = new MBRegistrationRecord - { - IsRegistered = reg.registered, - ExpirationDate = reg.expDate, - RegChecked = true, - RegError = !success - }; - - record.TrialVersion = IsInTrial(reg.expDate, record.RegChecked, record.IsRegistered); - record.IsValid = !record.RegChecked || record.IsRegistered || record.TrialVersion; - - return record; - } - - private bool IsInTrial(DateTime expirationDate, bool regChecked, bool isRegistered) - { - //don't set this until we've successfully obtained exp date - if (!regChecked) - { - return false; - } - - var isInTrial = expirationDate > DateTime.UtcNow; - - return isInTrial && !isRegistered; - } - - /// - /// Resets the supporter info. - /// - private void ResetSupporterInfo() - { - _isMbSupporter = null; - _isMbSupporterInitialized = false; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Security/RegRecord.cs b/MediaBrowser.Server.Implementations/Security/RegRecord.cs deleted file mode 100644 index 947ec629f0..0000000000 --- a/MediaBrowser.Server.Implementations/Security/RegRecord.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace MediaBrowser.Server.Implementations.Security -{ - class RegRecord - { - public string featId { get; set; } - public bool registered { get; set; } - public DateTime expDate { get; set; } - public string key { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 3a5939df1a..5f609de27a 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -112,6 +112,7 @@ using Emby.Server.Implementations.MediaEncoder; using Emby.Server.Implementations.Notifications; using Emby.Server.Implementations.Persistence; using Emby.Server.Implementations.Playlists; +using Emby.Server.Implementations.Security; using Emby.Server.Implementations.ServerManager; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.Sync; @@ -532,7 +533,7 @@ namespace MediaBrowser.Server.Startup.Common { await base.RegisterResources(progress).ConfigureAwait(false); - SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager, FileSystemManager); + SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager, FileSystemManager, CryptographyProvider); RegisterSingleInstance(SecurityManager); InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager, CryptographyProvider); -- cgit v1.2.3