Sfoglia il codice sorgente

Merge pull request #3394 from Ullmie02/fix-startupwizzard

Fix startup wizard in 10.6
Joshua M. Boniface 5 anni fa
parent
commit
680dd95292

+ 16 - 0
Emby.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -51,6 +51,22 @@ namespace Emby.Server.Implementations.HttpServer.Security
             return user;
             return user;
         }
         }
 
 
+        public AuthorizationInfo Authenticate(HttpRequest request)
+        {
+            var auth = _authorizationContext.GetAuthorizationInfo(request);
+            if (auth?.User == null)
+            {
+                return null;
+            }
+
+            if (auth.User.HasPermission(PermissionKind.IsDisabled))
+            {
+                throw new SecurityException("User account has been disabled.");
+            }
+
+            return auth;
+        }
+
         private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
         private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
         {
         {
             // This code is executed before the service
             // This code is executed before the service

+ 70 - 31
Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
 using Microsoft.Net.Http.Headers;
 using Microsoft.Net.Http.Headers;
 
 
 namespace Emby.Server.Implementations.HttpServer.Security
 namespace Emby.Server.Implementations.HttpServer.Security
@@ -38,6 +39,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
             return GetAuthorization(requestContext);
             return GetAuthorization(requestContext);
         }
         }
 
 
