// Kyoo - A portable and vast media library solution. // Copyright (c) Kyoo. // // See AUTHORS.md and LICENSE file in the project root for full license information. // // Kyoo is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // Kyoo is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using Autofac; using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Services; using Kyoo.Abstractions; using Kyoo.Abstractions.Controllers; using Kyoo.Authentication.Models; using Kyoo.Authentication.Views; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Logging; using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode; namespace Kyoo.Authentication { /// /// A module that enable OpenID authentication for Kyoo. /// public class AuthenticationModule : IPlugin { /// public string Slug => "auth"; /// public string Name => "Authentication"; /// public string Description => "Enable OpenID authentication for Kyoo."; /// public Dictionary Configuration => new() { { AuthenticationOption.Path, typeof(AuthenticationOption) }, { PermissionOption.Path, typeof(PermissionOption) }, { CertificateOption.Path, typeof(CertificateOption) } }; /// /// The configuration to use. /// private readonly IConfiguration _configuration; /// /// The logger used to allow IdentityServer to log things. /// private readonly ILogger _logger; /// /// The environment information to check if the app runs in debug mode /// private readonly IWebHostEnvironment _environment; /// /// Create a new authentication module instance and use the given configuration and environment. /// /// The configuration to use /// The logger used to allow IdentityServer to log things /// The environment information to check if the app runs in debug mode [SuppressMessage("ReSharper", "ContextualLoggerProblem", Justification = "The logger is used for a dependency that is not created via the container.")] public AuthenticationModule(IConfiguration configuration, ILogger logger, IWebHostEnvironment environment) { _configuration = configuration; _logger = logger; _environment = environment; } /// public void Configure(ContainerBuilder builder) { builder.RegisterType().As().SingleInstance(); DefaultCorsPolicyService cors = new(_logger) { AllowedOrigins = { new Uri(_configuration.GetPublicUrl()).GetLeftPart(UriPartial.Authority) } }; builder.RegisterInstance(cors).As().SingleInstance(); } /// public void Configure(IServiceCollection services) { string publicUrl = _configuration.GetPublicUrl(); if (_environment.IsDevelopment()) IdentityModelEventSource.ShowPII = true; services.AddControllers(); // TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos) // TODO Check if tokens should be stored. List clients = new(); _configuration.GetSection("authentication:clients").Bind(clients); CertificateOption certificateOptions = new(); _configuration.GetSection(CertificateOption.Path).Bind(certificateOptions); clients.AddRange(IdentityContext.GetClients()); foreach (Client client in clients) { client.RedirectUris = client.RedirectUris .Select(x => x.StartsWith("/") ? publicUrl + x : x) .ToArray(); } services.AddIdentityServer(options => { options.IssuerUri = publicUrl; options.UserInteraction.LoginUrl = $"{publicUrl}/login"; options.UserInteraction.ErrorUrl = $"{publicUrl}/error"; options.UserInteraction.LogoutUrl = $"{publicUrl}/logout"; }) .AddInMemoryIdentityResources(IdentityContext.GetIdentityResources()) .AddInMemoryApiScopes(IdentityContext.GetScopes()) .AddInMemoryApiResources(IdentityContext.GetApis()) .AddInMemoryClients(clients) .AddProfileService() .AddSigninKeys(certificateOptions); services.AddAuthentication() .AddJwtBearer(options => { options.Authority = publicUrl; options.Audience = "kyoo"; options.RequireHttpsMetadata = false; }); } /// public IEnumerable ConfigureSteps => new IStartupAction[] { SA.New(app => { PhysicalFileProvider provider = new(Path.Combine( Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "login")); app.UseDefaultFiles(new DefaultFilesOptions { RequestPath = new PathString("/login"), FileProvider = provider, RedirectToAppendTrailingSlash = true }); app.UseStaticFiles(new StaticFileOptions { RequestPath = new PathString("/login"), FileProvider = provider }); }, SA.StaticFiles), SA.New(app => { app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict }); app.UseAuthentication(); }, SA.Authentication), SA.New(app => { app.Use((ctx, next) => { ctx.SetIdentityServerOrigin(_configuration.GetPublicUrl()); return next(); }); app.UseIdentityServer(); }, SA.Endpoint), SA.New(app => app.UseAuthorization(), SA.Authorization) }; } }