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 --- Emby.Common.Implementations/BaseApplicationHost.cs | 6 + .../Net/DisposableManagedObjectBase.cs | 74 ++++++ Emby.Common.Implementations/Net/SocketFactory.cs | 113 +++++++++ Emby.Common.Implementations/Net/UdpSocket.cs | 268 +++++++++++++++++++++ .../Reflection/AssemblyInfo.cs | 8 + 5 files changed, 469 insertions(+) create mode 100644 Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs create mode 100644 Emby.Common.Implementations/Net/SocketFactory.cs create mode 100644 Emby.Common.Implementations/Net/UdpSocket.cs (limited to 'Emby.Common.Implementations') diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index e1f7ef08a..bdebe894e 100644 --- a/Emby.Common.Implementations/BaseApplicationHost.cs +++ b/Emby.Common.Implementations/BaseApplicationHost.cs @@ -28,11 +28,13 @@ using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using Emby.Common.Implementations.Cryptography; using Emby.Common.Implementations.Diagnostics; +using Emby.Common.Implementations.Net; using Emby.Common.Implementations.Threading; using MediaBrowser.Common; using MediaBrowser.Common.IO; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Diagnostics; +using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Threading; @@ -153,6 +155,7 @@ namespace Emby.Common.Implementations protected IProcessFactory ProcessFactory { get; private set; } protected ITimerFactory TimerFactory { get; private set; } + protected ISocketFactory SocketFactory { get; private set; } /// /// Gets the name. @@ -549,6 +552,9 @@ return null; TimerFactory = new TimerFactory(); RegisterSingleInstance(TimerFactory); + SocketFactory = new SocketFactory(null); + RegisterSingleInstance(SocketFactory); + RegisterSingleInstance(CryptographyProvider); return Task.FromResult(true); diff --git a/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs b/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs new file mode 100644 index 000000000..8476cea32 --- /dev/null +++ b/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Emby.Common.Implementations.Net +{ + /// + /// Correclty implements the interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an property. + /// + public abstract class DisposableManagedObjectBase : IDisposable + { + + #region Public Methods + + /// + /// Override this method and dispose any objects you own the lifetime of if disposing is true; + /// + /// True if managed objects should be disposed, if false, only unmanaged resources should be released. + protected abstract void Dispose(bool disposing); + + /// + /// Throws and if the property is true. + /// + /// + /// Thrown if the property is true. + /// + protected virtual void ThrowIfDisposed() + { + if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName); + } + + #endregion + + #region Public Properties + + /// + /// Sets or returns a boolean indicating whether or not this instance has been disposed. + /// + /// + public bool IsDisposed + { + get; + private set; + } + + #endregion + + #region IDisposable Members + + /// + /// Disposes this object instance and all internally managed resources. + /// + /// + /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes. + /// + /// + public void Dispose() + { + try + { + IsDisposed = true; + + Dispose(true); + } + finally + { + GC.SuppressFinalize(this); + } + } + + #endregion + } +} diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs new file mode 100644 index 000000000..3a2cea12a --- /dev/null +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using MediaBrowser.Model.Net; + +namespace Emby.Common.Implementations.Net +{ + public class SocketFactory : ISocketFactory + { + // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS + // Be careful to check any changes compile and work for all platform projects it is shared in. + + // Not entirely happy with this. Would have liked to have done something more generic/reusable, + // but that wasn't really the point so kept to YAGNI principal for now, even if the + // interfaces are a bit ugly, specific and make assumptions. + + /// + /// Used by RSSDP components to create implementations of the interface, to perform platform agnostic socket communications. + /// + private IPAddress _LocalIP; + + /// + /// Default constructor. + /// + /// A string containing the IP address of the local network adapter to bind sockets to. Null or empty string will use . + public SocketFactory(string localIP) + { + if (String.IsNullOrEmpty(localIP)) + _LocalIP = IPAddress.Any; + else + _LocalIP = IPAddress.Parse(localIP); + } + + #region ISocketFactory Members + + /// + /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. + /// + /// An integer specifying the local port to bind the socket to. + /// An implementation of the interface used by RSSDP components to perform socket operations. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpSocket(int localPort) + { + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + try + { + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP)); + return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + + /// + /// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port. + /// + /// The multicast IP address to make the socket a member of. + /// The multicast time to live value for the socket. + /// The number of the local port to bind to. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) + { + if (ipAddress == null) throw new ArgumentNullException("ipAddress"); + if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress"); + if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive"); + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + try + { +#if NETSTANDARD1_3 + // The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket + // See https://github.com/dotnet/corefx/pull/11509 for more details + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + { + retVal.ExclusiveAddressUse = false; + } +#else + retVal.ExclusiveAddressUse = false; +#endif + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); + retVal.MulticastLoopback = true; + + return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + + #endregion + } +} diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs new file mode 100644 index 000000000..86ce9c83b --- /dev/null +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Security; +using System.Threading.Tasks; +using MediaBrowser.Model.Net; + +namespace Emby.Common.Implementations.Net +{ + // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS + // Be careful to check any changes compile and work for all platform projects it is shared in. + + internal sealed class UdpSocket : DisposableManagedObjectBase, IUdpSocket + { + + #region Fields + + private System.Net.Sockets.Socket _Socket; + private int _LocalPort; + + #endregion + + #region Constructors + + public UdpSocket(System.Net.Sockets.Socket socket, int localPort, string ipAddress) + { + if (socket == null) throw new ArgumentNullException("socket"); + + _Socket = socket; + _LocalPort = localPort; + + IPAddress ip = null; + if (String.IsNullOrEmpty(ipAddress)) + ip = IPAddress.Any; + else + ip = IPAddress.Parse(ipAddress); + + _Socket.Bind(new IPEndPoint(ip, _LocalPort)); + if (_LocalPort == 0) + _LocalPort = (_Socket.LocalEndPoint as IPEndPoint).Port; + } + + #endregion + + #region IUdpSocket Members + + public Task ReceiveAsync() + { + ThrowIfDisposed(); + + var tcs = new TaskCompletionSource(); + + System.Net.EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); + var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); + state.TaskCompletionSource = tcs; + +#if NETSTANDARD1_6 + _Socket.ReceiveFromAsync(new System.ArraySegment(state.Buffer), System.Net.Sockets.SocketFlags.None, state.EndPoint) + .ContinueWith((task, asyncState) => + { + if (task.Status != TaskStatus.Faulted) + { + var receiveState = asyncState as AsyncReceiveState; + receiveState.EndPoint = task.Result.RemoteEndPoint; + ProcessResponse(receiveState, () => task.Result.ReceivedBytes); + } + }, state); +#else + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, System.Net.Sockets.SocketFlags.None, ref state.EndPoint, new AsyncCallback(this.ProcessResponse), state); +#endif + + return tcs.Task; + } + + public Task SendTo(byte[] messageData, IpEndPointInfo endPoint) + { + ThrowIfDisposed(); + + if (messageData == null) throw new ArgumentNullException("messageData"); + if (endPoint == null) throw new ArgumentNullException("endPoint"); + +#if NETSTANDARD1_6 + _Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); + return Task.FromResult(true); +#else + var taskSource = new TaskCompletionSource(); + + try + { + _Socket.BeginSendTo(messageData, 0, messageData.Length, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result => + { + try + { + _Socket.EndSend(result); + taskSource.TrySetResult(true); + } + catch (SocketException ex) + { + taskSource.TrySetException(ex); + } + catch (ObjectDisposedException ex) + { + taskSource.TrySetException(ex); + } + catch (InvalidOperationException ex) + { + taskSource.TrySetException(ex); + } + catch (SecurityException ex) + { + taskSource.TrySetException(ex); + } + }, null); + } + catch (SocketException ex) + { + taskSource.TrySetException(ex); + } + catch (ObjectDisposedException ex) + { + taskSource.TrySetException(ex); + } + catch (SecurityException ex) + { + taskSource.TrySetException(ex); + } + + //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); + + return taskSource.Task; +#endif + } + + #endregion + + #region Overrides + + protected override void Dispose(bool disposing) + { + if (disposing) + { + var socket = _Socket; + if (socket != null) + socket.Dispose(); + } + } + + #endregion + + #region Private Methods + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] + private static void ProcessResponse(AsyncReceiveState state, Func receiveData) + { + try + { + var bytesRead = receiveData(); + + var ipEndPoint = state.EndPoint as IPEndPoint; + state.TaskCompletionSource.SetResult( + new ReceivedUdpData() + { + Buffer = state.Buffer, + ReceivedBytes = bytesRead, + ReceivedFrom = ToIpEndPointInfo(ipEndPoint) + } + ); + } + catch (ObjectDisposedException) + { + state.TaskCompletionSource.SetCanceled(); + } + catch (SocketException se) + { + if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown) + state.TaskCompletionSource.SetException(se); + else + state.TaskCompletionSource.SetCanceled(); + } + catch (Exception ex) + { + state.TaskCompletionSource.SetException(ex); + } + } + + private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) + { + if (endpoint == null) + { + return null; + } + + return new IpEndPointInfo + { + IpAddress = new IpAddressInfo + { + Address = endpoint.Address.ToString(), + IsIpv6 = endpoint.AddressFamily == AddressFamily.InterNetworkV6 + }, + + Port = endpoint.Port + }; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] + private void ProcessResponse(IAsyncResult asyncResult) + { +#if NET46 + var state = asyncResult.AsyncState as AsyncReceiveState; + try + { + var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.EndPoint); + + var ipEndPoint = state.EndPoint as IPEndPoint; + state.TaskCompletionSource.SetResult( + new ReceivedUdpData() + { + Buffer = state.Buffer, + ReceivedBytes = bytesRead, + ReceivedFrom = ToIpEndPointInfo(ipEndPoint) + } + ); + } + catch (ObjectDisposedException) + { + state.TaskCompletionSource.SetCanceled(); + } + catch (SocketException se) + { + if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown) + state.TaskCompletionSource.SetException(se); + else + state.TaskCompletionSource.SetCanceled(); + } + catch (Exception ex) + { + state.TaskCompletionSource.SetException(ex); + } +#endif + } + + #endregion + + #region Private Classes + + private class AsyncReceiveState + { + public AsyncReceiveState(System.Net.Sockets.Socket socket, EndPoint endPoint) + { + this.Socket = socket; + this.EndPoint = endPoint; + } + + public EndPoint EndPoint; + public byte[] Buffer = new byte[8192]; + + public System.Net.Sockets.Socket Socket { get; private set; } + + public TaskCompletionSource TaskCompletionSource { get; set; } + + } + + #endregion + + } +} diff --git a/Emby.Common.Implementations/Reflection/AssemblyInfo.cs b/Emby.Common.Implementations/Reflection/AssemblyInfo.cs index 820856da5..bd2cb7cf0 100644 --- a/Emby.Common.Implementations/Reflection/AssemblyInfo.cs +++ b/Emby.Common.Implementations/Reflection/AssemblyInfo.cs @@ -14,5 +14,13 @@ namespace Emby.Common.Implementations.Reflection #endif return type.GetTypeInfo().Assembly.GetManifestResourceStream(resource); } + + public string[] GetManifestResourceNames(Type type) + { +#if NET46 + return type.Assembly.GetManifestResourceNames(); +#endif + return type.GetTypeInfo().Assembly.GetManifestResourceNames(); + } } } -- 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 'Emby.Common.Implementations') diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index a8aa1a3cd..5b965efdc 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 5d27e84dd..4a27ddb74 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 000000000..454ee6026 --- /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 000000000..c3a7e9450 --- /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 000000000..d484085d3 --- /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 50e32572d..ca537752a 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 9af765c23..d6223c465 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 7b37925ba..000000000 --- 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 7dc78a3af..000000000 --- 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 947ec629f..000000000 --- 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 3a5939df1..5f609de27 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