+        public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext)
+        {
+            var auth = GetAuthorizationDictionary(requestContext);
+            var (authInfo, _) =
+                GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
+            return authInfo;
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the authorization.
         /// Gets the authorization.
         /// </summary>
         /// </summary>
@@ -46,7 +55,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
         private AuthorizationInfo GetAuthorization(IRequest httpReq)
         private AuthorizationInfo GetAuthorization(IRequest httpReq)
         {
         {
             var auth = GetAuthorizationDictionary(httpReq);
             var auth = GetAuthorizationDictionary(httpReq);
+            var (authInfo, originalAuthInfo) =
+                GetAuthorizationInfoFromDictionary(auth, httpReq.Headers, httpReq.QueryString);
+
+            if (originalAuthInfo != null)
+            {
+                httpReq.Items["OriginalAuthenticationInfo"] = originalAuthInfo;
+            }
+
+            httpReq.Items["AuthorizationInfo"] = authInfo;
+            return authInfo;
+        }
 
 
+        private (AuthorizationInfo authInfo, AuthenticationInfo originalAuthenticationInfo) GetAuthorizationInfoFromDictionary(
+            in Dictionary<string, string> auth,
+            in IHeaderDictionary headers,
+            in IQueryCollection queryString)
+        {
             string deviceId = null;
             string deviceId = null;
             string device = null;
             string device = null;
             string client = null;
             string client = null;
@@ -64,20 +89,20 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
 
             if (string.IsNullOrEmpty(token))
             if (string.IsNullOrEmpty(token))
             {
             {
-                token = httpReq.Headers["X-Emby-Token"];
+                token = headers["X-Emby-Token"];
             }
             }
 
 
             if (string.IsNullOrEmpty(token))
             if (string.IsNullOrEmpty(token))
             {
             {
-                token = httpReq.Headers["X-MediaBrowser-Token"];
+                token = headers["X-MediaBrowser-Token"];
             }
             }
 
 
             if (string.IsNullOrEmpty(token))
             if (string.IsNullOrEmpty(token))
             {
             {
-                token = httpReq.QueryString["api_key"];
+                token = queryString["api_key"];
             }
             }
 
 
-            var info = new AuthorizationInfo
+            var authInfo = new AuthorizationInfo
             {
             {
                 Client = client,
                 Client = client,
                 Device = device,
                 Device = device,
@@ -86,6 +111,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
                 Token = token
                 Token = token
             };
             };
 
 
+            AuthenticationInfo originalAuthenticationInfo = null;
             if (!string.IsNullOrWhiteSpace(token))
             if (!string.IsNullOrWhiteSpace(token))
             {
             {
                 var result = _authRepo.Get(new AuthenticationInfoQuery
                 var result = _authRepo.Get(new AuthenticationInfoQuery
@@ -93,81 +119,77 @@ namespace Emby.Server.Implementations.HttpServer.Security
                     AccessToken = token
                     AccessToken = token
                 });
                 });
 
 
-                var tokenInfo = result.Items.Count > 0 ? result.Items[0] : null;
+                originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
 
 
-                if (tokenInfo != null)
+                if (originalAuthenticationInfo != null)
                 {
                 {
                     var updateToken = false;
                     var updateToken = false;
 
 
                     // TODO: Remove these checks for IsNullOrWhiteSpace
                     // TODO: Remove these checks for IsNullOrWhiteSpace
-                    if (string.IsNullOrWhiteSpace(info.Client))
+                    if (string.IsNullOrWhiteSpace(authInfo.Client))
                     {
                     {
-                        info.Client = tokenInfo.AppName;
+                        authInfo.Client = originalAuthenticationInfo.AppName;
                     }
                     }
 
 
-                    if (string.IsNullOrWhiteSpace(info.DeviceId))
+                    if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
                     {
                     {
-                        info.DeviceId = tokenInfo.DeviceId;
+                        authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
                     }
                     }
 
 
                     // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
                     // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
-                    var allowTokenInfoUpdate = info.Client == null || info.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
+                    var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
 
 
-                    if (string.IsNullOrWhiteSpace(info.Device))
+                    if (string.IsNullOrWhiteSpace(authInfo.Device))
                     {
                     {
-                        info.Device = tokenInfo.DeviceName;
+                        authInfo.Device = originalAuthenticationInfo.DeviceName;
                     }
                     }
-                    else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
+                    else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
                     {
                     {
                         if (allowTokenInfoUpdate)
                         if (allowTokenInfoUpdate)
                         {
                         {
                             updateToken = true;
                             updateToken = true;
-                            tokenInfo.DeviceName = info.Device;
+                            originalAuthenticationInfo.DeviceName = authInfo.Device;
                         }
                         }
                     }
                     }
 
 
-                    if (string.IsNullOrWhiteSpace(info.Version))
+                    if (string.IsNullOrWhiteSpace(authInfo.Version))
                     {
                     {
-                        info.Version = tokenInfo.AppVersion;
+                        authInfo.Version = originalAuthenticationInfo.AppVersion;
                     }
                     }
-                    else if (!string.Equals(info.Version, tokenInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
+                    else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
                     {
                     {
                         if (allowTokenInfoUpdate)
                         if (allowTokenInfoUpdate)
                         {
                         {
                             updateToken = true;
                             updateToken = true;
-                            tokenInfo.AppVersion = info.Version;
+                            originalAuthenticationInfo.AppVersion = authInfo.Version;
                         }
                         }
                     }
                     }
 
 
-                    if ((DateTime.UtcNow - tokenInfo.DateLastActivity).TotalMinutes > 3)
+                    if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
                     {
                     {
-                        tokenInfo.DateLastActivity = DateTime.UtcNow;
+                        originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
                         updateToken = true;
                         updateToken = true;
                     }
                     }
 
 
-                    if (!tokenInfo.UserId.Equals(Guid.Empty))
+                    if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
                     {
                     {
-                        info.User = _userManager.GetUserById(tokenInfo.UserId);
+                        authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
 
 
-                        if (info.User != null && !string.Equals(info.User.Username, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase))
+                        if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
                         {
                         {
-                            tokenInfo.UserName = info.User.Username;
+                            originalAuthenticationInfo.UserName = authInfo.User.Username;
                             updateToken = true;
                             updateToken = true;
                         }
                         }
                     }
                     }
 
 
                     if (updateToken)
                     if (updateToken)
                     {
                     {
-                        _authRepo.Update(tokenInfo);
+                        _authRepo.Update(originalAuthenticationInfo);
                     }
                     }
                 }
                 }
-
-                httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo;
             }
             }
 
 
-            httpReq.Items["AuthorizationInfo"] = info;
-
-            return info;
+            return (authInfo, originalAuthenticationInfo);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -187,6 +209,23 @@ namespace Emby.Server.Implementations.HttpServer.Security
             return GetAuthorization(auth);
             return GetAuthorization(auth);
         }
         }
 
 
+        /// <summary>
+        /// Gets the auth.
+        /// </summary>
+        /// <param name="httpReq">The HTTP req.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq)
+        {
+            var auth = httpReq.Headers["X-Emby-Authorization"];
+
+            if (string.IsNullOrEmpty(auth))
+            {
+                auth = httpReq.Headers[HeaderNames.Authorization];
+            }
+
+            return GetAuthorization(auth);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the authorization.
         /// Gets the authorization.
         /// </summary>
         /// </summary>

+ 4 - 7
Jellyfin.Api/Auth/CustomAuthenticationHandler.cs

@@ -39,21 +39,18 @@ namespace Jellyfin.Api.Auth
         /// <inheritdoc />
         /// <inheritdoc />
         protected override Task<AuthenticateResult> HandleAuthenticateAsync()
         protected override Task<AuthenticateResult> HandleAuthenticateAsync()
         {
         {
-            var authenticatedAttribute = new AuthenticatedAttribute();
             try
             try
             {
             {
-                var user = _authService.Authenticate(Request, authenticatedAttribute);
-                if (user == null)
+                var authorizationInfo = _authService.Authenticate(Request);
+                if (authorizationInfo == null)
                 {
                 {
                     return Task.FromResult(AuthenticateResult.Fail("Invalid user"));
                     return Task.FromResult(AuthenticateResult.Fail("Invalid user"));
                 }
                 }
 
 
                 var claims = new[]
                 var claims = new[]
                 {
                 {
-                    new Claim(ClaimTypes.Name, user.Username),
-                    new Claim(
-                        ClaimTypes.Role,
-                        value: user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User)
+                    new Claim(ClaimTypes.Name, authorizationInfo.User.Username),
+                    new Claim(ClaimTypes.Role, authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User)
                 };
                 };
                 var identity = new ClaimsIdentity(claims, Scheme.Name);
                 var identity = new ClaimsIdentity(claims, Scheme.Name);
                 var principal = new ClaimsPrincipal(identity);
                 var principal = new ClaimsPrincipal(identity);

+ 7 - 0
MediaBrowser.Controller/Net/IAuthService.cs

@@ -11,5 +11,12 @@ namespace MediaBrowser.Controller.Net
         void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues);
         void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues);
 
 
         User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues);
         User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues);
+
+        /// <summary>
+        /// Authenticate request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>Authorization information. Null if unauthenticated.</returns>
+        AuthorizationInfo Authenticate(HttpRequest request);
     }
     }
 }
 }

