Create robot tests for login and register

This commit is contained in:
Zoe Roux 2022-05-08 00:40:36 +02:00
parent ad57df45d7
commit 14d23af0fd
No known key found for this signature in database
GPG Key ID: 54F19BB73170955D
20 changed files with 205 additions and 267 deletions

View File

@ -70,7 +70,6 @@ csharp_indent_case_contents = true
csharp_indent_switch_labels = true csharp_indent_switch_labels = true
# Modifiers # Modifiers
dotnet_style_readonly_field = true:suggestion dotnet_style_readonly_field = true:suggestion
csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
# Naming style # Naming style
dotnet_naming_symbols.privates.applicable_kinds = property,method,event,delegate dotnet_naming_symbols.privates.applicable_kinds = property,method,event,delegate

View File

@ -10,7 +10,7 @@ title: Build
To develop for Kyoo, you will need the **.NET 5.0 SDK**, **node** and **npm** for the webapp. If you want to build the transcoder, you will also need a cmake compatible environment. To develop for Kyoo, you will need the **.NET 5.0 SDK**, **node** and **npm** for the webapp. If you want to build the transcoder, you will also need a cmake compatible environment.
## Building ## Building
To run the development server, simply open the .sln file with your favorite C# IDE (like Jetbrain's Rider or Visual Studio) and press run or you can use the CLI and use the ```dotnet run dotnet run -p src/Kyoo.Host.Console --launch-profile "Console"``` command. To run the development server, simply open the .sln file with your favorite C# IDE (like Jetbrain's Rider or Visual Studio) and press run or you can use the CLI and use the ```dotnet run --project src/Kyoo.Host.Console --launch-profile "Console"``` command.
To pack the application, run the ```dotnet publish -c Release -o <build_path> Kyoo.Host.Console``` command. This will build the server, the webapp and the transcoder and output files in the <build_path> directory. To pack the application, run the ```dotnet publish -c Release -o <build_path> Kyoo.Host.Console``` command. This will build the server, the webapp and the transcoder and output files in the <build_path> directory.
## Skipping parts ## Skipping parts

View File

@ -17,6 +17,7 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic; using System.Collections.Generic;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
{ {
@ -62,11 +63,13 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The list of shows the user has finished. /// The list of shows the user has finished.
/// </summary> /// </summary>
[SerializeIgnore]
public ICollection<Show> Watched { get; set; } public ICollection<Show> Watched { get; set; }
/// <summary> /// <summary>
/// The list of episodes the user is watching (stopped in progress or the next episode of the show) /// The list of episodes the user is watching (stopped in progress or the next episode of the show)
/// </summary> /// </summary>
[SerializeIgnore]
public ICollection<WatchedEpisode> CurrentlyWatching { get; set; } public ICollection<WatchedEpisode> CurrentlyWatching { get; set; }
} }
} }

View File

@ -74,14 +74,16 @@ namespace Kyoo.Authentication
public void Configure(ContainerBuilder builder) public void Configure(ContainerBuilder builder)
{ {
builder.RegisterType<PermissionValidator>().As<IPermissionValidator>().SingleInstance(); builder.RegisterType<PermissionValidator>().As<IPermissionValidator>().SingleInstance();
builder.RegisterType<TokenController>().As<ITokenController>().SingleInstance();
} }
/// <inheritdoc /> /// <inheritdoc />
public void Configure(IServiceCollection services) public void Configure(IServiceCollection services)
{ {
Uri publicUrl = _configuration.GetPublicUrl(); Uri publicUrl = _configuration.GetPublicUrl();
AuthenticationOption jwt = new(); AuthenticationOption jwt = ConfigurationBinder.Get<AuthenticationOption>(
_configuration.GetSection(AuthenticationOption.Path).Bind(jwt); _configuration.GetSection(AuthenticationOption.Path)
);
// TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos) // TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos)
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

View File

@ -72,7 +72,7 @@ namespace Kyoo.Authentication
/// <summary> /// <summary>
/// The permission to validate. /// The permission to validate.
/// </summary> /// </summary>
private readonly string _permission; private readonly string? _permission;
/// <summary> /// <summary>
/// The kind of permission needed. /// The kind of permission needed.
@ -133,7 +133,7 @@ namespace Kyoo.Authentication
/// <inheritdoc /> /// <inheritdoc />
public async Task OnAuthorizationAsync(AuthorizationFilterContext context) public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{ {
string permission = _permission; string? permission = _permission;
Kind? kind = _kind; Kind? kind = _kind;
if (permission == null || kind == null) if (permission == null || kind == null)

View File

@ -31,8 +31,8 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
namespace Kyoo.Authentication; namespace Kyoo.Authentication
{
/// <summary> /// <summary>
/// The service that controls jwt creation and validation. /// The service that controls jwt creation and validation.
/// </summary> /// </summary>
@ -147,3 +147,4 @@ public class TokenController : ITokenController
throw new SecurityTokenException("Token not associated to any user."); throw new SecurityTokenException("Token not associated to any user.");
} }
} }
}

