< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Security.AuthorizationContext
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
Line coverage
69%
Covered lines: 86
Uncovered lines: 38
Coverable lines: 124
Total lines: 319
Line coverage: 69.3%
Branch coverage
54%
Covered branches: 46
Total branches: 84
Branch coverage: 54.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/16/2026 - 12:14:00 AM Line coverage: 85.7% (36/42) Branch coverage: 69.4% (25/36) Total lines: 3194/19/2026 - 12:14:27 AM Line coverage: 69.3% (86/124) Branch coverage: 58.3% (49/84) Total lines: 3195/6/2026 - 12:15:23 AM Line coverage: 72.5% (90/124) Branch coverage: 67.8% (57/84) Total lines: 3195/20/2026 - 12:15:44 AM Line coverage: 72.5% (90/124) Branch coverage: 64.2% (54/84) Total lines: 3196/2/2026 - 12:15:49 AM Line coverage: 69.3% (86/124) Branch coverage: 54.7% (46/84) Total lines: 319 3/16/2026 - 12:14:00 AM Line coverage: 85.7% (36/42) Branch coverage: 69.4% (25/36) Total lines: 3194/19/2026 - 12:14:27 AM Line coverage: 69.3% (86/124) Branch coverage: 58.3% (49/84) Total lines: 3195/6/2026 - 12:15:23 AM Line coverage: 72.5% (90/124) Branch coverage: 67.8% (57/84) Total lines: 3195/20/2026 - 12:15:44 AM Line coverage: 72.5% (90/124) Branch coverage: 64.2% (54/84) Total lines: 3196/2/2026 - 12:15:49 AM Line coverage: 69.3% (86/124) Branch coverage: 54.7% (46/84) Total lines: 319

Coverage delta