+ 11 - 0
MediaBrowser.Controller/Net/IAuthorizationContext.cs

@@ -1,7 +1,11 @@
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
 
 
 namespace MediaBrowser.Controller.Net
 namespace MediaBrowser.Controller.Net
 {
 {
+    /// <summary>
+    /// IAuthorization context.
+    /// </summary>
     public interface IAuthorizationContext
     public interface IAuthorizationContext
     {
     {
         /// <summary>
         /// <summary>
@@ -17,5 +21,12 @@ namespace MediaBrowser.Controller.Net
         /// <param name="requestContext">The request context.</param>
         /// <param name="requestContext">The request context.</param>
         /// <returns>AuthorizationInfo.</returns>
         /// <returns>AuthorizationInfo.</returns>
         AuthorizationInfo GetAuthorizationInfo(IRequest requestContext);
         AuthorizationInfo GetAuthorizationInfo(IRequest requestContext);
+
+        /// <summary>
+        /// Gets the authorization information.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <returns>AuthorizationInfo.</returns>
+        AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext);
     }
     }
 }
 }

+ 21 - 48
tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs

@@ -1,7 +1,6 @@
 using System;
 using System;
 using System.Linq;
 using System.Linq;
 using System.Security.Claims;
 using System.Security.Claims;
-using System.Text.Encodings.Web;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using AutoFixture;
 using AutoFixture;
 using AutoFixture.AutoMoq;
 using AutoFixture.AutoMoq;
@@ -9,7 +8,6 @@ using Jellyfin.Api.Auth;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Enums;
-using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
 using Microsoft.AspNetCore.Authentication;
 using Microsoft.AspNetCore.Authentication;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
@@ -26,12 +24,6 @@ namespace Jellyfin.Api.Tests.Auth
         private readonly IFixture _fixture;
         private readonly IFixture _fixture;
 
 
         private readonly Mock<IAuthService> _jellyfinAuthServiceMock;
         private readonly Mock<IAuthService> _jellyfinAuthServiceMock;
-        private readonly Mock<IOptionsMonitor<AuthenticationSchemeOptions>> _optionsMonitorMock;
-        private readonly Mock<ISystemClock> _clockMock;
-        private readonly Mock<IServiceProvider> _serviceProviderMock;
-        private readonly Mock<IAuthenticationService> _authenticationServiceMock;
-        private readonly UrlEncoder _urlEncoder;
-        private readonly HttpContext _context;
 
 
         private readonly CustomAuthenticationHandler _sut;
         private readonly CustomAuthenticationHandler _sut;
         private readonly AuthenticationScheme _scheme;
         private readonly AuthenticationScheme _scheme;
@@ -47,26 +39,23 @@ namespace Jellyfin.Api.Tests.Auth
             AllowFixtureCircularDependencies();
             AllowFixtureCircularDependencies();
 
 
             _jellyfinAuthServiceMock = _fixture.Freeze<Mock<IAuthService>>();
             _jellyfinAuthServiceMock = _fixture.Freeze<Mock<IAuthService>>();
-            _optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
-            _clockMock = _fixture.Freeze<Mock<ISystemClock>>();
-            _serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
-            _authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
+            var optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
+            var serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
+            var authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
             _fixture.Register<ILoggerFactory>(() => new NullLoggerFactory());
             _fixture.Register<ILoggerFactory>(() => new NullLoggerFactory());
 
 
