diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index 315b48b837..37142af194 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -44,6 +44,11 @@ namespace MediaBrowser.Controller.Net /// event EventHandler WebSocketConnected; + /// + /// Occurs when [web socket connecting]. + /// + event EventHandler WebSocketConnecting; + /// /// Inits this instance. /// diff --git a/MediaBrowser.Controller/Net/IServerManager.cs b/MediaBrowser.Controller/Net/IServerManager.cs index d90a0f8ed6..5191a62e31 100644 --- a/MediaBrowser.Controller/Net/IServerManager.cs +++ b/MediaBrowser.Controller/Net/IServerManager.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.Net; +using MediaBrowser.Model.Events; using System; using System.Collections.Generic; using System.Threading; @@ -58,5 +58,10 @@ namespace MediaBrowser.Controller.Net /// /// The web socket connections. IEnumerable WebSocketConnections { get; } + + /// + /// Occurs when [web socket connected]. + /// + event EventHandler> WebSocketConnected; } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index 37fd6708d9..e21df3c395 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -1,5 +1,6 @@ using MediaBrowser.Model.Net; using System; +using System.Collections.Specialized; using System.Threading; using System.Threading.Tasks; @@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Net /// Occurs when [closed]. /// event EventHandler Closed; - + /// /// Gets the id. /// @@ -24,6 +25,17 @@ namespace MediaBrowser.Controller.Net /// The last activity date. DateTime LastActivityDate { get; } + /// + /// Gets or sets the URL. + /// + /// The URL. + string Url { get; set; } + /// + /// Gets or sets the query string. + /// + /// The query string. + NameValueCollection QueryString { get; set; } + /// /// Gets or sets the receive action. /// diff --git a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs index 394fcd92ff..ffeaf286e2 100644 --- a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs +++ b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Specialized; namespace MediaBrowser.Controller.Net { @@ -7,6 +8,16 @@ namespace MediaBrowser.Controller.Net /// public class WebSocketConnectEventArgs : EventArgs { + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + /// + /// Gets or sets the query string. + /// + /// The query string. + public NameValueCollection QueryString { get; set; } /// /// Gets or sets the web socket. /// @@ -18,4 +29,35 @@ namespace MediaBrowser.Controller.Net /// The endpoint. public string Endpoint { get; set; } } + + public class WebSocketConnectingEventArgs : EventArgs + { + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + /// + /// Gets or sets the endpoint. + /// + /// The endpoint. + public string Endpoint { get; set; } + /// + /// Gets or sets the query string. + /// + /// The query string. + public NameValueCollection QueryString { get; set; } + /// + /// Gets or sets a value indicating whether [allow connection]. + /// + /// true if [allow connection]; otherwise, false. + public bool AllowConnection { get; set; } + + public WebSocketConnectingEventArgs() + { + QueryString = new NameValueCollection(); + AllowConnection = true; + } + } + } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 4082f56008..b51b590cf7 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -278,6 +278,13 @@ namespace MediaBrowser.Controller.Session /// SessionInfo. SessionInfo GetSession(string deviceId, string client, string version); + /// + /// Gets the session by authentication token. + /// + /// The token. + /// SessionInfo. + SessionInfo GetSessionByAuthenticationToken(string token); + /// /// Logouts the specified access token. /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 4727e6035b..f910542063 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer private readonly ContainerAdapter _containerAdapter; public event EventHandler WebSocketConnected; + public event EventHandler WebSocketConnecting; private readonly List _localEndpoints = new List(); @@ -196,7 +197,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer _listener = GetListener(); - _listener.WebSocketHandler = WebSocketHandler; + _listener.WebSocketConnected = OnWebSocketConnected; + _listener.WebSocketConnecting = OnWebSocketConnecting; _listener.ErrorHandler = ErrorHandler; _listener.RequestHandler = RequestHandler; @@ -208,7 +210,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer return new WebSocketSharpListener(_logger, OnRequestReceived, CertificatePath); } - private void WebSocketHandler(WebSocketConnectEventArgs args) + private void OnWebSocketConnecting(WebSocketConnectingEventArgs args) + { + if (WebSocketConnecting != null) + { + WebSocketConnecting(this, args); + } + } + + private void OnWebSocketConnected(WebSocketConnectEventArgs args) { if (WebSocketConnected != null) { diff --git a/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs index e77600e938..dc315601f2 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/IHttpListener.cs @@ -24,8 +24,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// Gets or sets the web socket handler. /// /// The web socket handler. - Action WebSocketHandler { get; set; } + Action WebSocketConnected { get; set; } + /// + /// Gets or sets the web socket connecting. + /// + /// The web socket connecting. + Action WebSocketConnecting { get; set; } + /// /// Starts this instance. /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs index 9d1ddb7fc4..954db3a9d8 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -23,6 +23,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security { var authorization = _authContext.GetAuthorizationInfo(requestContext); + if (!string.IsNullOrWhiteSpace(authorization.Token)) + { + return _sessionManager.GetSessionByAuthenticationToken(authorization.Token); + } return _sessionManager.GetSession(authorization.DeviceId, authorization.Client, authorization.Version); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index 0c5c9e9bf1..a223cf68d0 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Net; +using System.Collections.Specialized; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations.Logging; using ServiceStack; @@ -18,9 +19,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp private readonly ILogger _logger; private readonly Action _endpointListener; - private readonly string _certificatePath ; + private readonly string _certificatePath; - public WebSocketSharpListener(ILogger logger, Action endpointListener, + public WebSocketSharpListener(ILogger logger, Action endpointListener, string certificatePath) { _logger = logger; @@ -32,7 +33,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp public Func RequestHandler { get; set; } - public Action WebSocketHandler { get; set; } + public Action WebSocketConnecting { get; set; } + + public Action WebSocketConnected { get; set; } public void Start(IEnumerable urlPrefixes) { @@ -115,15 +118,43 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { try { - var webSocketContext = ctx.AcceptWebSocket(null); + var endpoint = ctx.Request.RemoteEndPoint.ToString(); + var url = ctx.Request.RawUrl; + var queryString = new NameValueCollection(ctx.Request.QueryString); + + var connectingArgs = new WebSocketConnectingEventArgs + { + Url = url, + QueryString = queryString, + Endpoint = endpoint + }; + + if (WebSocketConnecting != null) + { + WebSocketConnecting(connectingArgs); + } - if (WebSocketHandler != null) + if (connectingArgs.AllowConnection) { - WebSocketHandler(new WebSocketConnectEventArgs + _logger.Debug("Web socket connection allowed"); + + var webSocketContext = ctx.AcceptWebSocket(null); + + if (WebSocketConnected != null) { - WebSocket = new SharpWebSocket(webSocketContext.WebSocket, _logger), - Endpoint = ctx.Request.RemoteEndPoint.ToString() - }); + WebSocketConnected(new WebSocketConnectEventArgs + { + Url = url, + QueryString = queryString, + WebSocket = new SharpWebSocket(webSocketContext.WebSocket, _logger), + Endpoint = endpoint + }); + } + } + else + { + _logger.Warn("Web socket connection not allowed"); + ctx.Response.Close(); } } catch (Exception ex) diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index dcfb9c4490..6428d10a97 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -1,11 +1,14 @@ -using MediaBrowser.Controller; +using MediaBrowser.Common.Events; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Net.Sockets; using System.Threading; @@ -44,6 +47,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager get { return _webSocketConnections; } } + public event EventHandler> WebSocketConnected; + /// /// The _logger /// @@ -140,10 +145,17 @@ namespace MediaBrowser.Server.Implementations.ServerManager { var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) { - OnReceive = ProcessWebSocketMessageReceived + OnReceive = ProcessWebSocketMessageReceived, + Url = e.Url, + QueryString = new NameValueCollection(e.QueryString) }; _webSocketConnections.Add(connection); + + if (WebSocketConnected != null) + { + EventHelper.FireEventIfNotNull(WebSocketConnected, this, new GenericEventArgs (connection), _logger); + } } /// diff --git a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs index 08e4c2ec2e..738e82bd02 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs @@ -1,10 +1,10 @@ -using System.Text; -using MediaBrowser.Common.Events; +using MediaBrowser.Common.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using System; +using System.Collections.Specialized; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -66,6 +66,17 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// The id. public Guid Id { get; private set; } + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + /// + /// Gets or sets the query string. + /// + /// The query string. + public NameValueCollection QueryString { get; set; } + /// /// Initializes a new instance of the class. /// diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 8eef8536ab..a09f585fd4 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1640,6 +1640,26 @@ namespace MediaBrowser.Server.Implementations.Session string.Equals(i.Client, client)); } + public SessionInfo GetSessionByAuthenticationToken(string token) + { + var result = _authRepo.Get(new AuthenticationInfoQuery + { + AccessToken = token + }); + + if (result.Items.Length == 0) + { + return null; + } + + var info = result.Items[0]; + + // TODO: Make Token part of SessionInfo and get result that way + // This can't be done until all apps are updated to new authentication. + return Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, info.DeviceId) && + string.Equals(i.Client, info.AppName)); + } + public Task SendMessageToUserSessions(string userId, string name, T data, CancellationToken cancellationToken) { diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index d1bb4b837c..b581f9144e 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,9 +1,12 @@ using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using System; +using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -13,7 +16,7 @@ namespace MediaBrowser.Server.Implementations.Session /// /// Class SessionWebSocketListener /// - public class SessionWebSocketListener : IWebSocketListener + public class SessionWebSocketListener : IWebSocketListener, IDisposable { /// /// The _true task result @@ -35,17 +38,75 @@ namespace MediaBrowser.Server.Implementations.Session /// private readonly IJsonSerializer _json; + private readonly IHttpServer _httpServer; + private readonly IServerManager _serverManager; + + /// /// Initializes a new instance of the class. /// /// The session manager. /// The log manager. /// The json. - public SessionWebSocketListener(ISessionManager sessionManager, ILogManager logManager, IJsonSerializer json) + /// The HTTP server. + /// The server manager. + public SessionWebSocketListener(ISessionManager sessionManager, ILogManager logManager, IJsonSerializer json, IHttpServer httpServer, IServerManager serverManager) { _sessionManager = sessionManager; _logger = logManager.GetLogger(GetType().Name); _json = json; + _httpServer = httpServer; + _serverManager = serverManager; + httpServer.WebSocketConnecting += _httpServer_WebSocketConnecting; + serverManager.WebSocketConnected += _serverManager_WebSocketConnected; + } + + void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) + { + var session = GetSession(e.Argument.QueryString); + + if (session != null) + { + var controller = session.SessionController as WebSocketController; + + if (controller == null) + { + controller = new WebSocketController(session, _logger, _sessionManager); + } + + controller.AddWebSocket(e.Argument); + + session.SessionController = controller; + } + else + { + _logger.Warn("Unable to determine session based on url: {0}", e.Argument.Url); + } + } + + void _httpServer_WebSocketConnecting(object sender, WebSocketConnectingEventArgs e) + { + if (e.QueryString.AllKeys.Contains("api_key", StringComparer.OrdinalIgnoreCase)) + { + var session = GetSession(e.QueryString); + + if (session == null) + { + e.AllowConnection = false; + } + } + } + + private SessionInfo GetSession(NameValueCollection queryString) + { + var token = queryString["api_key"]; + return _sessionManager.GetSessionByAuthenticationToken(token); + } + + public void Dispose() + { + _httpServer.WebSocketConnecting -= _httpServer_WebSocketConnecting; + _serverManager.WebSocketConnected -= _serverManager_WebSocketConnected; } ///