View File

@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
@ -34,7 +35,8 @@ namespace Kyoo.Authentication
/// <returns>The list of permissions</returns> /// <returns>The list of permissions</returns>
public static ICollection<string> GetPermissions(this ClaimsPrincipal user) public static ICollection<string> GetPermissions(this ClaimsPrincipal user)
{ {
return user.Claims.FirstOrDefault(x => x.Type == "permissions")?.Value.Split(','); return user.Claims.FirstOrDefault(x => x.Type == "permissions")?.Value.Split(',')
?? Array.Empty<string>();
} }
} }
} }

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<LoginRoot>../Kyoo.WebLogin/</LoginRoot> <LoginRoot>../Kyoo.WebLogin/</LoginRoot>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,46 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
namespace Kyoo.Authentication.Models.DTO
{
/// <summary>
/// A model only used on account update requests.
/// </summary>
public class AccountUpdateRequest
{
/// <summary>
/// The new email address of the user
/// </summary>
[EmailAddress(ErrorMessage = "The email is invalid.")]
public string Email { get; set; }
/// <summary>
/// The new username of the user.
/// </summary>
[MinLength(4, ErrorMessage = "The username must have at least 4 characters")]
public string Username { get; set; }
/// <summary>
/// The picture icon.
/// </summary>
public IFormFile Picture { get; set; }
}
}

View File

@ -34,13 +34,14 @@ namespace Kyoo.Authentication.Models.DTO
public string Password { get; set; } public string Password { get; set; }
/// <summary> /// <summary>
/// Should the user stay logged in? If true a cookie will be put. /// Initializes a new instance of the <see cref="LoginRequest"/> class.
/// </summary> /// </summary>
public bool StayLoggedIn { get; set; } /// <param name="username">The user's username.</param>
/// <param name="password">The user's password.</param>
/// <summary> public LoginRequest(string username, string password)
/// The return url of the login flow. {
/// </summary> Username = username;
public string ReturnURL { get; set; } Password = password;
}
} }
} }

View File

@ -1,36 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
namespace Kyoo.Authentication.Models.DTO
{
/// <summary>
/// A model to represent an otac request
/// </summary>
public class OtacRequest
{
/// <summary>
/// The One Time Access Code
/// </summary>
public string Otac { get; set; }
/// <summary>
/// Should the user stay logged
/// </summary>
public bool StayLoggedIn { get; set; }
}
}

View File

@ -1,41 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
namespace Kyoo.Authentication.Models.DTO
{
/// <summary>
/// A one time access token
/// </summary>
public class OtacResponse
{
/// <summary>
/// The One Time Access Token that allow one to connect to an account without typing a password or without
/// any kind of verification. This is valid only one time and only for a short period of time.
/// </summary>
public string OTAC { get; set; }
/// <summary>
/// Create a new <see cref="OtacResponse"/>.
/// </summary>
/// <param name="otac">The one time access token.</param>
public OtacResponse(string otac)
{
OTAC = otac;
}
}
}