Coverage delta 17 -17

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetAuthorizationInfo(...)0%2040%
GetAuthorizationInfo()100%11100%
GetAuthorization()100%210%
GetAuthorizationInfoFromDictionary()50%1734862.16%
GetAuthorizationDictionary(...)50%7675%
GetAuthorization(...)37.5%9880%
GetParts(...)88.88%1818100%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Net;
 6using System.Threading.Tasks;
 7using Jellyfin.Data.Queries;
 8using Jellyfin.Database.Implementations;
 9using Jellyfin.Extensions;
 10using MediaBrowser.Controller;
 11using MediaBrowser.Controller.Configuration;
 12using MediaBrowser.Controller.Devices;
 13using MediaBrowser.Controller.Library;
 14using MediaBrowser.Controller.Net;
 15using Microsoft.AspNetCore.Http;
 16using Microsoft.EntityFrameworkCore;
 17using Microsoft.Net.Http.Headers;
 18
 19namespace Jellyfin.Server.Implementations.Security
 20{
 21    public class AuthorizationContext : IAuthorizationContext
 22    {
 23        private readonly IDbContextFactory<JellyfinDbContext> _jellyfinDbProvider;
 24        private readonly IUserManager _userManager;
 25        private readonly IDeviceManager _deviceManager;
 26        private readonly IServerApplicationHost _serverApplicationHost;
 27        private readonly IServerConfigurationManager _configurationManager;
 28
 29        public AuthorizationContext(
 30            IDbContextFactory<JellyfinDbContext> jellyfinDb,
 31            IUserManager userManager,
 32            IDeviceManager deviceManager,
 33            IServerApplicationHost serverApplicationHost,
 34            IServerConfigurationManager configurationManager)
 35        {
 2036            _jellyfinDbProvider = jellyfinDb;
 2037            _userManager = userManager;
 2038            _deviceManager = deviceManager;
 2039            _serverApplicationHost = serverApplicationHost;
 2040            _configurationManager = configurationManager;
 2041        }
 42
 43        public Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext)
 44        {
 045            if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached) && cached is n
 46            {
 047                return Task.FromResult((AuthorizationInfo)cached); // Cache should never contain null
 48            }
 49
 050            return GetAuthorization(requestContext);
 51        }
 52
 53        public async Task<AuthorizationInfo> GetAuthorizationInfo(HttpRequest requestContext)
 54        {
 18655            var auth = GetAuthorizationDictionary(requestContext);
 18656            var authInfo = await GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query).
 18657            return authInfo;
 18658        }
 59
 60        /// <summary>
 61        /// Gets the authorization.
 62        /// </summary>
 63        /// <param name="httpContext">The HTTP context.</param>
 64        /// <returns>Dictionary{System.StringSystem.String}.</returns>
 65        private async Task<AuthorizationInfo> GetAuthorization(HttpContext httpContext)
 66        {
 067            var authInfo = await GetAuthorizationInfo(httpContext.Request).ConfigureAwait(false);
 68
 069            httpContext.Request.HttpContext.Items["AuthorizationInfo"] = authInfo;
 070            return authInfo;
 071        }
 72
 73        private async Task<AuthorizationInfo> GetAuthorizationInfoFromDictionary(
 74            Dictionary<string, string>? auth,
 75            IHeaderDictionary headers,
 76            IQueryCollection queryString)
 77        {
 18678            string? deviceId = null;
 18679            string? deviceName = null;
 18680            string? client = null;
 18681            string? version = null;
 18682            string? token = null;
 83
 18684            if (auth is not null)
 85            {
 12786                auth.TryGetValue("DeviceId", out deviceId);
 12787                auth.TryGetValue("Device", out deviceName);
 12788                auth.TryGetValue("Client", out client);
 12789                auth.TryGetValue("Version", out version);
 12790                auth.TryGetValue("Token", out token);
 91            }
 92
 18693            if (_configurationManager.Configuration.EnableLegacyAuthorization && string.IsNullOrEmpty(token))
 94            {
 095                token = headers["X-Emby-Token"];
 96            }
 97
 18698            if (_configurationManager.Configuration.EnableLegacyAuthorization && string.IsNullOrEmpty(token))
 99            {
 0100                token = headers["X-MediaBrowser-Token"];
 101            }
 102
 186103            if (string.IsNullOrEmpty(token))
 104            {
 89105                token = queryString["ApiKey"];
 106            }
 107
 186108            if (_configurationManager.Configuration.EnableLegacyAuthorization && string.IsNullOrEmpty(token))
 109            {
 0110                token = queryString["api_key"];
 111            }
 112
 186113            var authInfo = new AuthorizationInfo
 186114            {
 186115                Client = client,
 186116                Device = deviceName,
 186117                DeviceId = deviceId,
 186118                Version = version,
 186119                Token = token,
 186120                IsAuthenticated = false
 186121            };
 122
 186123            if (!authInfo.HasToken)
 124            {
 125                // Request doesn't contain a token.
 89126                return authInfo;
 127            }
 128
 97129            var dbContext = await _jellyfinDbProvider.CreateDbContextAsync().ConfigureAwait(false);
 97130            await using (dbContext.ConfigureAwait(false))
 131            {
 97132                var device = _deviceManager.GetDevices(
 97133                    new DeviceQuery { AccessToken = token }).Items.FirstOrDefault();
 134
 97135                if (device is not null)
 136                {
 97137                    authInfo.IsAuthenticated = true;
 97138                    var updateToken = false;
 139
 140                    // TODO: Remove these checks for IsNullOrWhiteSpace
 97141                    if (string.IsNullOrWhiteSpace(authInfo.Client))
 142                    {
 0143                        authInfo.Client = device.AppName;
 144                    }
 145
 97146                    if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
 147                    {
 0148                        authInfo.DeviceId = device.DeviceId;
 149                    }
 150
 151                    // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
 97152                    var allowTokenInfoUpdate = !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCas
 153
 97154                    if (string.IsNullOrWhiteSpace(authInfo.Device))
 155                    {
 0156                        authInfo.Device = device.DeviceName;
 157                    }
 97158                    else if (!string.Equals(authInfo.Device, device.DeviceName, StringComparison.OrdinalIgnoreCase))
 159                    {
 0160                        if (allowTokenInfoUpdate)
 161                        {
 0162                            updateToken = true;
 0163                            device.DeviceName = authInfo.Device;
 164                        }
 165                    }
 166
 97167                    if (string.IsNullOrWhiteSpace(authInfo.Version))
 168                    {
 0169                        authInfo.Version = device.AppVersion;
 170                    }
 97171                    else if (!string.Equals(authInfo.Version, device.AppVersion, StringComparison.OrdinalIgnoreCase))
 172                    {
 0173                        if (allowTokenInfoUpdate)
 174                        {
 0175                            updateToken = true;
 0176                            device.AppVersion = authInfo.Version;
 177                        }
 178                    }
 179
 97180                    if ((DateTime.UtcNow - device.DateLastActivity).TotalMinutes > 3)
 181                    {
 0182                        device.DateLastActivity = DateTime.UtcNow;
 0183                        updateToken = true;
 184                    }
 185
 97186                    authInfo.User = _userManager.GetUserById(device.UserId);
 187
 97188                    if (updateToken)
 189                    {
 0190                        await _deviceManager.UpdateDevice(device).ConfigureAwait(false);
 191                    }
 192                }
 193                else
 194                {
 0195                    var key = await dbContext.ApiKeys.FirstOrDefaultAsync(apiKey => apiKey.AccessToken == token).Configu
 0196                    if (key is not null)
 197                    {
 0198                        authInfo.IsAuthenticated = true;
 0199                        authInfo.Client = key.Name;
 0200                        authInfo.Token = key.AccessToken;
 0201                        if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
 202                        {
 0203                            authInfo.DeviceId = _serverApplicationHost.SystemId;
 204                        }
 205
 0206                        if (string.IsNullOrWhiteSpace(authInfo.Device))
 207                        {
 0208                            authInfo.Device = _serverApplicationHost.Name;
 209                        }
 210
 0211                        if (string.IsNullOrWhiteSpace(authInfo.Version))
 212                        {
 0213                            authInfo.Version = _serverApplicationHost.ApplicationVersionString;
 214                        }
 215
 0216                        authInfo.IsApiKey = true;
 217                    }
 218                }
 219
 97220                return authInfo;
 221            }
 186222        }
 223
 224        /// <summary>
 225        /// Gets the auth.
 226        /// </summary>
 227        /// <param name="httpReq">The HTTP request.</param>
 228        /// <returns>Dictionary{System.StringSystem.String}.</returns>
 229        private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq)
 230        {
 186231            var auth = httpReq.Headers[HeaderNames.Authorization];
 232
 186233            if (_configurationManager.Configuration.EnableLegacyAuthorization && string.IsNullOrEmpty(auth))
 234            {
 0235                auth = httpReq.Headers["X-Emby-Authorization"];
 236            }
 237
 186238            return auth.Count > 0 ? GetAuthorization(auth[0]) : null;
 239        }
 240
 241        /// <summary>
 242        /// Gets the authorization.
 243        /// </summary>
 244        /// <param name="authorizationHeader">The authorization header.</param>
 245        /// <returns>Dictionary{System.StringSystem.String}.</returns>
 246        private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader)
 247        {
 127248            var firstSpace = authorizationHeader.IndexOf(' ');
 249
 250            // There should be at least two parts
 127251            if (firstSpace == -1)
 252            {
 0253                return null;
 254            }
 255
 127256            var name = authorizationHeader[..firstSpace];
 257
 127258            var validName = name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase);
 127259            validName = validName || (_configurationManager.Configuration.EnableLegacyAuthorization && name.Equals("Emby
 260
 127261            if (!validName)
 262            {
 0263                return null;
 264            }
 265
 266            // Remove up until the first space
 127267            authorizationHeader = authorizationHeader[(firstSpace + 1)..];
 127268            return GetParts(authorizationHeader);
 269        }
 270
 271        /// <summary>
 272        /// Get the authorization header components.
 273        /// </summary>
 274        /// <param name="authorizationHeader">The authorization header.</param>
 275        /// <returns>Dictionary{System.StringSystem.String}.</returns>
 276        public static Dictionary<string, string> GetParts(ReadOnlySpan<char> authorizationHeader)
 277        {
 132278            var result = new Dictionary<string, string>();
 132279            var escaped = false;
 132280            int start = 0;
 132281            string key = string.Empty;
 282
 283            int i;
 34378284            for (i = 0; i < authorizationHeader.Length; i++)
 285            {
 17057286                var token = authorizationHeader[i];
 17057287                if (token == '"' || token == ',')
 288                {
 289                    // Applying a XOR logic to evaluate whether it is opening or closing a value
 1511290                    escaped = (!escaped) == (token == '"');
 1511291                    if (token == ',' && !escaped)
 292                    {
 293                        // Meeting a comma after a closing escape char means the value is complete
 481294                        if (start < i)
 295                        {
 481296                            result[key] = WebUtility.UrlDecode(authorizationHeader[start..i].Trim('"').ToString());
 481297                            key = string.Empty;
 298                        }
 299
 481300                        start = i + 1;
 301                    }
 302                }
 15546303                else if (!escaped && token == '=')
 304                {
 613305                    key = authorizationHeader[start..i].Trim().ToString();
 613306                    start = i + 1;
 307                }
 308            }
 309
 310            // Add last value
 132311            if (start < i)
 312            {
 132313                result[key] = WebUtility.UrlDecode(authorizationHeader[start..i].Trim('"').ToString());
 314            }
 315
 132316            return result;
 317        }
 318    }
 319}