mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 09:59:06 -07:00
Migrate authentication db to EF Core
This commit is contained in:
parent
b03f2353d8
commit
a0c6f72762
@ -36,7 +36,6 @@ using Emby.Server.Implementations.Playlists;
|
||||
using Emby.Server.Implementations.Plugins;
|
||||
using Emby.Server.Implementations.QuickConnect;
|
||||
using Emby.Server.Implementations.ScheduledTasks;
|
||||
using Emby.Server.Implementations.Security;
|
||||
using Emby.Server.Implementations.Serialization;
|
||||
using Emby.Server.Implementations.Session;
|
||||
using Emby.Server.Implementations.SyncPlay;
|
||||
@ -57,7 +56,6 @@ using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Chapters;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@ -73,7 +71,6 @@ using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.QuickConnect;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
@ -599,8 +596,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||
|
||||
ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
||||
|
||||
ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||
ServiceCollection.AddSingleton<EncodingHelper>();
|
||||
|
||||
@ -657,8 +652,7 @@ namespace Emby.Server.Implementations
|
||||
|
||||
ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
||||
|
||||
ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
||||
ServiceCollection.AddSingleton<ISessionContext, SessionContext>();
|
||||
ServiceCollection.AddScoped<ISessionContext, SessionContext>();
|
||||
|
||||
ServiceCollection.AddSingleton<IAuthService, AuthService>();
|
||||
ServiceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
|
||||
@ -687,8 +681,6 @@ namespace Emby.Server.Implementations
|
||||
_mediaEncoder = Resolve<IMediaEncoder>();
|
||||
_sessionManager = Resolve<ISessionManager>();
|
||||
|
||||
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
||||
|
||||
SetStaticProperties();
|
||||
|
||||
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Net;
|
||||
@ -17,9 +18,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
_authorizationContext = authorizationContext;
|
||||
}
|
||||
|
||||
public AuthorizationInfo Authenticate(HttpRequest request)
|
||||
public async Task<AuthorizationInfo> Authenticate(HttpRequest request)
|
||||
{
|
||||
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||
var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false);
|
||||
|
||||
if (!auth.HasToken)
|
||||
{
|
||||
|
@ -24,12 +24,18 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
_sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
public Task<SessionInfo> GetSession(HttpContext requestContext)
|
||||
public async Task<SessionInfo> GetSession(HttpContext requestContext)
|
||||
{
|
||||
var authorization = _authContext.GetAuthorizationInfo(requestContext);
|
||||
var authorization = await _authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false);
|
||||
|
||||
var user = authorization.User;
|
||||
return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user);
|
||||
return await _sessionManager.LogSessionActivity(
|
||||
authorization.Client,
|
||||
authorization.Version,
|
||||
authorization.DeviceId,
|
||||
authorization.Device,
|
||||
requestContext.GetNormalizedRemoteIp().ToString(),
|
||||
user).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<SessionInfo> GetSession(object requestContext)
|
||||
|
@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <inheritdoc />
|
||||
public async Task WebSocketRequestHandler(HttpContext context)
|
||||
{
|
||||
_ = _authService.Authenticate(context.Request);
|
||||
_ = await _authService.Authenticate(context.Request).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
|
||||
|
@ -1,407 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Security
|
||||
{
|
||||
public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
|
||||
{
|
||||
public AuthenticationRepository(ILogger<AuthenticationRepository> logger, IServerConfigurationManager config)
|
||||
: base(logger)
|
||||
{
|
||||
DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db");
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
string[] queries =
|
||||
{
|
||||
"create table if not exists Tokens (Id INTEGER PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, UserName TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateLastActivity DATETIME NOT NULL)",
|
||||
"create table if not exists Devices (Id TEXT NOT NULL PRIMARY KEY, CustomName TEXT, Capabilities TEXT)",
|
||||
"drop index if exists idx_AccessTokens",
|
||||
"drop index if exists Tokens1",
|
||||
"drop index if exists Tokens2",
|
||||
|
||||
"create index if not exists Tokens3 on Tokens (AccessToken, DateLastActivity)",
|
||||
"create index if not exists Tokens4 on Tokens (Id, DateLastActivity)",
|
||||
"create index if not exists Devices1 on Devices (Id)"
|
||||
};
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
var tableNewlyCreated = !TableExists(connection, "Tokens");
|
||||
|
||||
connection.RunQueries(queries);
|
||||
|
||||
TryMigrate(connection, tableNewlyCreated);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryMigrate(ManagedConnection connection, bool tableNewlyCreated)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (tableNewlyCreated && TableExists(connection, "AccessTokens"))
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
var existingColumnNames = GetColumnNames(db, "AccessTokens");
|
||||
|
||||
AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames);
|
||||
AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames);
|
||||
AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames);
|
||||
}, TransactionMode);
|
||||
|
||||
connection.RunQueries(new[]
|
||||
{
|
||||
"update accesstokens set DateLastActivity=DateCreated where DateLastActivity is null",
|
||||
"update accesstokens set DeviceName='Unknown' where DeviceName is null",
|
||||
"update accesstokens set AppName='Unknown' where AppName is null",
|
||||
"update accesstokens set AppVersion='1' where AppVersion is null",
|
||||
"INSERT INTO Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) SELECT AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity FROM AccessTokens where deviceid not null and devicename not null and appname not null and isactive=1"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error migrating authentication database");
|
||||
}
|
||||
}
|
||||
|
||||
public void Create(AuthenticationInfo info)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)"))
|
||||
{
|
||||
statement.TryBind("@AccessToken", info.AccessToken);
|
||||
|
||||
statement.TryBind("@DeviceId", info.DeviceId);
|
||||
statement.TryBind("@AppName", info.AppName);
|
||||
statement.TryBind("@AppVersion", info.AppVersion);
|
||||
statement.TryBind("@DeviceName", info.DeviceName);
|
||||
statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||
statement.TryBind("@UserName", info.UserName);
|
||||
statement.TryBind("@IsActive", true);
|
||||
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
|
||||
statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(AuthenticationInfo info)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id"))
|
||||
{
|
||||
statement.TryBind("@Id", info.Id);
|
||||
|
||||
statement.TryBind("@AccessToken", info.AccessToken);
|
||||
|
||||
statement.TryBind("@DeviceId", info.DeviceId);
|
||||
statement.TryBind("@AppName", info.AppName);
|
||||
statement.TryBind("@AppVersion", info.AppVersion);
|
||||
statement.TryBind("@DeviceName", info.DeviceName);
|
||||
statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||
statement.TryBind("@UserName", info.UserName);
|
||||
statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue());
|
||||
statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue());
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(AuthenticationInfo info)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id"))
|
||||
{
|
||||
statement.TryBind("@Id", info.Id);
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private const string BaseSelectText = "select Tokens.Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, DateCreated, DateLastActivity, Devices.CustomName from Tokens left join Devices on Tokens.DeviceId=Devices.Id";
|
||||
|
||||
private static void BindAuthenticationQueryParams(AuthenticationInfoQuery query, IStatement statement)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(query.AccessToken))
|
||||
{
|
||||
statement.TryBind("@AccessToken", query.AccessToken);
|
||||
}
|
||||
|
||||
if (!query.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
statement.TryBind("@UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.DeviceId))
|
||||
{
|
||||
statement.TryBind("@DeviceId", query.DeviceId);
|
||||
}
|
||||
}
|
||||
|
||||
public QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
}
|
||||
|
||||
var commandText = BaseSelectText;
|
||||
|
||||
var whereClauses = new List<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(query.AccessToken))
|
||||
{
|
||||
whereClauses.Add("AccessToken=@AccessToken");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(query.DeviceId))
|
||||
{
|
||||
whereClauses.Add("DeviceId=@DeviceId");
|
||||
}
|
||||
|
||||
if (!query.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
whereClauses.Add("UserId=@UserId");
|
||||
}
|
||||
|
||||
if (query.HasUser.HasValue)
|
||||
{
|
||||
if (query.HasUser.Value)
|
||||
{
|
||||
whereClauses.Add("UserId not null");
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("UserId is null");
|
||||
}
|
||||
}
|
||||
|
||||
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
|
||||
commandText += whereTextWithoutPaging;
|
||||
|
||||
commandText += " ORDER BY DateLastActivity desc";
|
||||
|
||||
if (query.Limit.HasValue || query.StartIndex.HasValue)
|
||||
{
|
||||
var offset = query.StartIndex ?? 0;
|
||||
|
||||
if (query.Limit.HasValue || offset > 0)
|
||||
{
|
||||
commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
var statementTexts = new[]
|
||||
{
|
||||
commandText,
|
||||
"select count (Id) from Tokens" + whereTextWithoutPaging
|
||||
};
|
||||
|
||||
var list = new List<AuthenticationInfo>();
|
||||
var result = new QueryResult<AuthenticationInfo>();
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
var statements = PrepareAll(db, statementTexts);
|
||||
|
||||
using (var statement = statements[0])
|
||||
{
|
||||
BindAuthenticationQueryParams(query, statement);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
list.Add(Get(row));
|
||||
}
|
||||
|
||||
using (var totalCountStatement = statements[1])
|
||||
{
|
||||
BindAuthenticationQueryParams(query, totalCountStatement);
|
||||
|
||||
result.TotalRecordCount = totalCountStatement.ExecuteQuery()
|
||||
.SelectScalarInt()
|
||||
.First();
|
||||
}
|
||||
}
|
||||
},
|
||||
ReadTransactionMode);
|
||||
}
|
||||
|
||||
result.Items = list;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static AuthenticationInfo Get(IReadOnlyList<IResultSetValue> reader)
|
||||
{
|
||||
var info = new AuthenticationInfo
|
||||
{
|
||||
Id = reader[0].ToInt64(),
|
||||
AccessToken = reader[1].ToString()
|
||||
};
|
||||
|
||||
if (reader[2].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.DeviceId = reader[2].ToString();
|
||||
}
|
||||
|
||||
if (reader[3].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.AppName = reader[3].ToString();
|
||||
}
|
||||
|
||||
if (reader[4].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.AppVersion = reader[4].ToString();
|
||||
}
|
||||
|
||||
if (reader[5].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.DeviceName = reader[5].ToString();
|
||||
}
|
||||
|
||||
if (reader[6].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.UserId = new Guid(reader[6].ToString());
|
||||
}
|
||||
|
||||
if (reader[7].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.UserName = reader[7].ToString();
|
||||
}
|
||||
|
||||
info.DateCreated = reader[8].ReadDateTime();
|
||||
|
||||
if (reader[9].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.DateLastActivity = reader[9].ReadDateTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
info.DateLastActivity = info.DateCreated;
|
||||
}
|
||||
|
||||
if (reader[10].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.DeviceName = reader[10].ToString();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public DeviceOptions GetDeviceOptions(string deviceId)
|
||||
{
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
return connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId"))
|
||||
{
|
||||
statement.TryBind("@DeviceId", deviceId);
|
||||
|
||||
var result = new DeviceOptions(deviceId);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
if (row[0].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
result.CustomName = row[0].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}, ReadTransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))"))
|
||||
{
|
||||
statement.TryBind("@Id", deviceId);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.CustomName))
|
||||
{
|
||||
statement.TryBindNull("@CustomName");
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBind("@CustomName", options.CustomName);
|
||||
}
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Data.Queries;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
@ -23,7 +24,6 @@ using MediaBrowser.Controller.Events;
|
||||
using MediaBrowser.Controller.Events.Session;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@ -52,7 +52,6 @@ namespace Emby.Server.Implementations.Session
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IAuthenticationRepository _authRepo;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
|
||||
/// <summary>
|
||||
@ -75,7 +74,6 @@ namespace Emby.Server.Implementations.Session
|
||||
IDtoService dtoService,
|
||||
IImageProcessor imageProcessor,
|
||||
IServerApplicationHost appHost,
|
||||
IAuthenticationRepository authRepo,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
@ -88,7 +86,6 @@ namespace Emby.Server.Implementations.Session
|
||||
_dtoService = dtoService;
|
||||
_imageProcessor = imageProcessor;
|
||||
_appHost = appHost;
|
||||
_authRepo = authRepo;
|
||||
_deviceManager = deviceManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
|
||||
@ -1486,7 +1483,7 @@ namespace Emby.Server.Implementations.Session
|
||||
throw new SecurityException("User is at their maximum number of sessions.");
|
||||
}
|
||||
|
||||
var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName);
|
||||
var token = await GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false);
|
||||
|
||||
var session = await LogSessionActivity(
|
||||
request.App,
|
||||
@ -1509,21 +1506,21 @@ namespace Emby.Server.Implementations.Session
|
||||
return returnResult;
|
||||
}
|
||||
|
||||
private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
|
||||
private async Task<string> GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
|
||||
{
|
||||
var existing = _authRepo.Get(
|
||||
new AuthenticationInfoQuery
|
||||
var existing = (await _deviceManager.GetDevices(
|
||||
new DeviceQuery
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
UserId = user.Id,
|
||||
Limit = 1
|
||||
}).Items.FirstOrDefault();
|
||||
}).ConfigureAwait(false)).Items.FirstOrDefault();
|
||||
|
||||
var allExistingForDevice = _authRepo.Get(
|
||||
new AuthenticationInfoQuery
|
||||
var allExistingForDevice = (await _deviceManager.GetDevices(
|
||||
new DeviceQuery
|
||||
{
|
||||
DeviceId = deviceId
|
||||
}).Items;
|
||||
}).ConfigureAwait(false)).Items;
|
||||
|
||||
foreach (var auth in allExistingForDevice)
|
||||
{
|
||||
@ -1531,7 +1528,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
try
|
||||
{
|
||||
Logout(auth);
|
||||
await Logout(auth).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -1546,29 +1543,14 @@ namespace Emby.Server.Implementations.Session
|
||||
return existing.AccessToken;
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var newToken = new AuthenticationInfo
|
||||
{
|
||||
AppName = app,
|
||||
AppVersion = appVersion,
|
||||
DateCreated = now,
|
||||
DateLastActivity = now,
|
||||
DeviceId = deviceId,
|
||||
DeviceName = deviceName,
|
||||
UserId = user.Id,
|
||||
AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
|
||||
UserName = user.Username
|
||||
};
|
||||
|
||||
_logger.LogInformation("Creating new access token for user {0}", user.Id);
|
||||
_authRepo.Create(newToken);
|
||||
var device = await _deviceManager.CreateDevice(new Device(user.Id, app, appVersion, deviceName, deviceId)).ConfigureAwait(false);
|
||||
|
||||
return newToken.AccessToken;
|
||||
return device.AccessToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Logout(string accessToken)
|
||||
public async Task Logout(string accessToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
@ -1577,27 +1559,27 @@ namespace Emby.Server.Implementations.Session
|
||||
throw new ArgumentNullException(nameof(accessToken));
|
||||
}
|
||||
|
||||
var existing = _authRepo.Get(
|
||||
new AuthenticationInfoQuery
|
||||
var existing = (await _deviceManager.GetDevices(
|
||||
new DeviceQuery
|
||||
{
|
||||
Limit = 1,
|
||||
AccessToken = accessToken
|
||||
}).Items;
|
||||
}).ConfigureAwait(false)).Items;
|
||||
|
||||
if (existing.Count > 0)
|
||||
{
|
||||
Logout(existing[0]);
|
||||
await Logout(existing[0]).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Logout(AuthenticationInfo existing)
|
||||
public async Task Logout(Device existing)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
_logger.LogInformation("Logging out access token {0}", existing.AccessToken);
|
||||
|
||||
_authRepo.Delete(existing);
|
||||
await _deviceManager.DeleteDevice(existing).ConfigureAwait(false);
|
||||
|
||||
var sessions = Sessions
|
||||
.Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase))
|
||||
@ -1617,30 +1599,24 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RevokeUserTokens(Guid userId, string currentAccessToken)
|
||||
public async Task RevokeUserTokens(Guid userId, string currentAccessToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
var existing = _authRepo.Get(new AuthenticationInfoQuery
|
||||
var existing = await _deviceManager.GetDevices(new DeviceQuery
|
||||
{
|
||||
UserId = userId
|
||||
});
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
foreach (var info in existing.Items)
|
||||
{
|
||||
if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logout(info);
|
||||
await Logout(info).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RevokeToken(string token)
|
||||
{
|
||||
Logout(token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reports the capabilities.
|
||||
/// </summary>
|
||||
@ -1792,7 +1768,7 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<SessionInfo> GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion)
|
||||
public Task<SessionInfo> GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
@ -1825,20 +1801,20 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
|
||||
public async Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
|
||||
{
|
||||
var items = _authRepo.Get(new AuthenticationInfoQuery
|
||||
var items = (await _deviceManager.GetDevices(new DeviceQuery
|
||||
{
|
||||
AccessToken = token,
|
||||
Limit = 1
|
||||
}).Items;
|
||||
}).ConfigureAwait(false)).Items;
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null);
|
||||
return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -40,11 +40,11 @@ namespace Jellyfin.Api.Auth
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var authorizationInfo = _authService.Authenticate(Request);
|
||||
var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false);
|
||||
var role = UserRoles.User;
|
||||
if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
|
||||
{
|
||||
@ -68,16 +68,16 @@ namespace Jellyfin.Api.Auth
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
catch (AuthenticationException ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Error authenticating with {Handler}", nameof(CustomAuthenticationHandler));
|
||||
return Task.FromResult(AuthenticateResult.NoResult());
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
catch (SecurityException ex)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail(ex));
|
||||
return AuthenticateResult.Fail(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery] bool isLocked = false)
|
||||
{
|
||||
var userId = _authContext.GetAuthorizationInfo(Request).UserId;
|
||||
var userId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).UserId;
|
||||
|
||||
var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
|
||||
{
|
||||
|
@ -3,8 +3,8 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Data.Queries;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Devices;
|
||||
using MediaBrowser.Model.Querying;
|
||||
@ -21,22 +21,18 @@ namespace Jellyfin.Api.Controllers
|
||||
public class DevicesController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
private readonly IAuthenticationRepository _authenticationRepository;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DevicesController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param>
|
||||
/// <param name="authenticationRepository">Instance of <see cref="IAuthenticationRepository"/> interface.</param>
|
||||
/// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
|
||||
public DevicesController(
|
||||
IDeviceManager deviceManager,
|
||||
IAuthenticationRepository authenticationRepository,
|
||||
ISessionManager sessionManager)
|
||||
{
|
||||
_deviceManager = deviceManager;
|
||||
_authenticationRepository = authenticationRepository;
|
||||
_sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
@ -111,7 +107,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery, Required] string id,
|
||||
[FromBody, Required] DeviceOptions deviceOptions)
|
||||
{
|
||||
var existingDeviceOptions = _deviceManager.GetDeviceOptions(id);
|
||||
var existingDeviceOptions = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false);
|
||||
if (existingDeviceOptions == null)
|
||||
{
|
||||
return NotFound();
|
||||
@ -131,19 +127,19 @@ namespace Jellyfin.Api.Controllers
|
||||
[HttpDelete]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public ActionResult DeleteDevice([FromQuery, Required] string id)
|
||||
public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id)
|
||||
{
|
||||
var existingDevice = _deviceManager.GetDevice(id);
|
||||
var existingDevice = await _deviceManager.GetDevice(id).ConfigureAwait(false);
|
||||
if (existingDevice == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items;
|
||||
var sessions = await _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }).ConfigureAwait(false);
|
||||
|
||||
foreach (var session in sessions)
|
||||
foreach (var session in sessions.Items)
|
||||
{
|
||||
_sessionManager.Logout(session);
|
||||
await _sessionManager.Logout(session).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
|
@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] ImageType imageType,
|
||||
[FromQuery] int? index = null)
|
||||
{
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
|
||||
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
|
||||
}
|
||||
@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] ImageType imageType,
|
||||
[FromRoute] int index)
|
||||
{
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
|
||||
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
|
||||
}
|
||||
@ -190,7 +190,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] ImageType imageType,
|
||||
[FromQuery] int? index = null)
|
||||
{
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
|
||||
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
|
||||
}
|
||||
@ -234,7 +234,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] ImageType imageType,
|
||||
[FromRoute] int index)
|
||||
{
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
|
||||
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image.");
|
||||
}
|
||||
|
@ -331,10 +331,10 @@ namespace Jellyfin.Api.Controllers
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public ActionResult DeleteItem(Guid itemId)
|
||||
public async Task<ActionResult> DeleteItem(Guid itemId)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(itemId);
|
||||
var auth = _authContext.GetAuthorizationInfo(Request);
|
||||
var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
var user = auth.User;
|
||||
|
||||
if (!item.CanDelete(user))
|
||||
@ -361,7 +361,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
|
||||
public async Task<ActionResult> DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
|
||||
{
|
||||
if (ids.Length == 0)
|
||||
{
|
||||
@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers
|
||||
foreach (var i in ids)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(i);
|
||||
var auth = _authContext.GetAuthorizationInfo(Request);
|
||||
var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
var user = auth.User;
|
||||
|
||||
if (!item.CanDelete(user))
|
||||
@ -627,7 +627,7 @@ namespace Jellyfin.Api.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var auth = _authContext.GetAuthorizationInfo(Request);
|
||||
var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
|
||||
var user = auth.User;
|
||||
|
||||
|
@ -123,7 +123,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery, ParameterObsolete] bool? allowAudioStreamCopy,
|
||||
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto)
|
||||
{
|
||||
var authInfo = _authContext.GetAuthorizationInfo(Request);
|
||||
var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
|
||||
var profile = playbackInfoDto?.DeviceProfile;
|
||||
_logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
|
||||
|
@ -171,7 +171,8 @@ namespace Jellyfin.Api.Controllers
|
||||
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
|
||||
{
|
||||
await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
|
||||
var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
await _transcodingJobHelper.KillTranscodingJobs(authInfo.DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
|
||||
@ -320,7 +321,8 @@ namespace Jellyfin.Api.Controllers
|
||||
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
|
||||
{
|
||||
await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
|
||||
var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
await _transcodingJobHelper.KillTranscodingJobs(authInfo.DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false);
|
||||
|
@ -471,11 +471,11 @@ namespace Jellyfin.Api.Controllers
|
||||
[HttpPost("Sessions/Logout")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult ReportSessionEnded()
|
||||
public async Task<ActionResult> ReportSessionEnded()
|
||||
{
|
||||
AuthorizationInfo auth = _authContext.GetAuthorizationInfo(Request);
|
||||
AuthorizationInfo auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
|
||||
_sessionManager.Logout(auth.Token);
|
||||
await _sessionManager.Logout(auth.Token).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
@ -361,7 +361,7 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
long positionTicks = 0;
|
||||
|
||||
var accessToken = _authContext.GetAuthorizationInfo(Request).Token;
|
||||
var accessToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token;
|
||||
|
||||
while (positionTicks < runtime)
|
||||
{
|
||||
|
@ -116,9 +116,9 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromQuery] bool enableRedirection = true)
|
||||
{
|
||||
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
|
||||
_authorizationContext.GetAuthorizationInfo(Request).DeviceId = deviceId;
|
||||
(await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId = deviceId;
|
||||
|
||||
var authInfo = _authorizationContext.GetAuthorizationInfo(Request);
|
||||
var authInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
|
||||
_logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
|
||||
|
||||
|
@ -77,11 +77,11 @@ namespace Jellyfin.Api.Controllers
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<UserDto>> GetUsers(
|
||||
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers(
|
||||
[FromQuery] bool? isHidden,
|
||||
[FromQuery] bool? isDisabled)
|
||||
{
|
||||
var users = Get(isHidden, isDisabled, false, false);
|
||||
var users = await Get(isHidden, isDisabled, false, false).ConfigureAwait(false);
|
||||
return Ok(users);
|
||||
}
|
||||
|
||||
@ -92,15 +92,15 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <returns>An <see cref="IEnumerable{UserDto}"/> containing the public users.</returns>
|
||||
[HttpGet("Public")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<UserDto>> GetPublicUsers()
|
||||
public async Task<ActionResult<IEnumerable<UserDto>>> GetPublicUsers()
|
||||
{
|
||||
// If the startup wizard hasn't been completed then just return all users
|
||||
if (!_config.Configuration.IsStartupWizardCompleted)
|
||||
{
|
||||
return Ok(Get(false, false, false, false));
|
||||
return Ok(await Get(false, false, false, false).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
return Ok(Get(false, false, true, true));
|
||||
return Ok(await Get(false, false, true, true).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers
|
||||
public async Task<ActionResult> DeleteUser([FromRoute, Required] Guid userId)
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
_sessionManager.RevokeUserTokens(user.Id, null);
|
||||
await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false);
|
||||
await _userManager.DeleteUserAsync(userId).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<AuthenticationResult>> AuthenticateUserByName([FromBody, Required] AuthenticateUserByName request)
|
||||
{
|
||||
var auth = _authContext.GetAuthorizationInfo(Request);
|
||||
var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
@ -230,7 +230,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<AuthenticationResult>> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request)
|
||||
{
|
||||
var auth = _authContext.GetAuthorizationInfo(Request);
|
||||
var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
@ -271,7 +271,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromBody, Required] UpdateUserPassword request)
|
||||
{
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
|
||||
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password.");
|
||||
}
|
||||
@ -303,9 +303,9 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false);
|
||||
|
||||
var currentToken = _authContext.GetAuthorizationInfo(Request).Token;
|
||||
var currentToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token;
|
||||
|
||||
_sessionManager.RevokeUserTokens(user.Id, currentToken);
|
||||
await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
@ -325,11 +325,11 @@ namespace Jellyfin.Api.Controllers
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public ActionResult UpdateUserEasyPassword(
|
||||
public async Task<ActionResult> UpdateUserEasyPassword(
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromBody, Required] UpdateUserEasyPassword request)
|
||||
{
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
|
||||
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the easy password.");
|
||||
}
|
||||
@ -343,11 +343,11 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
if (request.ResetPassword)
|
||||
{
|
||||
_userManager.ResetEasyPassword(user);
|
||||
await _userManager.ResetEasyPassword(user).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword);
|
||||
await _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromBody, Required] UserDto updateUser)
|
||||
{
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false))
|
||||
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false).ConfigureAwait(false))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed.");
|
||||
}
|
||||
@ -431,8 +431,8 @@ namespace Jellyfin.Api.Controllers
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one enabled user in the system.");
|
||||
}
|
||||
|
||||
var currentToken = _authContext.GetAuthorizationInfo(Request).Token;
|
||||
_sessionManager.RevokeUserTokens(user.Id, currentToken);
|
||||
var currentToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token;
|
||||
await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false);
|
||||
@ -456,7 +456,7 @@ namespace Jellyfin.Api.Controllers
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromBody, Required] UserConfiguration userConfig)
|
||||
{
|
||||
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false))
|
||||
if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false).ConfigureAwait(false))
|
||||
{
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed");
|
||||
}
|
||||
@ -555,7 +555,7 @@ namespace Jellyfin.Api.Controllers
|
||||
return _userManager.GetUserDto(user);
|
||||
}
|
||||
|
||||
private IEnumerable<UserDto> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork)
|
||||
private async Task<IEnumerable<UserDto>> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork)
|
||||
{
|
||||
var users = _userManager.Users;
|
||||
|
||||
@ -571,7 +571,7 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
if (filterByDevice)
|
||||
{
|
||||
var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId;
|
||||
var deviceId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.UserViewDtos;
|
||||
@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <returns>An <see cref="OkResult"/> containing the user views.</returns>
|
||||
[HttpGet("Users/{userId}/Views")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
|
||||
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetUserViews(
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromQuery] bool? includeExternalContent,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] presetViews,
|
||||
@ -86,7 +87,7 @@ namespace Jellyfin.Api.Controllers
|
||||
query.PresetViews = presetViews;
|
||||
}
|
||||
|
||||
var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
|
||||
var app = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Client ?? string.Empty;
|
||||
if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows };
|
||||
|
@ -468,7 +468,7 @@ namespace Jellyfin.Api.Helpers
|
||||
/// <returns>A <see cref="Task"/> containing the <see cref="LiveStreamResponse"/>.</returns>
|
||||
public async Task<LiveStreamResponse> OpenMediaSource(HttpRequest httpRequest, LiveStreamRequest request)
|
||||
{
|
||||
var authInfo = _authContext.GetAuthorizationInfo(httpRequest);
|
||||
var authInfo = await _authContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false);
|
||||
|
||||
var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
|
@ -60,9 +60,9 @@ namespace Jellyfin.Api.Helpers
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="restrictUserPreferences">Whether to restrict the user preferences.</param>
|
||||
/// <returns>A <see cref="bool"/> whether the user can update the entry.</returns>
|
||||
internal static bool AssertCanUpdateUser(IAuthorizationContext authContext, HttpRequest requestContext, Guid userId, bool restrictUserPreferences)
|
||||
internal static async Task<bool> AssertCanUpdateUser(IAuthorizationContext authContext, HttpRequest requestContext, Guid userId, bool restrictUserPreferences)
|
||||
{
|
||||
var auth = authContext.GetAuthorizationInfo(requestContext);
|
||||
var auth = await authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false);
|
||||
|
||||
var authenticatedUser = auth.User;
|
||||
|
||||
@ -78,7 +78,7 @@ namespace Jellyfin.Api.Helpers
|
||||
|
||||
internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request)
|
||||
{
|
||||
var authorization = authContext.GetAuthorizationInfo(request);
|
||||
var authorization = await authContext.GetAuthorizationInfo(request).ConfigureAwait(false);
|
||||
var user = authorization.User;
|
||||
var session = await sessionManager.LogSessionActivity(
|
||||
authorization.Client,
|
||||
|
@ -17,9 +17,7 @@ using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
@ -101,7 +99,7 @@ namespace Jellyfin.Api.Helpers
|
||||
EnableDlnaHeaders = enableDlnaHeaders
|
||||
};
|
||||
|
||||
var auth = authorizationContext.GetAuthorizationInfo(httpRequest);
|
||||
var auth = await authorizationContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false);
|
||||
if (!auth.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
state.User = userManager.GetUserById(auth.UserId);
|
||||
|
@ -489,7 +489,7 @@ namespace Jellyfin.Api.Helpers
|
||||
|
||||
if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
||||
{
|
||||
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||
var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false);
|
||||
if (auth.User != null && !auth.User.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
|
||||
{
|
||||
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
|
||||
|
@ -6,6 +6,7 @@ using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Data.Queries;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Devices;
|
||||
@ -55,6 +56,17 @@ namespace Jellyfin.Server.Implementations.Devices
|
||||
DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Device> CreateDevice(Device device)
|
||||
{
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
dbContext.Devices.Add(device);
|
||||
|
||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
return device;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<DeviceOptions?> GetDeviceOptions(string deviceId)
|
||||
{
|
||||
@ -90,6 +102,61 @@ namespace Jellyfin.Server.Implementations.Devices
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<QueryResult<Device>> GetDevices(DeviceQuery query)
|
||||
{
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
var devices = dbContext.Devices.AsQueryable();
|
||||
|
||||
if (query.UserId.HasValue)
|
||||
{
|
||||
devices = devices.Where(device => device.UserId == query.UserId.Value);
|
||||
}
|
||||
|
||||
if (query.DeviceId != null)
|
||||
{
|
||||
devices = devices.Where(device => device.DeviceId == query.DeviceId);
|
||||
}
|
||||
|
||||
if (query.AccessToken != null)
|
||||
{
|
||||
devices = devices.Where(device => device.AccessToken == query.AccessToken);
|
||||
}
|
||||
|
||||
if (query.Skip.HasValue)
|
||||
{
|
||||
devices = devices.Skip(query.Skip.Value);
|
||||
}
|
||||
|
||||
var count = await devices.CountAsync().ConfigureAwait(false);
|
||||
|
||||
if (query.Limit.HasValue)
|
||||
{
|
||||
devices = devices.Take(query.Limit.Value);
|
||||
}
|
||||
|
||||
return new QueryResult<Device>
|
||||
{
|
||||
Items = await devices.ToListAsync().ConfigureAwait(false),
|
||||
StartIndex = query.Skip ?? 0,
|
||||
TotalRecordCount = count
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<QueryResult<DeviceInfo>> GetDeviceInfos(DeviceQuery query)
|
||||
{
|
||||
var devices = await GetDevices(query).ConfigureAwait(false);
|
||||
|
||||
return new QueryResult<DeviceInfo>
|
||||
{
|
||||
Items = devices.Items.Select(ToDeviceInfo).ToList(),
|
||||
StartIndex = devices.StartIndex,
|
||||
TotalRecordCount = devices.TotalRecordCount
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId, bool? supportsSync)
|
||||
{
|
||||
@ -117,6 +184,12 @@ namespace Jellyfin.Server.Implementations.Devices
|
||||
return new QueryResult<DeviceInfo>(array);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteDevice(Device device)
|
||||
{
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanAccessDevice(User user, string deviceId)
|
||||
{
|
||||
|
@ -214,6 +214,15 @@ namespace Jellyfin.Server.Implementations
|
||||
modelBuilder.Entity<Device>()
|
||||
.HasIndex(entity => new { entity.DeviceId, entity.DateLastActivity });
|
||||
|
||||
modelBuilder.Entity<Device>()
|
||||
.HasIndex(entity => new { entity.AccessToken, entity.DateLastActivity });
|
||||
|
||||
modelBuilder.Entity<Device>()
|
||||
.HasIndex(entity => new { entity.UserId, entity.DeviceId });
|
||||
|
||||
modelBuilder.Entity<Device>()
|
||||
.HasIndex(entity => entity.DeviceId);
|
||||
|
||||
modelBuilder.Entity<DeviceOptions>()
|
||||
.HasIndex(entity => entity.DeviceId)
|
||||
.IsUnique();
|
||||
|
645
Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs
generated
Normal file
645
Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs
generated
Normal file
@ -0,0 +1,645 @@
|
||||
#pragma warning disable CS1591
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
[DbContext(typeof(JellyfinDb))]
|
||||
[Migration("20210521032224_AddDevices")]
|
||||
partial class AddDevices
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("jellyfin")
|
||||
.HasAnnotation("ProductVersion", "5.0.5");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("DayOfWeek")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("EndHour")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("StartHour")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AccessSchedules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("DateCreated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ItemId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LogSeverity")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ShortOverview")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ActivityLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Client")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId", "ItemId", "Client", "Key")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CustomItemDisplayPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ChromecastVersion")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Client")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DashboardTheme")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EnableNextVideoInfoOverlay")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("IndexBy")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ScrollDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowBackdrop")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowSidebar")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SkipBackwardLength")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SkipForwardLength")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("TvHome")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId", "ItemId", "Client")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DisplayPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("DisplayPreferencesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DisplayPreferencesId");
|
||||
|
||||
b.ToTable("HomeSection");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ImageInfos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Client")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("IndexBy")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("ItemId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("RememberIndexing")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("RememberSorting")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SortBy")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ViewType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("ItemDisplayPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("Permission_Permissions_Guid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Value")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId", "Kind")
|
||||
.IsUnique()
|
||||
.HasFilter("[UserId] IS NOT NULL");
|
||||
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("Preference_Preferences_Guid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(65535)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId", "Kind")
|
||||
.IsUnique()
|
||||
.HasFilter("[UserId] IS NOT NULL");
|
||||
|
||||
b.ToTable("Preferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("AccessToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateCreated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AccessToken")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ApiKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AccessToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AppName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AppVersion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateCreated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateLastActivity")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.HasIndex("AccessToken", "DateLastActivity");
|
||||
|
||||
b.HasIndex("DeviceId", "DateLastActivity");
|
||||
|
||||
b.HasIndex("UserId", "DeviceId");
|
||||
|
||||
b.ToTable("Devices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CustomName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DeviceOptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AudioLanguagePreference")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AuthenticationProviderId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("DisplayCollectionsView")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DisplayMissingEpisodes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("EasyPassword")
|
||||
.HasMaxLength(65535)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EnableAutoLogin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableLocalPassword")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableNextEpisodeAutoPlay")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableUserPreferenceAccess")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HidePlayedInLatest")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("InternalId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("InvalidLoginAttemptCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastActivityDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("LastLoginDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxActiveSessions")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("MaxParentalAgeRating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("MustUpdatePassword")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.HasMaxLength(65535)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordResetProviderId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PlayDefaultAudioTrack")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("RememberAudioSelections")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("RememberSubtitleSelections")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RemoteClientBitrateLimit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SubtitleLanguagePreference")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SubtitleMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SyncPlayAccess")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT")
|
||||
.UseCollation("NOCASE");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("AccessSchedules")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("DisplayPreferences")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
|
||||
.WithMany("HomeSections")
|
||||
.HasForeignKey("DisplayPreferencesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithOne("ProfileImage")
|
||||
.HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("ItemDisplayPreferences")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("Permissions")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("Preferences")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||
{
|
||||
b.Navigation("HomeSections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("AccessSchedules");
|
||||
|
||||
b.Navigation("DisplayPreferences");
|
||||
|
||||
b.Navigation("ItemDisplayPreferences");
|
||||
|
||||
b.Navigation("Permissions");
|
||||
|
||||
b.Navigation("Preferences");
|
||||
|
||||
b.Navigation("ProfileImage");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1601
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
public partial class AddDevices : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiKeys",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false),
|
||||
AccessToken = table.Column<Guid>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiKeys", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DeviceOptions",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
DeviceId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
CustomName = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DeviceOptions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Devices",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
AccessToken = table.Column<string>(type: "TEXT", nullable: false),
|
||||
AppName = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false),
|
||||
AppVersion = table.Column<string>(type: "TEXT", maxLength: 32, nullable: false),
|
||||
DeviceName = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false),
|
||||
DeviceId = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false),
|
||||
IsActive = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
DateLastActivity = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Devices", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Devices_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiKeys_AccessToken",
|
||||
schema: "jellyfin",
|
||||
table: "ApiKeys",
|
||||
column: "AccessToken",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeviceOptions_DeviceId",
|
||||
schema: "jellyfin",
|
||||
table: "DeviceOptions",
|
||||
column: "DeviceId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Devices_AccessToken_DateLastActivity",
|
||||
schema: "jellyfin",
|
||||
table: "Devices",
|
||||
columns: new[] { "AccessToken", "DateLastActivity" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Devices_DeviceId",
|
||||
schema: "jellyfin",
|
||||
table: "Devices",
|
||||
column: "DeviceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Devices_DeviceId_DateLastActivity",
|
||||
schema: "jellyfin",
|
||||
table: "Devices",
|
||||
columns: new[] { "DeviceId", "DateLastActivity" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Devices_UserId_DeviceId",
|
||||
schema: "jellyfin",
|
||||
table: "Devices",
|
||||
columns: new[] { "UserId", "DeviceId" });
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiKeys",
|
||||
schema: "jellyfin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "DeviceOptions",
|
||||
schema: "jellyfin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Devices",
|
||||
schema: "jellyfin");
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("jellyfin")
|
||||
.HasAnnotation("ProductVersion", "5.0.3");
|
||||
.HasAnnotation("ProductVersion", "5.0.5");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
@ -332,6 +332,107 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.ToTable("Preferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("AccessToken")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateCreated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AccessToken")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ApiKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AccessToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AppName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AppVersion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateCreated")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateLastActivity")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId");
|
||||
|
||||
b.HasIndex("AccessToken", "DateLastActivity");
|
||||
|
||||
b.HasIndex("DeviceId", "DateLastActivity");
|
||||
|
||||
b.HasIndex("UserId", "DeviceId");
|
||||
|
||||
b.ToTable("Devices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CustomName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DeviceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DeviceOptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -505,6 +606,17 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||
{
|
||||
b.Navigation("HomeSections");
|
||||
|
@ -4,39 +4,39 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer.Security
|
||||
namespace Jellyfin.Server.Implementations.Security
|
||||
{
|
||||
public class AuthorizationContext : IAuthorizationContext
|
||||
{
|
||||
private readonly IAuthenticationRepository _authRepo;
|
||||
private readonly JellyfinDb _jellyfinDb;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
public AuthorizationContext(IAuthenticationRepository authRepo, IUserManager userManager)
|
||||
public AuthorizationContext(JellyfinDb jellyfinDb, IUserManager userManager)
|
||||
{
|
||||
_authRepo = authRepo;
|
||||
_jellyfinDb = jellyfinDb;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext)
|
||||
public Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext)
|
||||
{
|
||||
if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached))
|
||||
if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached) && cached != null)
|
||||
{
|
||||
return (AuthorizationInfo)cached;
|
||||
return Task.FromResult((AuthorizationInfo)cached);
|
||||
}
|
||||
|
||||
return GetAuthorization(requestContext);
|
||||
}
|
||||
|
||||
public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext)
|
||||
public async Task<AuthorizationInfo> GetAuthorizationInfo(HttpRequest requestContext)
|
||||
{
|
||||
var auth = GetAuthorizationDictionary(requestContext);
|
||||
var authInfo = GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
|
||||
var authInfo = await GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query).ConfigureAwait(false);
|
||||
return authInfo;
|
||||
}
|
||||
|
||||
@ -45,35 +45,37 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
/// </summary>
|
||||
/// <param name="httpReq">The HTTP req.</param>
|
||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||
private AuthorizationInfo GetAuthorization(HttpContext httpReq)
|
||||
private async Task<AuthorizationInfo> GetAuthorization(HttpContext httpReq)
|
||||
{
|
||||
var auth = GetAuthorizationDictionary(httpReq);
|
||||
var authInfo = GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query);
|
||||
var authInfo = await GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query).ConfigureAwait(false);
|
||||
|
||||
httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo;
|
||||
return authInfo;
|
||||
}
|
||||
|
||||
private AuthorizationInfo GetAuthorizationInfoFromDictionary(
|
||||
in Dictionary<string, string> auth,
|
||||
in IHeaderDictionary headers,
|
||||
in IQueryCollection queryString)
|
||||
private async Task<AuthorizationInfo> GetAuthorizationInfoFromDictionary(
|
||||
IReadOnlyDictionary<string, string>? auth,
|
||||
IHeaderDictionary headers,
|
||||
IQueryCollection queryString)
|
||||
{
|
||||
string deviceId = null;
|
||||
string device = null;
|
||||
string client = null;
|
||||
string version = null;
|
||||
string token = null;
|
||||
string? deviceId = null;
|
||||
string? deviceName = null;
|
||||
string? client = null;
|
||||
string? version = null;
|
||||
string? token = null;
|
||||
|
||||
if (auth != null)
|
||||
{
|
||||
auth.TryGetValue("DeviceId", out deviceId);
|
||||
auth.TryGetValue("Device", out device);
|
||||
auth.TryGetValue("Device", out deviceName);
|
||||
auth.TryGetValue("Client", out client);
|
||||
auth.TryGetValue("Version", out version);
|
||||
auth.TryGetValue("Token", out token);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1508
|
||||
// headers can return StringValues.Empty
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
token = headers["X-Emby-Token"];
|
||||
@ -98,7 +100,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
var authInfo = new AuthorizationInfo
|
||||
{
|
||||
Client = client,
|
||||
Device = device,
|
||||
Device = deviceName,
|
||||
DeviceId = deviceId,
|
||||
Version = version,
|
||||
Token = token,
|
||||
@ -111,80 +113,69 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
// Request doesn't contain a token.
|
||||
return authInfo;
|
||||
}
|
||||
#pragma warning restore CA1508
|
||||
|
||||
authInfo.HasToken = true;
|
||||
var result = _authRepo.Get(new AuthenticationInfoQuery
|
||||
{
|
||||
AccessToken = token
|
||||
});
|
||||
var device = await _jellyfinDb.Devices.FirstOrDefaultAsync(d => d.AccessToken == token).ConfigureAwait(false);
|
||||
|
||||
if (result.Items.Count > 0)
|
||||
if (device != null)
|
||||
{
|
||||
authInfo.IsAuthenticated = true;
|
||||
}
|
||||
|
||||
var originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
|
||||
|
||||
if (originalAuthenticationInfo != null)
|
||||
if (device != null)
|
||||
{
|
||||
var updateToken = false;
|
||||
|
||||
// TODO: Remove these checks for IsNullOrWhiteSpace
|
||||
if (string.IsNullOrWhiteSpace(authInfo.Client))
|
||||
{
|
||||
authInfo.Client = originalAuthenticationInfo.AppName;
|
||||
authInfo.Client = device.AppName;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
|
||||
{
|
||||
authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
|
||||
authInfo.DeviceId = device.DeviceId;
|
||||
}
|
||||
|
||||
// Temporary. TODO - allow clients to specify that the token has been shared with a casting device
|
||||
var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
|
||||
var allowTokenInfoUpdate = !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authInfo.Device))
|
||||
{
|
||||
authInfo.Device = originalAuthenticationInfo.DeviceName;
|
||||
authInfo.Device = device.DeviceName;
|
||||
}
|
||||
else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
|
||||
else if (!string.Equals(authInfo.Device, device.DeviceName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (allowTokenInfoUpdate)
|
||||
{
|
||||
updateToken = true;
|
||||
originalAuthenticationInfo.DeviceName = authInfo.Device;
|
||||
device.DeviceName = authInfo.Device;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authInfo.Version))
|
||||
{
|
||||
authInfo.Version = originalAuthenticationInfo.AppVersion;
|
||||
authInfo.Version = device.AppVersion;
|
||||
}
|
||||
else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
|
||||
else if (!string.Equals(authInfo.Version, device.AppVersion, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (allowTokenInfoUpdate)
|
||||
{
|
||||
updateToken = true;
|
||||
originalAuthenticationInfo.AppVersion = authInfo.Version;
|
||||
device.AppVersion = authInfo.Version;
|
||||
}
|
||||
}
|
||||
|
||||
if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
|
||||
if ((DateTime.UtcNow - device.DateLastActivity).TotalMinutes > 3)
|
||||
{
|
||||
originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
|
||||
device.DateLastActivity = DateTime.UtcNow;
|
||||
updateToken = true;
|
||||
}
|
||||
|
||||
if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
|
||||
if (!device.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
|
||||
|
||||
if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
originalAuthenticationInfo.UserName = authInfo.User.Username;
|
||||
updateToken = true;
|
||||
}
|
||||
|
||||
authInfo.User = _userManager.GetUserById(device.UserId);
|
||||
authInfo.IsApiKey = false;
|
||||
}
|
||||
else
|
||||
@ -194,7 +185,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
|
||||
if (updateToken)
|
||||
{
|
||||
_authRepo.Update(originalAuthenticationInfo);
|
||||
_jellyfinDb.Devices.Update(device);
|
||||
await _jellyfinDb.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,7 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
/// </summary>
|
||||
/// <param name="httpReq">The HTTP req.</param>
|
||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||
private Dictionary<string, string> GetAuthorizationDictionary(HttpContext httpReq)
|
||||
private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq)
|
||||
{
|
||||
var auth = httpReq.Request.Headers["X-Emby-Authorization"];
|
||||
|
||||
@ -223,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
/// </summary>
|
||||
/// <param name="httpReq">The HTTP req.</param>
|
||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||
private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq)
|
||||
private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq)
|
||||
{
|
||||
var auth = httpReq.Headers["X-Emby-Authorization"];
|
||||
|
||||
@ -240,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
/// </summary>
|
||||
/// <param name="authorizationHeader">The authorization header.</param>
|
||||
/// <returns>Dictionary{System.StringSystem.String}.</returns>
|
||||
private Dictionary<string, string> GetAuthorization(string authorizationHeader)
|
||||
private Dictionary<string, string>? GetAuthorization(string? authorizationHeader)
|
||||
{
|
||||
if (authorizationHeader == null)
|
||||
{
|
@ -4,10 +4,10 @@ using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Data.Queries;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Users
|
||||
@ -15,14 +15,12 @@ namespace Jellyfin.Server.Implementations.Users
|
||||
public sealed class DeviceAccessEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IAuthenticationRepository _authRepo;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
|
||||
public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager)
|
||||
public DeviceAccessEntryPoint(IUserManager userManager, IDeviceManager deviceManager, ISessionManager sessionManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_authRepo = authRepo;
|
||||
_deviceManager = deviceManager;
|
||||
_sessionManager = sessionManager;
|
||||
}
|
||||
@ -38,27 +36,27 @@ namespace Jellyfin.Server.Implementations.Users
|
||||
{
|
||||
}
|
||||
|
||||
private void OnUserUpdated(object? sender, GenericEventArgs<User> e)
|
||||
private async void OnUserUpdated(object? sender, GenericEventArgs<User> e)
|
||||
{
|
||||
var user = e.Argument;
|
||||
if (!user.HasPermission(PermissionKind.EnableAllDevices))
|
||||
{
|
||||
UpdateDeviceAccess(user);
|
||||
await UpdateDeviceAccess(user).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDeviceAccess(User user)
|
||||
private async Task UpdateDeviceAccess(User user)
|
||||
{
|
||||
var existing = _authRepo.Get(new AuthenticationInfoQuery
|
||||
var existing = (await _deviceManager.GetDevices(new DeviceQuery
|
||||
{
|
||||
UserId = user.Id
|
||||
}).Items;
|
||||
}).ConfigureAwait(false)).Items;
|
||||
|
||||
foreach (var authInfo in existing)
|
||||
foreach (var device in existing)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId))
|
||||
if (!string.IsNullOrEmpty(device.DeviceId) && !_deviceManager.CanAccessDevice(user, device.DeviceId))
|
||||
{
|
||||
_sessionManager.Logout(authInfo);
|
||||
await _sessionManager.Logout(device).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Activity;
|
||||
using Jellyfin.Server.Implementations.Devices;
|
||||
using Jellyfin.Server.Implementations.Events;
|
||||
using Jellyfin.Server.Implementations.Security;
|
||||
using Jellyfin.Server.Implementations.Users;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.BaseItemManager;
|
||||
@ -94,6 +95,8 @@ namespace Jellyfin.Server
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>();
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>();
|
||||
|
||||
ServiceCollection.AddScoped<IAuthorizationContext, AuthorizationContext>();
|
||||
|
||||
base.RegisterServices();
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Data.Queries;
|
||||
using MediaBrowser.Model.Devices;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Session;
|
||||
@ -17,6 +18,13 @@ namespace MediaBrowser.Controller.Devices
|
||||
{
|
||||
event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new device.
|
||||
/// </summary>
|
||||
/// <param name="device">The device to create.</param>
|
||||
/// <returns>A <see cref="Task{Device}"/> representing the creation of the device.</returns>
|
||||
Task<Device> CreateDevice(Device device);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the capabilities.
|
||||
/// </summary>
|
||||
@ -38,6 +46,15 @@ namespace MediaBrowser.Controller.Devices
|
||||
/// <returns>DeviceInfo.</returns>
|
||||
Task<DeviceInfo> GetDevice(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets devices based on the provided query.
|
||||
/// </summary>
|
||||
/// <param name="query">The device query.</param>
|
||||
/// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the devices.</returns>
|
||||
Task<QueryResult<Device>> GetDevices(DeviceQuery query);
|
||||
|
||||
Task<QueryResult<DeviceInfo>> GetDeviceInfos(DeviceQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the devices.
|
||||
/// </summary>
|
||||
@ -46,6 +63,8 @@ namespace MediaBrowser.Controller.Devices
|
||||
/// <returns>IEnumerable<DeviceInfo>.</returns>
|
||||
Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId, bool? supportsSync);
|
||||
|
||||
Task DeleteDevice(Device device);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance [can access device] the specified user identifier.
|
||||
/// </summary>
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace MediaBrowser.Controller.Net
|
||||
@ -12,6 +13,6 @@ namespace MediaBrowser.Controller.Net
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>Authorization information. Null if unauthenticated.</returns>
|
||||
AuthorizationInfo Authenticate(HttpRequest request);
|
||||
Task<AuthorizationInfo> Authenticate(HttpRequest request);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace MediaBrowser.Controller.Net
|
||||
@ -11,14 +12,14 @@ namespace MediaBrowser.Controller.Net
|
||||
/// Gets the authorization information.
|
||||
/// </summary>
|
||||
/// <param name="requestContext">The request context.</param>
|
||||
/// <returns>AuthorizationInfo.</returns>
|
||||
AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext);
|
||||
/// <returns>A task containing the authorization info.</returns>
|
||||
Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authorization information.
|
||||
/// </summary>
|
||||
/// <param name="requestContext">The request context.</param>
|
||||
/// <returns>AuthorizationInfo.</returns>
|
||||
AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext);
|
||||
/// <returns>A <see cref="Task"/> containing the authorization info.</returns>
|
||||
Task<AuthorizationInfo> GetAuthorizationInfo(HttpRequest requestContext);
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Controller.Security
|
||||
{
|
||||
public class AuthenticationInfoQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the device identifier.
|
||||
/// </summary>
|
||||
/// <value>The device identifier.</value>
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user identifier.
|
||||
/// </summary>
|
||||
/// <value>The user identifier.</value>
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the access token.
|
||||
/// </summary>
|
||||
/// <value>The access token.</value>
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is active.
|
||||
/// </summary>
|
||||
/// <value><c>null</c> if [is active] contains no value, <c>true</c> if [is active]; otherwise, <c>false</c>.</value>
|
||||
public bool? IsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has user.
|
||||
/// </summary>
|
||||
/// <value><c>null</c> if [has user] contains no value, <c>true</c> if [has user]; otherwise, <c>false</c>.</value>
|
||||
public bool? HasUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start index.
|
||||
/// </summary>
|
||||
/// <value>The start index.</value>
|
||||
public int? StartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the limit.
|
||||
/// </summary>
|
||||
/// <value>The limit.</value>
|
||||
public int? Limit { get; set; }
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace MediaBrowser.Controller.Security
|
||||
{
|
||||
public interface IAuthenticationRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the specified information.
|
||||
/// </summary>
|
||||
/// <param name="info">The information.</param>
|
||||
/// <returns>Task.</returns>
|
||||
void Create(AuthenticationInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified information.
|
||||
/// </summary>
|
||||
/// <param name="info">The information.</param>
|
||||
/// <returns>Task.</returns>
|
||||
void Update(AuthenticationInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified query.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>QueryResult{AuthenticationInfo}.</returns>
|
||||
QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query);
|
||||
|
||||
void Delete(AuthenticationInfo info);
|
||||
|
||||
DeviceOptions GetDeviceOptions(string deviceId);
|
||||
|
||||
void UpdateDeviceOptions(string deviceId, DeviceOptions options);
|
||||
}
|
||||
}
|
@ -6,10 +6,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Data.Events;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Model.Devices;
|
||||
using MediaBrowser.Model.Session;
|
||||
using MediaBrowser.Model.SyncPlay;
|
||||
|
||||
@ -325,26 +327,23 @@ namespace MediaBrowser.Controller.Session
|
||||
/// <param name="remoteEndpoint">The remote endpoint.</param>
|
||||
/// <param name="appVersion">The application version.</param>
|
||||
/// <returns>Task<SessionInfo>.</returns>
|
||||
Task<SessionInfo> GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion);
|
||||
Task<SessionInfo> GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion);
|
||||
|
||||
/// <summary>
|
||||
/// Logouts the specified access token.
|
||||
/// </summary>
|
||||
/// <param name="accessToken">The access token.</param>
|
||||
void Logout(string accessToken);
|
||||
/// <returns>A <see cref="Task"/> representing the log out process.</returns>
|
||||
Task Logout(string accessToken);
|
||||
|
||||
void Logout(AuthenticationInfo accessToken);
|
||||
Task Logout(Device accessToken);
|
||||
|
||||
/// <summary>
|
||||
/// Revokes the user tokens.
|
||||
/// </summary>
|
||||
void RevokeUserTokens(Guid userId, string currentAccessToken);
|
||||
|
||||
/// <summary>
|
||||
/// Revokes the token.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
void RevokeToken(string id);
|
||||
/// <param name="userId">The user's id.</param>
|
||||
/// <param name="currentAccessToken">The current access token.</param>
|
||||
Task RevokeUserTokens(Guid userId, string currentAccessToken);
|
||||
|
||||
void CloseIfNeeded(SessionInfo session);
|
||||
}
|
||||
|
@ -15,6 +15,11 @@ namespace MediaBrowser.Model.Devices
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the access token.
|
||||
/// </summary>
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier.
|
||||
/// </summary>
|
||||
|
@ -136,7 +136,7 @@ namespace Jellyfin.Api.Tests.Auth
|
||||
_jellyfinAuthServiceMock.Setup(
|
||||
a => a.Authenticate(
|
||||
It.IsAny<HttpRequest>()))
|
||||
.Returns(authorizationInfo);
|
||||
.Returns(Task.FromResult(authorizationInfo));
|
||||
|
||||
return authorizationInfo;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user