View File

@ -44,9 +44,22 @@ namespace Kyoo.Authentication.Models.DTO
/// <summary> /// <summary>
/// The user's password. /// The user's password.
/// </summary> /// </summary>
[MinLength(8, ErrorMessage = "The password must have at least {1} characters")] [MinLength(4, ErrorMessage = "The password must have at least {1} characters")]
public string Password { get; set; } public string Password { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RegisterRequest"/> class.
/// </summary>
/// <param name="email">The user email address.</param>
/// <param name="username">The user's username.</param>
/// <param name="password">The user's password.</param>
public RegisterRequest(string email, string username, string password)
{
Email = email;
Username = username;
Password = password;
}
/// <summary> /// <summary>
/// Convert this register request to a new <see cref="User"/> class. /// Convert this register request to a new <see cref="User"/> class.
/// </summary> /// </summary>

View File

@ -55,5 +55,18 @@ namespace Kyoo.Authentication
[JsonProperty("expire_in")] [JsonProperty("expire_in")]
[JsonPropertyName("expire_in")] [JsonPropertyName("expire_in")]
public TimeSpan ExpireIn { get; set; } public TimeSpan ExpireIn { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="JwtToken"/> class.
/// </summary>
/// <param name="accessToken">The access token used to authorize requests.</param>
/// <param name="refreshToken">The refresh token to retrieve a new access token.</param>
/// <param name="expireIn">When the access token will expire.</param>
public JwtToken(string accessToken, string refreshToken, TimeSpan expireIn)
{
AccessToken = accessToken;
RefreshToken = refreshToken;
ExpireIn = expireIn;
}
} }
} }

View File

@ -28,19 +28,24 @@ namespace Kyoo.Authentication.Models
/// </summary> /// </summary>
public const string Path = "authentication"; public const string Path = "authentication";
/// <summary>
/// The default jwt secret.
/// </summary>
public const string DefaultSecret = "jwt-secret";
/// <summary> /// <summary>
/// The secret used to encrypt the jwt. /// The secret used to encrypt the jwt.
/// </summary> /// </summary>
public string Secret { get; set; } public string Secret { get; set; } = DefaultSecret;
/// <summary> /// <summary>
/// Options for permissions /// Options for permissions
/// </summary> /// </summary>
public PermissionOption Permissions { get; set; } public PermissionOption Permissions { get; set; } = new();
/// <summary> /// <summary>
/// Root path of user's profile pictures. /// Root path of user's profile pictures.
/// </summary> /// </summary>
public string ProfilePicturePath { get; set; } public string ProfilePicturePath { get; set; } = "users/";
} }
} }

View File

@ -31,11 +31,11 @@ namespace Kyoo.Authentication.Models
/// <summary> /// <summary>
/// The default permissions that will be given to a non-connected user. /// The default permissions that will be given to a non-connected user.
/// </summary> /// </summary>
public string[] Default { get; set; } public string[] Default { get; set; } = new[] { "overall.read", "overall.write" };
/// <summary> /// <summary>
/// Permissions applied to a new user. /// Permissions applied to a new user.
/// </summary> /// </summary>
public string[] NewUser { get; set; } public string[] NewUser { get; set; } = new[] { "overall.read", "overall.write" };
} }
} }

View File

