diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs
index 559517d2..26b50dd1 100644
--- a/Kyoo.Authentication/AuthenticationModule.cs
+++ b/Kyoo.Authentication/AuthenticationModule.cs
@@ -3,16 +3,19 @@ using System.Collections.Generic;
using IdentityServer4.Extensions;
using IdentityServer4.Services;
using Kyoo.Authentication.Models;
+using Kyoo.Authentication.Views;
using Kyoo.Controllers;
-using Kyoo.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+using Microsoft.IdentityModel.Logging;
namespace Kyoo.Authentication
{
@@ -53,16 +56,25 @@ namespace Kyoo.Authentication
///
private readonly ILoggerFactory _loggerFactory;
-
+ ///
+ /// 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 factory to allow IdentityServer to log things
- public AuthenticationModule(IConfiguration configuration, ILoggerFactory loggerFactory)
+ /// The environment information to check if the app runs in debug mode
+ public AuthenticationModule(IConfiguration configuration,
+ ILoggerFactory loggerFactory,
+ IWebHostEnvironment environment)
{
_configuration = configuration;
_loggerFactory = loggerFactory;
+ _environment = environment;
}
///
@@ -70,8 +82,16 @@ namespace Kyoo.Authentication
{
string publicUrl = _configuration.GetValue("public_url").TrimEnd('/');
+ if (_environment.IsDevelopment())
+ IdentityModelEventSource.ShowPII = true;
+
services.AddControllers();
+ // services.AddIdentityCore()
+ // .AddSignInManager()
+ // .AddDefaultTokenProviders()
+ // .AddUserStore();
+
// services.AddDbContext(options =>
// {
// options.UseNpgsql(_configuration.GetDatabaseConnection("postgres"));
@@ -113,25 +133,25 @@ namespace Kyoo.Authentication
// options.EnableTokenCleanup = true;
// })
.AddInMemoryIdentityResources(IdentityContext.GetIdentityResources())
+ .AddInMemoryApiScopes(IdentityContext.GetScopes())
.AddInMemoryApiResources(IdentityContext.GetApis())
.AddInMemoryClients(IdentityContext.GetClients())
- .AddDeveloperSigningCredential();
- // .AddProfileService()
- // .AddSigninKeys(certificateOptions);
+ .AddProfileService()
+ .AddSigninKeys(certificateOptions);
// TODO implement means to add clients or api scopes for other plugins.
// TODO split scopes (kyoo.read should be task.read, video.read etc)
- services.AddAuthentication(o =>
- {
- o.DefaultScheme = IdentityConstants.ApplicationScheme;
- o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
- })
- .AddIdentityCookies(_ => { });
+ // services.AddAuthentication(o =>
+ // {
+ // o.DefaultScheme = IdentityConstants.ApplicationScheme;
+ // o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
+ // })
+ // .AddIdentityCookies(_ => { });
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Authority = publicUrl;
- options.Audience = "Kyoo";
+ options.Audience = "kyoo";
options.RequireHttpsMetadata = false;
});
@@ -146,10 +166,10 @@ namespace Kyoo.Authentication
{
options.AddPolicy(permission, policy =>
{
- policy.AuthenticationSchemes.Add(IdentityConstants.ApplicationScheme);
+ // policy.AuthenticationSchemes.Add(IdentityConstants.ApplicationScheme);
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.AddRequirements(new AuthRequirement(permission));
- policy.RequireScope($"kyoo.{permission.ToLower()}");
+ // policy.RequireScope($"kyoo.{permission.ToLower()}");
});
}
});
diff --git a/Kyoo.Authentication/Controllers/AuthorizationValidatorHandler.cs b/Kyoo.Authentication/Controllers/AuthorizationValidatorHandler.cs
index 00df30db..1df1c86a 100644
--- a/Kyoo.Authentication/Controllers/AuthorizationValidatorHandler.cs
+++ b/Kyoo.Authentication/Controllers/AuthorizationValidatorHandler.cs
@@ -41,7 +41,7 @@ namespace Kyoo.Authentication
else
{
ICollection defaultPerms = _options.CurrentValue.Default;
- if (defaultPerms.Contains(requirement.Permission.ToLower()))
+ if (defaultPerms?.Contains(requirement.Permission.ToLower()) == true)
context.Succeed(requirement);
}
diff --git a/Kyoo.Authentication/Controllers/UserStore.cs b/Kyoo.Authentication/Controllers/UserStore.cs
new file mode 100644
index 00000000..0649796f
--- /dev/null
+++ b/Kyoo.Authentication/Controllers/UserStore.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Kyoo.Controllers;
+using Kyoo.Models;
+using Microsoft.AspNetCore.Identity;
+
+namespace Kyoo.Authentication
+{
+ ///
+ /// An implementation of an that uses an .
+ ///
+ public class UserStore : IUserStore
+ {
+ ///
+ /// The user repository used to store users.
+ ///
+ private readonly IUserRepository _users;
+
+ ///
+ /// Create a new .
+ ///
+ /// The user repository to use
+ public UserStore(IUserRepository users)
+ {
+ _users = users;
+ }
+
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Implementation of the IDisposable pattern
+ ///
+ /// True if this class should be disposed.
+ protected virtual void Dispose(bool disposing)
+ {
+ bool _ = disposing;
+ // Not implemented because this class has nothing to dispose.
+ }
+
+ ///
+ public Task GetUserIdAsync(User user, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(user.ID.ToString());
+ }
+
+ ///
+ public Task GetUserNameAsync(User user, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(user.Username);
+ }
+
+ ///
+ public Task SetUserNameAsync(User user, string userName, CancellationToken cancellationToken)
+ {
+ user.Username = userName;
+ return Task.CompletedTask;
+ }
+
+ ///
+ public Task GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(user.Slug);
+ }
+
+ ///
+ public Task SetNormalizedUserNameAsync(User user, string normalizedName, CancellationToken cancellationToken)
+ {
+ user.Slug = normalizedName;
+ return Task.CompletedTask;
+ }
+
+ ///
+ public async Task CreateAsync(User user, CancellationToken cancellationToken)
+ {
+ try
+ {
+ await _users.Create(user);
+ return IdentityResult.Success;
+ }
+ catch (Exception ex)
+ {
+ return IdentityResult.Failed(new IdentityError {Code = ex.GetType().Name, Description = ex.Message});
+ }
+ }
+
+ ///
+ public async Task UpdateAsync(User user, CancellationToken cancellationToken)
+ {
+ try
+ {
+ await _users.Edit(user, false);
+ return IdentityResult.Success;
+ }
+ catch (Exception ex)
+ {
+ return IdentityResult.Failed(new IdentityError {Code = ex.GetType().Name, Description = ex.Message});
+ }
+ }
+
+ ///
+ public async Task DeleteAsync(User user, CancellationToken cancellationToken)
+ {
+ try
+ {
+ await _users.Delete(user);
+ return IdentityResult.Success;
+ }
+ catch (Exception ex)
+ {
+ return IdentityResult.Failed(new IdentityError {Code = ex.GetType().Name, Description = ex.Message});
+ }
+ }
+
+ ///
+ public Task FindByIdAsync(string userId, CancellationToken cancellationToken)
+ {
+ return _users.GetOrDefault(int.Parse(userId));
+ }
+
+ ///
+ public Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
+ {
+ return _users.GetOrDefault(normalizedUserName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Kyoo.Authentication/IdentityContext.cs b/Kyoo.Authentication/IdentityContext.cs
index 21d649e1..071a601b 100644
--- a/Kyoo.Authentication/IdentityContext.cs
+++ b/Kyoo.Authentication/IdentityContext.cs
@@ -23,7 +23,7 @@ namespace Kyoo.Authentication
new()
{
ClientId = "kyoo.webapp",
-
+
AccessTokenType = AccessTokenType.Jwt,
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
@@ -33,7 +33,7 @@ namespace Kyoo.Authentication
AllowOfflineAccess = true,
RequireConsent = false,
- AllowedScopes = { "openid", "profile", "kyoo.read", "kyoo.write", "kyoo.play", "kyoo.download", "kyoo.admin" },
+ AllowedScopes = { "openid", "profile", "kyoo.read", "kyoo.write", "kyoo.play", "kyoo.admin" },
RedirectUris = { "/", "/silent.html" },
PostLogoutRedirectUris = { "/logout" }
}
@@ -60,11 +60,6 @@ namespace Kyoo.Authentication
DisplayName = "Allow playback of movies and episodes."
},
new ApiScope
- {
- Name = "kyoo.download",
- DisplayName = "Allow downloading of episodes and movies from kyoo."
- },
- new ApiScope
{
Name = "kyoo.admin",
DisplayName = "Full access to the admin's API and the public API."
@@ -76,9 +71,8 @@ namespace Kyoo.Authentication
{
return new[]
{
- new ApiResource
+ new ApiResource("kyoo", "Kyoo")
{
- Name = "Kyoo",
Scopes = GetScopes().Select(x => x.Name).ToArray()
}
};
diff --git a/Kyoo.Authentication/Models/Options/PermissionOption.cs b/Kyoo.Authentication/Models/Options/PermissionOption.cs
index 0a26f89e..8d6c698d 100644
--- a/Kyoo.Authentication/Models/Options/PermissionOption.cs
+++ b/Kyoo.Authentication/Models/Options/PermissionOption.cs
@@ -1,5 +1,3 @@
-using System.Collections.Generic;
-
namespace Kyoo.Authentication.Models
{
///
diff --git a/Kyoo.Authentication/Views/AccountApi.cs b/Kyoo.Authentication/Views/AccountApi.cs
index bd49da94..81bc3a55 100644
--- a/Kyoo.Authentication/Views/AccountApi.cs
+++ b/Kyoo.Authentication/Views/AccountApi.cs
@@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Security.Claims;
using System.Threading.Tasks;
+using IdentityServer4;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
@@ -14,6 +16,7 @@ using Kyoo.Models;
using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@@ -39,6 +42,7 @@ namespace Kyoo.Authentication.Views
/// A file manager to send profile pictures
///
private readonly IFileManager _files;
+ // private readonly SignInManager _signInManager;
///
/// Options about authentication. Those options are monitored and reloads are supported.
@@ -57,11 +61,13 @@ namespace Kyoo.Authentication.Views
// IIdentityServerInteractionService interaction,
IFileManager files,
IOptions options)
+ //, SignInManager signInManager)
{
_users = users;
// _interaction = interaction;
_files = files;
_options = options;
+ // _signInManager = signInManager;
}
@@ -114,14 +120,14 @@ namespace Kyoo.Authentication.Views
public async Task Login([FromBody] LoginRequest login)
{
// AuthorizationRequest context = await _interaction.GetAuthorizationContextAsync(login.ReturnURL);
- User user = await _users.Get(x => x.Username == login.Username);
+ User user = await _users.GetOrDefault(x => x.Username == login.Username);
if (user == null)
return Unauthorized();
if (!PasswordUtils.CheckPassword(login.Password, user.Password))
return Unauthorized();
- await HttpContext.SignInAsync(user.ID.ToString(), user.ToPrincipal(), StayLogged(login.StayLoggedIn));
+ // await _signInManager.SignInAsync(user, login.StayLoggedIn);
return Ok(new { RedirectUrl = login.ReturnURL, IsOk = true });
}
@@ -142,7 +148,15 @@ namespace Kyoo.Authentication.Views
{
code = "ExpiredOTAC", description = "The OTAC has expired. Try to login with your password."
});
- await HttpContext.SignInAsync(user.ID.ToString(), user.ToPrincipal(), StayLogged(otac.StayLoggedIn));
+
+
+ IdentityServerUser iduser = new(user.ID.ToString())
+ {
+ DisplayName = user.Username
+ };
+
+ await HttpContext.SignInAsync(iduser, StayLogged(otac.StayLoggedIn));
+ // await _signInManager.SignInAsync(user, otac.StayLoggedIn);
return Ok();
}
@@ -153,22 +167,23 @@ namespace Kyoo.Authentication.Views
[Authorize]
public async Task Logout()
{
- await HttpContext.SignOutAsync();
+ // await _signInManager.SignOutAsync();
return Ok();
}
// TODO check with the extension method
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
- User user = await _users.Get(int.Parse(context.Subject.GetSubjectId()));
+ User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
if (user == null)
return;
context.IssuedClaims.AddRange(user.GetClaims());
+ context.IssuedClaims.Add(new Claim("permissions", string.Join(',', user.Permissions)));
}
public async Task IsActiveAsync(IsActiveContext context)
{
- User user = await _users.Get(int.Parse(context.Subject.GetSubjectId()));
+ User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
context.IsActive = user != null;
}
@@ -186,8 +201,10 @@ namespace Kyoo.Authentication.Views
[Authorize]
public async Task> Update([FromForm] AccountUpdateRequest data)
{
- User user = await _users.Get(int.Parse(HttpContext.User.GetSubjectId()));
-
+ User user = await _users.GetOrDefault(int.Parse(HttpContext.User.GetSubjectId()));
+
+ if (user == null)
+ return Unauthorized();
if (!string.IsNullOrEmpty(data.Email))
user.Email = data.Email;
if (!string.IsNullOrEmpty(data.Username))
@@ -204,7 +221,7 @@ namespace Kyoo.Authentication.Views
[HttpGet("permissions")]
public ActionResult> GetDefaultPermissions()
{
- return _options.Value.Permissions.Default;
+ return _options.Value.Permissions.Default ?? Array.Empty();
}
}
}
\ No newline at end of file
diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs
index 6c100622..2cd0c909 100644
--- a/Kyoo.Common/Controllers/ILibraryManager.cs
+++ b/Kyoo.Common/Controllers/ILibraryManager.cs
@@ -84,6 +84,7 @@ namespace Kyoo.Controllers
/// The type of the resource
/// If the item is not found
/// The resource found
+ [ItemNotNull]
Task Get(int id) where T : class, IResource;
///
@@ -93,6 +94,7 @@ namespace Kyoo.Controllers
/// The type of the resource
/// If the item is not found
/// The resource found
+ [ItemNotNull]
Task Get(string slug) where T : class, IResource;
///
@@ -102,6 +104,7 @@ namespace Kyoo.Controllers
/// The type of the resource
/// If the item is not found
/// The first resource found that match the where function
+ [ItemNotNull]
Task Get(Expression> where) where T : class, IResource;
///
@@ -111,6 +114,7 @@ namespace Kyoo.Controllers
/// The season's number
/// If the item is not found
/// The season found
+ [ItemNotNull]
Task Get(int showID, int seasonNumber);
///
@@ -120,6 +124,7 @@ namespace Kyoo.Controllers
/// The season's number
/// If the item is not found
/// The season found
+ [ItemNotNull]
Task Get(string showSlug, int seasonNumber);
///
@@ -130,6 +135,7 @@ namespace Kyoo.Controllers
/// The episode's number
/// If the item is not found
/// The episode found
+ [ItemNotNull]
Task Get(int showID, int seasonNumber, int episodeNumber);
///
@@ -140,6 +146,7 @@ namespace Kyoo.Controllers
/// The episode's number
/// If the item is not found
/// The episode found
+ [ItemNotNull]
Task Get(string showSlug, int seasonNumber, int episodeNumber);
///
@@ -148,7 +155,8 @@ namespace Kyoo.Controllers
/// The slug of the track
/// The type (Video, Audio or Subtitle)
/// If the item is not found
- /// The tracl found
+ /// The track found
+ [ItemNotNull]
Task