From d257901c6400f678d0025ef4cfe5f61d6d579008 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 8 May 2022 23:14:08 +0200 Subject: [PATCH] Add more robot tests --- .../Models/Resources/User.cs | 2 + .../Controllers/PermissionValidator.cs | 5 +- .../Controllers/TokenController.cs | 6 -- .../Models/Options/AuthenticationOption.cs | 2 +- src/Kyoo.Authentication/Views/AuthApi.cs | 67 ++++++++++++++- src/Kyoo.Core/Views/Helper/CrudApi.cs | 8 +- src/Kyoo.Host.Generic/settings.json | 2 +- tests/robot/Authentication/auth.robot | 18 ---- tests/robot/auth/auth.robot | 83 +++++++++++++++++++ tests/robot/pyproject.toml | 4 + 10 files changed, 163 insertions(+), 34 deletions(-) delete mode 100644 tests/robot/Authentication/auth.robot create mode 100644 tests/robot/auth/auth.robot create mode 100644 tests/robot/pyproject.toml diff --git a/src/Kyoo.Abstractions/Models/Resources/User.cs b/src/Kyoo.Abstractions/Models/Resources/User.cs index 17ad2591..cf9f5368 100644 --- a/src/Kyoo.Abstractions/Models/Resources/User.cs +++ b/src/Kyoo.Abstractions/Models/Resources/User.cs @@ -45,6 +45,7 @@ namespace Kyoo.Abstractions.Models /// /// The user password (hashed, it can't be read like that). The hashing format is implementation defined. /// + [SerializeIgnore] public string Password { get; set; } /// @@ -55,6 +56,7 @@ namespace Kyoo.Abstractions.Models /// /// Arbitrary extra data that can be used by specific authentication implementations. /// + [SerializeIgnore] public Dictionary ExtraData { get; set; } /// diff --git a/src/Kyoo.Authentication/Controllers/PermissionValidator.cs b/src/Kyoo.Authentication/Controllers/PermissionValidator.cs index d3200d1c..0b4f8768 100644 --- a/src/Kyoo.Authentication/Controllers/PermissionValidator.cs +++ b/src/Kyoo.Authentication/Controllers/PermissionValidator.cs @@ -96,7 +96,10 @@ namespace Kyoo.Authentication /// The kind of permission needed. /// The group of the permission. /// The option containing default values. - public PermissionValidatorFilter(string permission, Kind kind, Group group, + public PermissionValidatorFilter( + string permission, + Kind kind, + Group group, IOptionsMonitor options) { _permission = permission; diff --git a/src/Kyoo.Authentication/Controllers/TokenController.cs b/src/Kyoo.Authentication/Controllers/TokenController.cs index db841f89..c7962cd4 100644 --- a/src/Kyoo.Authentication/Controllers/TokenController.cs +++ b/src/Kyoo.Authentication/Controllers/TokenController.cs @@ -62,9 +62,6 @@ namespace Kyoo.Authentication /// public string CreateAccessToken(User user, out TimeSpan expireIn) { - if (user == null) - throw new ArgumentNullException(nameof(user)); - expireIn = new TimeSpan(1, 0, 0); SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret)); @@ -94,9 +91,6 @@ namespace Kyoo.Authentication /// public Task CreateRefreshToken(User user) { - if (user == null) - throw new ArgumentNullException(nameof(user)); - SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret)); SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature); JwtSecurityToken token = new( diff --git a/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs b/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs index 2c2e6711..b57ed640 100644 --- a/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs +++ b/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs @@ -31,7 +31,7 @@ namespace Kyoo.Authentication.Models /// /// The default jwt secret. /// - public const string DefaultSecret = "jwt-secret"; + public const string DefaultSecret = "4c@mraGB!KRfF@kpS8739y9FcHemKxBsqqxLbdR?"; /// /// The secret used to encrypt the jwt. diff --git a/src/Kyoo.Authentication/Views/AuthApi.cs b/src/Kyoo.Authentication/Views/AuthApi.cs index 9906fe8a..b237975b 100644 --- a/src/Kyoo.Authentication/Views/AuthApi.cs +++ b/src/Kyoo.Authentication/Views/AuthApi.cs @@ -175,15 +175,76 @@ namespace Kyoo.Authentication.Views /// The currently authenticated user. /// The given access token is invalid. [HttpGet("me")] - [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> GetMe() { if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID)) return Forbid(); - return await _users.Get(userID); + try + { + return await _users.Get(userID); + } + catch (ItemNotFoundException) + { + return Forbid(); + } } - // TODO: Add a put to edit the current user. + /// + /// Edit self + /// + /// + /// Edit information about the currently authenticated user. + /// + /// The new data for the current user. + /// + /// Should old properties of the resource be discarded or should null values considered as not changed? + /// + /// The currently authenticated user after modifications. + /// The given access token is invalid. + [HttpPut("me")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> EditMe(User user, [FromQuery] bool resetOld = true) + { + if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID)) + return Forbid(); + try + { + user.ID = userID; + return await _users.Edit(user, resetOld); + } + catch (ItemNotFoundException) + { + return Forbid(); + } + } + + /// + /// Delete account + /// + /// + /// Delete the current account. + /// + /// The currently authenticated user after modifications. + /// The given access token is invalid. + [HttpDelete("me")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> DeleteMe() + { + if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID)) + return Forbid(); + try + { + await _users.Delete(userID); + return NoContent(); + } + catch (ItemNotFoundException) + { + return Forbid(); + } + } } } diff --git a/src/Kyoo.Core/Views/Helper/CrudApi.cs b/src/Kyoo.Core/Views/Helper/CrudApi.cs index 16e8beb1..3aa3cc01 100644 --- a/src/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/src/Kyoo.Core/Views/Helper/CrudApi.cs @@ -220,7 +220,7 @@ namespace Kyoo.Core.Api /// No item could be found with the given id or slug. [HttpDelete("{identifier:id}")] [PartialPermission(Kind.Delete)] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Delete(Identifier identifier) { @@ -236,7 +236,7 @@ namespace Kyoo.Core.Api return NotFound(); } - return Ok(); + return NoContent(); } /// @@ -250,7 +250,7 @@ namespace Kyoo.Core.Api /// One or multiple filters are invalid. [HttpDelete] [PartialPermission(Kind.Delete)] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] public async Task Delete([FromQuery] Dictionary where) { @@ -263,7 +263,7 @@ namespace Kyoo.Core.Api return BadRequest(new RequestError(ex.Message)); } - return Ok(); + return NoContent(); } } } diff --git a/src/Kyoo.Host.Generic/settings.json b/src/Kyoo.Host.Generic/settings.json index 778fb20a..bd3f637c 100644 --- a/src/Kyoo.Host.Generic/settings.json +++ b/src/Kyoo.Host.Generic/settings.json @@ -65,7 +65,7 @@ "newUser": ["overall.read", "overall.write"] }, "profilePicturePath": "users/", - "secret": "jwt-secret" + "secret": "4c@mraGB!KRfF@kpS8740y9FcHemKxBsqqxLbdR?" }, "tvdb": { diff --git a/tests/robot/Authentication/auth.robot b/tests/robot/Authentication/auth.robot deleted file mode 100644 index 623666bf..00000000 --- a/tests/robot/Authentication/auth.robot +++ /dev/null @@ -1,18 +0,0 @@ -*** Settings *** -Documentation Tests of the /auth route. -... Ensures that the user can authenticate on kyoo. -Resource ../rest.resource - - -*** 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 diff --git a/tests/robot/auth/auth.robot b/tests/robot/auth/auth.robot new file mode 100644 index 00000000..5070f51b --- /dev/null +++ b/tests/robot/auth/auth.robot @@ -0,0 +1,83 @@ +*** Settings *** +Documentation Tests of the /auth route. +... Ensures that the user can authenticate on kyoo. + +Resource ../rest.resource + + +*** Keywords *** +Login + [Documentation] Shortcut to login with the given username for future requests + [Arguments] ${username} + &{res}= POST /auth/login {"username": "${username}", "password": "password-${username}"} + Output + Integer response status 200 + String response body access_token + Set Headers {"Authorization": "Bearer ${res.body.access_token}"} + +Register + [Documentation] Shortcut to register with the given username for future requests + [Arguments] ${username} + &{res}= POST + ... /auth/register + ... {"username": "${username}", "password": "password-${username}", "email": "${username}@kyoo.moe"} + Output + Integer response status 200 + String response body access_token + Set Headers {"Authorization": "Bearer ${res.body.access_token}"} + +Logout + [Documentation] Logout the current user, only the local client is affected. + Set Headers {"Authorization": ""} + + +*** Test Cases *** +Me cant be accessed without an account + Get /auth/me + Output + Integer response status 403 + +Bad Account + [Documentation] Login fails if user does not exist + POST /auth/login {"username": "i-don-t-exist", "password": "pass"} + Output + Integer response status 403 + +Register + [Documentation] Create a new user and login in it + Register user-1 + [Teardown] DELETE /auth/me + +Register Duplicates + [Documentation] If two users tries to register with the same username, it fails + Register user-duplicate + # We can't use the `Register` keyword because it assert for success + POST /auth/register {"username": "user-duplicate", "password": "pass", "email": "mail@kyoo.moe"} + Output + Integer response status 409 + [Teardown] DELETE /auth/me + +Delete Account + [Documentation] Check if a user can delete it's account + Register I-should-be-deleted + DELETE /auth/me + Output + Integer response status 204 + +Login + [Documentation] Create a new user and login in it + Register login-user + ${res}= GET /auth/me + Output + Integer response status 200 + String response body username login-user + + Logout + Login login-user + ${me}= Get /auth/me + Output + Output ${me} + Should Be Equal As Strings ${res["body"]} ${me["body"]} + + [Teardown] DELETE /auth/me + diff --git a/tests/robot/pyproject.toml b/tests/robot/pyproject.toml new file mode 100644 index 00000000..e5e1011e --- /dev/null +++ b/tests/robot/pyproject.toml @@ -0,0 +1,4 @@ +[tool.robotidy] +configure = [ + "MergeAndOrderSections:order=comments,settings,keywords,variables,testcases" +]