@ -63,6 +63,16 @@ namespace Kyoo.Authentication.Views
_token = token; _token = token;
} }
/// <summary>
/// Create a new Forbidden result from an object.
/// </summary>
/// <param name="value">The json value to output on the response.</param>
/// <returns>A new forbidden result with the given json object.</returns>
public static ObjectResult Forbid(object value)
{
return new ObjectResult(value) { StatusCode = StatusCodes.Status403Forbidden };
}
/// <summary> /// <summary>
/// Login. /// Login.
/// </summary> /// </summary>
@ -71,7 +81,7 @@ namespace Kyoo.Authentication.Views
/// </remarks> /// </remarks>
/// <param name="request">The body of the request.</param> /// <param name="request">The body of the request.</param>
/// <returns>A new access and a refresh token.</returns> /// <returns>A new access and a refresh token.</returns>
/// <response code="400">The user and password does not match.</response> /// <response code="403">The user and password does not match.</response>
[HttpPost("login")] [HttpPost("login")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
@ -79,14 +89,13 @@ namespace Kyoo.Authentication.Views
{ {
User user = await _users.GetOrDefault(x => x.Username == request.Username); User user = await _users.GetOrDefault(x => x.Username == request.Username);
if (user == null || !BCryptNet.Verify(request.Password, user.Password)) if (user == null || !BCryptNet.Verify(request.Password, user.Password))
return BadRequest(new RequestError("The user and password does not match.")); return Forbid(new RequestError("The user and password does not match."));
return new JwtToken return new JwtToken(
{ _token.CreateAccessToken(user, out TimeSpan expireIn),
AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn), await _token.CreateRefreshToken(user),
RefreshToken = await _token.CreateRefreshToken(user), expireIn
ExpireIn = expireIn );
};
} }
/// <summary> /// <summary>
@ -115,12 +124,11 @@ namespace Kyoo.Authentication.Views
return Conflict(new RequestError("A user already exists with this username.")); return Conflict(new RequestError("A user already exists with this username."));
} }
return new JwtToken return new JwtToken(
{ _token.CreateAccessToken(user, out TimeSpan expireIn),
AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn), await _token.CreateRefreshToken(user),
RefreshToken = await _token.CreateRefreshToken(user), expireIn
ExpireIn = expireIn );
};
} }
/// <summary> /// <summary>
@ -141,12 +149,11 @@ namespace Kyoo.Authentication.Views
{ {
int userId = _token.GetRefreshTokenUserID(token); int userId = _token.GetRefreshTokenUserID(token);
User user = await _users.Get(userId); User user = await _users.Get(userId);
return new JwtToken return new JwtToken(
{ _token.CreateAccessToken(user, out TimeSpan expireIn),
AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn), await _token.CreateRefreshToken(user),
RefreshToken = await _token.CreateRefreshToken(user), expireIn
ExpireIn = expireIn );
};
} }
catch (ItemNotFoundException) catch (ItemNotFoundException)
{ {
@ -176,5 +183,7 @@ namespace Kyoo.Authentication.Views
return Forbid(); return Forbid();
return await _users.Get(userID); return await _users.Get(userID);
} }
// TODO: Add a put to edit the current user.
} }
} }

View File

@ -61,8 +61,8 @@
"authentication": { "authentication": {
"permissions": { "permissions": {
"default": ["overall.read", "overall.write", "overall.create", "overall.delete", "admin.read", "admin.write"], "default": ["overall.read", "overall.write"],
"newUser": ["overall.read", "overall.write", "overall.create", "overall.delete", "admin.read", "admin.write"] "newUser": ["overall.read", "overall.write"]
}, },
"profilePicturePath": "users/", "profilePicturePath": "users/",
"secret": "jwt-secret" "secret": "jwt-secret"

View File

@ -1,7 +1,18 @@
*** Settings *** *** Settings ***
Documentation Tests of the /auth route. Documentation Tests of the /auth route.
... Ensures that the user can authenticate on kyoo. ... Ensures that the user can authenticate on kyoo.
Resource ${RESOURCES}/rest.resource Resource ../rest.resource
*** Test Cases *** *** Test Cases ***
BadAccount
[Documentation] Login fails if user does not exist
POST /auth/login {"username": "toto", "password": "tata"}
Output
Integer response status 403
Register
[Documentation] Create a new user and login in it
POST /auth/register {"username": "toto", "password": "tata", "email": "mail@kyoo.moe"}
Output
Integer response status 403

View File

@ -1,3 +1,4 @@
*** Settings *** *** Settings ***
Documentation Common things to handle rest requests Documentation Common things to handle rest requests
Library REST
Library REST http://localhost:5000/api