-            _urlEncoder = UrlEncoder.Default;
+            serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
+                .Returns(authenticationServiceMock.Object);
 
 
-            _serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
-                .Returns(_authenticationServiceMock.Object);
-
-            _optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
+            optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
                 .Returns(new AuthenticationSchemeOptions
                 .Returns(new AuthenticationSchemeOptions
                 {
                 {
                     ForwardAuthenticate = null
                     ForwardAuthenticate = null
                 });
                 });
 
 
-            _context = new DefaultHttpContext
+            HttpContext context = new DefaultHttpContext
             {
             {
-                RequestServices = _serviceProviderMock.Object
+                RequestServices = serviceProviderMock.Object
             };
             };
 
 
             _scheme = new AuthenticationScheme(
             _scheme = new AuthenticationScheme(
@@ -75,22 +64,7 @@ namespace Jellyfin.Api.Tests.Auth
                 typeof(CustomAuthenticationHandler));
                 typeof(CustomAuthenticationHandler));
 
 
             _sut = _fixture.Create<CustomAuthenticationHandler>();
             _sut = _fixture.Create<CustomAuthenticationHandler>();
-            _sut.InitializeAsync(_scheme, _context).Wait();
-        }
-
-        [Fact]
-        public async Task HandleAuthenticateAsyncShouldFailWithNullUser()
-        {
-            _jellyfinAuthServiceMock.Setup(
-                    a => a.Authenticate(
-                        It.IsAny<HttpRequest>(),
-                        It.IsAny<AuthenticatedAttribute>()))
-                .Returns((User?)null);
-
-            var authenticateResult = await _sut.AuthenticateAsync();
-
-            Assert.False(authenticateResult.Succeeded);
-            Assert.Equal("Invalid user", authenticateResult.Failure.Message);
+            _sut.InitializeAsync(_scheme, context).Wait();
         }
         }
 
 
         [Fact]
         [Fact]
@@ -100,8 +74,7 @@ namespace Jellyfin.Api.Tests.Auth
 
 
             _jellyfinAuthServiceMock.Setup(
             _jellyfinAuthServiceMock.Setup(
                     a => a.Authenticate(
                     a => a.Authenticate(
-                        It.IsAny<HttpRequest>(),
-                        It.IsAny<AuthenticatedAttribute>()))
+                        It.IsAny<HttpRequest>()))
                 .Throws(new SecurityException(errorMessage));
                 .Throws(new SecurityException(errorMessage));
 
 
             var authenticateResult = await _sut.AuthenticateAsync();
             var authenticateResult = await _sut.AuthenticateAsync();
@@ -123,10 +96,10 @@ namespace Jellyfin.Api.Tests.Auth
         [Fact]
         [Fact]
         public async Task HandleAuthenticateAsyncShouldAssignNameClaim()
         public async Task HandleAuthenticateAsyncShouldAssignNameClaim()
         {
         {
-            var user = SetupUser();
+            var authorizationInfo = SetupUser();
             var authenticateResult = await _sut.AuthenticateAsync();
             var authenticateResult = await _sut.AuthenticateAsync();
 
 
-            Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Username));
+            Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, authorizationInfo.User.Username));
         }
         }
 
 
         [Theory]
         [Theory]
@@ -134,10 +107,10 @@ namespace Jellyfin.Api.Tests.Auth
         [InlineData(false)]
         [InlineData(false)]
         public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin)
         public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin)
         {
         {
-            var user = SetupUser(isAdmin);
+            var authorizationInfo = SetupUser(isAdmin);
             var authenticateResult = await _sut.AuthenticateAsync();
             var authenticateResult = await _sut.AuthenticateAsync();
 
 
-            var expectedRole = user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User;
+            var expectedRole = authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User;
             Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole));
             Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole));
         }
         }
 
 
@@ -150,18 +123,18 @@ namespace Jellyfin.Api.Tests.Auth
             Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme);
             Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme);
         }
         }
 
 
-        private User SetupUser(bool isAdmin = false)
+        private AuthorizationInfo SetupUser(bool isAdmin = false)
         {
         {
-            var user = _fixture.Create<User>();
-            user.SetPermission(PermissionKind.IsAdministrator, isAdmin);
+            var authorizationInfo = _fixture.Create<AuthorizationInfo>();
+            authorizationInfo.User = _fixture.Create<User>();
+            authorizationInfo.User.SetPermission(PermissionKind.IsAdministrator, isAdmin);
 
 
             _jellyfinAuthServiceMock.Setup(
             _jellyfinAuthServiceMock.Setup(
                     a => a.Authenticate(
                     a => a.Authenticate(
-                        It.IsAny<HttpRequest>(),
-                        It.IsAny<AuthenticatedAttribute>()))
-                .Returns(user);
+                        It.IsAny<HttpRequest>()))
+                    .Returns(authorizationInfo);
 
 
-            return user;
+            return authorizationInfo;
         }
         }
 
 
         private void AllowFixtureCircularDependencies()
         private void AllowFixtureCircularDependencies()