2
0

ApiServiceCollectionExtensions.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using Jellyfin.Api;
  7. using Jellyfin.Api.Auth;
  8. using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
  9. using Jellyfin.Api.Auth.DownloadPolicy;
  10. using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
  11. using Jellyfin.Api.Auth.IgnoreSchedulePolicy;
  12. using Jellyfin.Api.Auth.LocalAccessPolicy;
  13. using Jellyfin.Api.Auth.RequiresElevationPolicy;
  14. using Jellyfin.Api.Constants;
  15. using Jellyfin.Api.Controllers;
  16. using Jellyfin.Server.Formatters;
  17. using Jellyfin.Server.Models;
  18. using MediaBrowser.Common.Json;
  19. using MediaBrowser.Model.Entities;
  20. using Microsoft.AspNetCore.Authentication;
  21. using Microsoft.AspNetCore.Authorization;
  22. using Microsoft.AspNetCore.Builder;
  23. using Microsoft.AspNetCore.HttpOverrides;
  24. using Microsoft.Extensions.DependencyInjection;
  25. using Microsoft.OpenApi.Models;
  26. using Swashbuckle.AspNetCore.SwaggerGen;
  27. namespace Jellyfin.Server.Extensions
  28. {
  29. /// <summary>
  30. /// API specific extensions for the service collection.
  31. /// </summary>
  32. public static class ApiServiceCollectionExtensions
  33. {
  34. /// <summary>
  35. /// Adds jellyfin API authorization policies to the DI container.
  36. /// </summary>
  37. /// <param name="serviceCollection">The service collection.</param>
  38. /// <returns>The updated service collection.</returns>
  39. public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection)
  40. {
  41. serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>();
  42. serviceCollection.AddSingleton<IAuthorizationHandler, DownloadHandler>();
  43. serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>();
  44. serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreScheduleHandler>();
  45. serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>();
  46. serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
  47. return serviceCollection.AddAuthorizationCore(options =>
  48. {
  49. options.AddPolicy(
  50. Policies.DefaultAuthorization,
  51. policy =>
  52. {
  53. policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
  54. policy.AddRequirements(new DefaultAuthorizationRequirement());
  55. });
  56. options.AddPolicy(
  57. Policies.Download,
  58. policy =>
  59. {
  60. policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
  61. policy.AddRequirements(new DownloadRequirement());
  62. });
  63. options.AddPolicy(
  64. Policies.FirstTimeSetupOrElevated,
  65. policy =>
  66. {
  67. policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
  68. policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement());
  69. });
  70. options.AddPolicy(
  71. Policies.IgnoreSchedule,
  72. policy =>
  73. {
  74. policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
  75. policy.AddRequirements(new IgnoreScheduleRequirement());
  76. });
  77. options.AddPolicy(
  78. Policies.LocalAccessOnly,
  79. policy =>
  80. {
  81. policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
  82. policy.AddRequirements(new LocalAccessRequirement());
  83. });
  84. options.AddPolicy(
  85. Policies.RequiresElevation,
  86. policy =>
  87. {
  88. policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
  89. policy.AddRequirements(new RequiresElevationRequirement());
  90. });
  91. });
  92. }
  93. /// <summary>
  94. /// Adds custom legacy authentication to the service collection.
  95. /// </summary>
  96. /// <param name="serviceCollection">The service collection.</param>
  97. /// <returns>The updated service collection.</returns>
  98. public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection)
  99. {
  100. return serviceCollection.AddAuthentication(AuthenticationSchemes.CustomAuthentication)
  101. .AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>(AuthenticationSchemes.CustomAuthentication, null);
  102. }
  103. /// <summary>
  104. /// Extension method for adding the jellyfin API to the service collection.
  105. /// </summary>
  106. /// <param name="serviceCollection">The service collection.</param>
  107. /// <param name="baseUrl">The base url for the API.</param>
  108. /// <returns>The MVC builder.</returns>
  109. public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl)
  110. {
  111. return serviceCollection
  112. .AddCors(options =>
  113. {
  114. options.AddPolicy(ServerCorsPolicy.DefaultPolicyName, ServerCorsPolicy.DefaultPolicy);
  115. })
  116. .Configure<ForwardedHeadersOptions>(options =>
  117. {
  118. options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
  119. })
  120. .AddMvc(opts =>
  121. {
  122. opts.UseGeneralRoutePrefix(baseUrl);
  123. opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter());
  124. opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());
  125. opts.OutputFormatters.Add(new CssOutputFormatter());
  126. })
  127. // Clear app parts to avoid other assemblies being picked up
  128. .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())
  129. .AddApplicationPart(typeof(StartupController).Assembly)
  130. .AddJsonOptions(options =>
  131. {
  132. // Update all properties that are set in JsonDefaults
  133. var jsonOptions = JsonDefaults.GetPascalCaseOptions();
  134. // From JsonDefaults
  135. options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling;
  136. options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented;
  137. options.JsonSerializerOptions.Converters.Clear();
  138. foreach (var converter in jsonOptions.Converters)
  139. {
  140. options.JsonSerializerOptions.Converters.Add(converter);
  141. }
  142. // From JsonDefaults.PascalCase
  143. options.JsonSerializerOptions.PropertyNamingPolicy = jsonOptions.PropertyNamingPolicy;
  144. })
  145. .AddControllersAsServices();
  146. }
  147. /// <summary>
  148. /// Adds Swagger to the service collection.
  149. /// </summary>
  150. /// <param name="serviceCollection">The service collection.</param>
  151. /// <returns>The updated service collection.</returns>
  152. public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection)
  153. {
  154. return serviceCollection.AddSwaggerGen(c =>
  155. {
  156. c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" });
  157. c.AddSecurityDefinition(AuthenticationSchemes.CustomAuthentication, new OpenApiSecurityScheme
  158. {
  159. Type = SecuritySchemeType.ApiKey,
  160. In = ParameterLocation.Header,
  161. Name = "X-Emby-Token",
  162. Description = "API key header parameter"
  163. });
  164. var securitySchemeRef = new OpenApiSecurityScheme
  165. {
  166. Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = AuthenticationSchemes.CustomAuthentication },
  167. };
  168. // TODO: Apply this with an operation filter instead of globally
  169. // https://github.com/domaindrivendev/Swashbuckle.AspNetCore#add-security-definitions-and-requirements
  170. c.AddSecurityRequirement(new OpenApiSecurityRequirement
  171. {
  172. { securitySchemeRef, Array.Empty<string>() }
  173. });
  174. // Add all xml doc files to swagger generator.
  175. var xmlFiles = Directory.GetFiles(
  176. AppContext.BaseDirectory,
  177. "*.xml",
  178. SearchOption.TopDirectoryOnly);
  179. foreach (var xmlFile in xmlFiles)
  180. {
  181. c.IncludeXmlComments(xmlFile);
  182. }
  183. // Order actions by route path, then by http method.
  184. c.OrderActionsBy(description =>
  185. $"{description.ActionDescriptor.RouteValues["controller"]}_{description.RelativePath}");
  186. // Use method name as operationId
  187. c.CustomOperationIds(description =>
  188. description.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null);
  189. // TODO - remove when all types are supported in System.Text.Json
  190. c.AddSwaggerTypeMappings();
  191. });
  192. }
  193. private static void AddSwaggerTypeMappings(this SwaggerGenOptions options)
  194. {
  195. /*
  196. * TODO remove when System.Text.Json supports non-string keys.
  197. * Used in Jellyfin.Api.Controller.GetChannels.
  198. */
  199. options.MapType<Dictionary<ImageType, string>>(() =>
  200. new OpenApiSchema
  201. {
  202. Type = "object",
  203. Properties = typeof(ImageType).GetEnumNames().ToDictionary(
  204. name => name,
  205. name => new OpenApiSchema
  206. {
  207. Type = "string",
  208. Format = "string"
  209. })
  210. });
  211. /*
  212. * Support BlurHash dictionary
  213. */
  214. options.MapType<Dictionary<ImageType, Dictionary<string, string>>>(() =>
  215. new OpenApiSchema
  216. {
  217. Type = "object",
  218. Properties = typeof(ImageType).GetEnumNames().ToDictionary(
  219. name => name,
  220. name => new OpenApiSchema
  221. {
  222. Type = "object", Properties = new Dictionary<string, OpenApiSchema>
  223. {
  224. {
  225. "string",
  226. new OpenApiSchema
  227. {
  228. Type = "string",
  229. Format = "string"
  230. }
  231. }
  232. }
  233. })
  234. });
  235. }
  236. }
  237. }