|
@@ -1,6 +1,4 @@
|
|
-using System.Security.Cryptography;
|
|
|
|
-using System.Text;
|
|
|
|
-using MediaBrowser.Common.Events;
|
|
|
|
|
|
+using MediaBrowser.Common.Events;
|
|
using MediaBrowser.Common.Extensions;
|
|
using MediaBrowser.Common.Extensions;
|
|
using MediaBrowser.Common.Net;
|
|
using MediaBrowser.Common.Net;
|
|
using MediaBrowser.Controller;
|
|
using MediaBrowser.Controller;
|
|
@@ -13,12 +11,14 @@ using MediaBrowser.Controller.Entities.TV;
|
|
using MediaBrowser.Controller.Library;
|
|
using MediaBrowser.Controller.Library;
|
|
using MediaBrowser.Controller.LiveTv;
|
|
using MediaBrowser.Controller.LiveTv;
|
|
using MediaBrowser.Controller.Persistence;
|
|
using MediaBrowser.Controller.Persistence;
|
|
|
|
+using MediaBrowser.Controller.Security;
|
|
using MediaBrowser.Controller.Session;
|
|
using MediaBrowser.Controller.Session;
|
|
using MediaBrowser.Model.Entities;
|
|
using MediaBrowser.Model.Entities;
|
|
using MediaBrowser.Model.Library;
|
|
using MediaBrowser.Model.Library;
|
|
using MediaBrowser.Model.Logging;
|
|
using MediaBrowser.Model.Logging;
|
|
using MediaBrowser.Model.Serialization;
|
|
using MediaBrowser.Model.Serialization;
|
|
using MediaBrowser.Model.Session;
|
|
using MediaBrowser.Model.Session;
|
|
|
|
+using MediaBrowser.Model.Users;
|
|
using System;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
@@ -27,7 +27,6 @@ using System.IO;
|
|
using System.Linq;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
-using MediaBrowser.Model.Users;
|
|
|
|
|
|
|
|
namespace MediaBrowser.Server.Implementations.Session
|
|
namespace MediaBrowser.Server.Implementations.Session
|
|
{
|
|
{
|
|
@@ -62,6 +61,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
private readonly IJsonSerializer _jsonSerializer;
|
|
private readonly IJsonSerializer _jsonSerializer;
|
|
private readonly IServerApplicationHost _appHost;
|
|
private readonly IServerApplicationHost _appHost;
|
|
|
|
|
|
|
|
+ private readonly IAuthenticationRepository _authRepo;
|
|
|
|
+
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Gets or sets the configuration manager.
|
|
/// Gets or sets the configuration manager.
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -104,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
/// <param name="logger">The logger.</param>
|
|
/// <param name="logger">The logger.</param>
|
|
/// <param name="userRepository">The user repository.</param>
|
|
/// <param name="userRepository">The user repository.</param>
|
|
/// <param name="libraryManager">The library manager.</param>
|
|
/// <param name="libraryManager">The library manager.</param>
|
|
- public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient)
|
|
|
|
|
|
+ public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo)
|
|
{
|
|
{
|
|
_userDataRepository = userDataRepository;
|
|
_userDataRepository = userDataRepository;
|
|
_configurationManager = configurationManager;
|
|
_configurationManager = configurationManager;
|
|
@@ -119,6 +120,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
_jsonSerializer = jsonSerializer;
|
|
_jsonSerializer = jsonSerializer;
|
|
_appHost = appHost;
|
|
_appHost = appHost;
|
|
_httpClient = httpClient;
|
|
_httpClient = httpClient;
|
|
|
|
+ _authRepo = authRepo;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -204,7 +206,12 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
/// <returns>Task.</returns>
|
|
/// <returns>Task.</returns>
|
|
/// <exception cref="System.ArgumentNullException">user</exception>
|
|
/// <exception cref="System.ArgumentNullException">user</exception>
|
|
/// <exception cref="System.UnauthorizedAccessException"></exception>
|
|
/// <exception cref="System.UnauthorizedAccessException"></exception>
|
|
- public async Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
|
|
|
|
|
|
+ public async Task<SessionInfo> LogSessionActivity(string clientType,
|
|
|
|
+ string appVersion,
|
|
|
|
+ string deviceId,
|
|
|
|
+ string deviceName,
|
|
|
|
+ string remoteEndPoint,
|
|
|
|
+ User user)
|
|
{
|
|
{
|
|
if (string.IsNullOrEmpty(clientType))
|
|
if (string.IsNullOrEmpty(clientType))
|
|
{
|
|
{
|
|
@@ -1157,7 +1164,37 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
|
|
|
|
public void ValidateSecurityToken(string token)
|
|
public void ValidateSecurityToken(string token)
|
|
{
|
|
{
|
|
|
|
+ if (string.IsNullOrWhiteSpace(token))
|
|
|
|
+ {
|
|
|
|
+ throw new UnauthorizedAccessException();
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ var result = _authRepo.Get(new AuthenticationInfoQuery
|
|
|
|
+ {
|
|
|
|
+ AccessToken = token
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ var info = result.Items.FirstOrDefault();
|
|
|
|
+
|
|
|
|
+ if (info == null)
|
|
|
|
+ {
|
|
|
|
+ throw new UnauthorizedAccessException();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!info.IsActive)
|
|
|
|
+ {
|
|
|
|
+ throw new UnauthorizedAccessException("Access token has expired.");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(info.UserId))
|
|
|
|
+ {
|
|
|
|
+ var user = _userManager.GetUserById(new Guid(info.UserId));
|
|
|
|
+
|
|
|
|
+ if (user == null || user.Configuration.IsDisabled)
|
|
|
|
+ {
|
|
|
|
+ throw new UnauthorizedAccessException("User account has been disabled.");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -1175,7 +1212,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
/// <exception cref="UnauthorizedAccessException"></exception>
|
|
/// <exception cref="UnauthorizedAccessException"></exception>
|
|
public async Task<AuthenticationResult> AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint)
|
|
public async Task<AuthenticationResult> AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint)
|
|
{
|
|
{
|
|
- var result = await _userManager.AuthenticateUser(username, password).ConfigureAwait(false);
|
|
|
|
|
|
+ var result = IsLocalhost(remoteEndPoint) || await _userManager.AuthenticateUser(username, password).ConfigureAwait(false);
|
|
|
|
|
|
if (!result)
|
|
if (!result)
|
|
{
|
|
{
|
|
@@ -1185,6 +1222,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
var user = _userManager.Users
|
|
var user = _userManager.Users
|
|
.First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
|
|
.First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
|
|
+ var token = await GetAuthorizationToken(user.Id.ToString("N"), deviceId, clientType, deviceName).ConfigureAwait(false);
|
|
|
|
+
|
|
var session = await LogSessionActivity(clientType,
|
|
var session = await LogSessionActivity(clientType,
|
|
appVersion,
|
|
appVersion,
|
|
deviceId,
|
|
deviceId,
|
|
@@ -1197,11 +1236,108 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
{
|
|
{
|
|
User = _dtoService.GetUserDto(user),
|
|
User = _dtoService.GetUserDto(user),
|
|
SessionInfo = GetSessionInfoDto(session),
|
|
SessionInfo = GetSessionInfoDto(session),
|
|
- AuthenticationToken = Guid.NewGuid().ToString("N")
|
|
|
|
|
|
+ AccessToken = token
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
- private bool IsLocal(string remoteEndpoint)
|
|
|
|
|
|
+ private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string deviceName)
|
|
|
|
+ {
|
|
|
|
+ var existing = _authRepo.Get(new AuthenticationInfoQuery
|
|
|
|
+ {
|
|
|
|
+ DeviceId = deviceId,
|
|
|
|
+ IsActive = true,
|
|
|
|
+ UserId = userId,
|
|
|
|
+ Limit = 1
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (existing.Items.Length > 0)
|
|
|
|
+ {
|
|
|
|
+ _logger.Debug("Reissuing access token");
|
|
|
|
+ return existing.Items[0].AccessToken;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var newToken = new AuthenticationInfo
|
|
|
|
+ {
|
|
|
|
+ AppName = app,
|
|
|
|
+ DateCreated = DateTime.UtcNow,
|
|
|
|
+ DeviceId = deviceId,
|
|
|
|
+ DeviceName = deviceName,
|
|
|
|
+ UserId = userId,
|
|
|
|
+ IsActive = true,
|
|
|
|
+ AccessToken = Guid.NewGuid().ToString("N")
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ _logger.Debug("Creating new access token for user {0}", userId);
|
|
|
|
+ await _authRepo.Create(newToken, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
+
|
|
|
|
+ return newToken.AccessToken;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public async Task Logout(string accessToken)
|
|
|
|
+ {
|
|
|
|
+ if (string.IsNullOrWhiteSpace(accessToken))
|
|
|
|
+ {
|
|
|
|
+ throw new ArgumentNullException("accessToken");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var existing = _authRepo.Get(new AuthenticationInfoQuery
|
|
|
|
+ {
|
|
|
|
+ Limit = 1,
|
|
|
|
+ AccessToken = accessToken
|
|
|
|
+
|
|
|
|
+ }).Items.FirstOrDefault();
|
|
|
|
+
|
|
|
|
+ if (existing != null)
|
|
|
|
+ {
|
|
|
|
+ existing.IsActive = false;
|
|
|
|
+
|
|
|
|
+ await _authRepo.Update(existing, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
+
|
|
|
|
+ var sessions = Sessions
|
|
|
|
+ .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ .ToList();
|
|
|
|
+
|
|
|
|
+ foreach (var session in sessions)
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ ReportSessionEnded(session.Id);
|
|
|
|
+ }
|
|
|
|
+ catch (Exception ex)
|
|
|
|
+ {
|
|
|
|
+ _logger.ErrorException("Error reporting session ended", ex);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public async Task RevokeUserTokens(string userId)
|
|
|
|
+ {
|
|
|
|
+ var existing = _authRepo.Get(new AuthenticationInfoQuery
|
|
|
|
+ {
|
|
|
|
+ IsActive = true,
|
|
|
|
+ UserId = userId
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ foreach (var info in existing.Items)
|
|
|
|
+ {
|
|
|
|
+ await Logout(info.AccessToken).ConfigureAwait(false);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private bool IsLocalhost(string remoteEndpoint)
|
|
|
|
+ {
|
|
|
|
+ if (string.IsNullOrWhiteSpace(remoteEndpoint))
|
|
|
|
+ {
|
|
|
|
+ throw new ArgumentNullException("remoteEndpoint");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 ||
|
|
|
|
+ remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
+ remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public bool IsLocal(string remoteEndpoint)
|
|
{
|
|
{
|
|
if (string.IsNullOrWhiteSpace(remoteEndpoint))
|
|
if (string.IsNullOrWhiteSpace(remoteEndpoint))
|
|
{
|
|
{
|
|
@@ -1211,12 +1347,11 @@ namespace MediaBrowser.Server.Implementations.Session
|
|
// Private address space:
|
|
// Private address space:
|
|
// http://en.wikipedia.org/wiki/Private_network
|
|
// http://en.wikipedia.org/wiki/Private_network
|
|
|
|
|
|
- return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 ||
|
|
|
|
|
|
+ return IsLocalhost(remoteEndpoint) ||
|
|
remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) ||
|
|
remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) ||
|
|
remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) ||
|
|
remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) ||
|
|
remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) ||
|
|
remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) ||
|
|
- remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
- remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
|
+ remoteEndpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|