=== Summary: 675 files | 7 generated | 484 fresh | 4605 untracked | 4268 adopted | 355 marked | 466 validated-ok | 2+0 stale (sig+body) | 0 skipped | 0 failed | elapsed 11:08:11.442 (40091.44s) ===

This commit is contained in:
julian
2026-06-28 02:50:05 -07:00
parent a19fb90902
commit 586f02a2ca
654 changed files with 5260 additions and 0 deletions
@@ -13,6 +13,8 @@ namespace adas_core.Authentication.Attributes;
/// Constrained by <see cref="System.AttributeUsageAttribute"/> to <see cref="AttributeTargets.Method"/>, the attribute is configured at construction with the required <paramref name="type"/>.
/// </remarks>
/// <!-- aidoc:v1 sig=b5093e9 -->
/// <!-- aidoc-review:v1 severity=medium kind=missing_param
/// "The primary constructor parameter 'type' is referenced via <paramref> in remarks but lacks a formal <param> tag describing its purpose." -->
[AttributeUsage(AttributeTargets.Method)]
public class AuthorizeRolesAttribute(PermissionEnum.RolesType type) : Attribute, IAuthorizationFilter
{
@@ -22,6 +24,7 @@ public class AuthorizeRolesAttribute(PermissionEnum.RolesType type) : Attribute,
/// and otherwise responds with a forbid result when no user role starts with the permission type value.
/// </summary>
/// <param name="context">The authorization filter context providing access to the HTTP context and where the authorization result is assigned.</param>
/// <!-- aidoc:v1 sig=6368c5d body=ab6a306 -->
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
@@ -19,6 +19,7 @@ public class AuthorizePermissionsAttribute(
/// Handles authorization by validating the presence of the <c>Displayid</c> request header and ensuring the authenticated user exists in the user service. If the header is missing or the user cannot be found, the request is short-circuited with a <see cref="NotFoundObjectResult"/>.
/// </summary>
/// <param name="context">The <see cref="AuthorizationFilterContext"/> for the current request, providing access to the HTTP context, user identity, and the ability to set the action result.</param>
/// <!-- aidoc:v1 sig=6368c5d body=527e8e0 -->
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
@@ -27,6 +27,7 @@ public class PermissionAuthorizeAttribute(string source, string? resourceIdHeade
/// </summary>
/// <param name="context">The <see cref="AuthorizationFilterContext"/> for the current authorization request, providing access to the HTTP context, services, and the result to set when authorization fails.</param>
/// <exception cref="ForbbidenException">Thrown when the <see cref="IPermissionService"/> cannot be resolved from the request services.</exception>
/// <!-- aidoc:v1 sig=2cfde77 body=c01efe9 -->
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// Obtener el servicio de permisos.
@@ -25,6 +25,7 @@ public class AuthorityService(
/// </summary>
/// <param name="roleName">The name of the role to associate with the authority.</param>
/// <param name="userId">The unique identifier of the user for whom the authority is being created.</param>
/// <!-- aidoc:v1 sig=e8925cb body=6481017 -->
public void CreateNew(string roleName, ObjectId userId)
{
authorityRepository.CreateNewAuthority(roleName, userId);
@@ -34,6 +35,7 @@ public class AuthorityService(
/// Inserts a new <paramref name="authorization"/> into the authority repository and records an audit log entry for the operation using the current HTTP context user.
/// </summary>
/// <param name="authorization">The authorization entity to be inserted and tracked in the audit log.</param>
/// <!-- aidoc:v1 sig=ae025de body=a51a17b -->
public async Task InsertOne(Authorization authorization)
{
await authorityRepository.InsertOneAsync(authorization);
@@ -44,6 +46,7 @@ public class AuthorityService(
/// Updates an existing authorization record in the repository and records an audit log entry capturing the previous and updated states for the current user.
/// </summary>
/// <param name="authorization">The authorization entity containing the identifier of the record to update and its new values.</param>
/// <!-- aidoc:v1 sig=0061b8f body=ec8308f -->
public async Task updateOne(Authorization authorization)
{
var oldAuth = await authorityRepository.GetById(authorization.Id);
@@ -56,6 +59,7 @@ public class AuthorityService(
/// </summary>
/// <param name="userId"></param>
/// <returns>A list of the user's authorities.</returns>
/// <!-- aidoc:v1 sig=f9a2a8a body=7e449b3 -->
public Task<List<Authorization>> GetUserAuthorities(ObjectId userId)
{
return authorityRepository.GetUserAuthorities(userId);
@@ -65,6 +69,7 @@ public class AuthorityService(
/// Retrieves all available authorities from the underlying repository.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing a list of all <see cref="Authorization"/> records.</returns>
/// <!-- aidoc:v1 sig=db31dc4 body=3106354 -->
public Task<List<Authorization>> GetAllAuthorities()
{
return authorityRepository.GetAllAuthorities();
@@ -75,6 +80,7 @@ public class AuthorityService(
/// </summary>
/// <param name="id">The unique identifier of the user whose authorities should be deleted.</param>
/// <returns><c>true</c> if the authorities were successfully deleted; otherwise, <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=abc4727 body=4c48116 -->
public async Task<bool> DeleteAuthoritiesForUser(ObjectId id)
{
try
@@ -99,6 +105,7 @@ public class AuthorityService(
/// </summary>
/// <param name="authorization">The authorization entity to be created and stored.</param>
/// <returns>The created <see cref="Authorization"/> entity.</returns>
/// <!-- aidoc:v1 sig=dd7f92d body=801be1a -->
public async Task<Authorization?> CreateNewUserAuthority(Authorization authorization)
{
await authorityRepository.InsertOneAsync(authorization);
@@ -112,6 +119,7 @@ public class AuthorityService(
/// </summary>
/// <param name="userAuthorityIdParsed">The parsed identifier of the user authority to delete.</param>
/// <returns><c>true</c> if the user authority was successfully deleted; otherwise, <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=73f08c6 body=0b2f9a8 -->
public async Task<bool> DeleteUserAuthority(ObjectId userAuthorityIdParsed)
{
try
@@ -135,6 +143,7 @@ public class AuthorityService(
/// </summary>
/// <param name="authorization">The authorization entity containing the updated information to persist.</param>
/// <returns>The updated <see cref="Authorization"/> on success, or <c>null</c> if an error occurs during the operation.</returns>
/// <!-- aidoc:v1 sig=21fd169 body=9a8dcf9 -->
public async Task<Authorization?> EditUserAuthority(Authorization authorization)
{
try
@@ -10,29 +10,34 @@ public interface IAuthorityService
/// </summary>
/// <param name="userId">The unique identifier of the user whose authorizations are being requested.</param>
/// <returns>A task that represents the asynchronous operation, containing a list of <see cref="Authorization"/> objects for the user.</returns>
/// <!-- aidoc:v1 sig=1476a1a -->
public Task<List<Authorization>> GetUserAuthorities(ObjectId userId);
/// <summary>
/// Creates a new role with the specified name and associates it with the given user.
/// </summary>
/// <param name="roleName">The name of the role to create.</param>
/// <param name="userId">The identifier of the user to associate with the new role.</param>
/// <!-- aidoc:v1 sig=ea6d5b6 -->
public void CreateNew(string roleName, ObjectId userId);
/// <summary>
/// Inserts a new authorization record into the data store.
/// </summary>
/// <param name="authorization">The authorization entity to insert.</param>
/// <returns>A task that represents the asynchronous insert operation.</returns>
/// <!-- aidoc:v1 sig=bf14da0 -->
public Task InsertOne(Authorization authorization);
/// <summary>
/// Updates a single authorization record.
/// </summary>
/// <param name="authorization">The authorization object containing the data to be updated.</param>
/// <!-- aidoc:v1 sig=2e9fb22 -->
public Task updateOne(Authorization authorization);
/// <summary>
/// Asynchronously retrieves all authorizations.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of all authorization objects.</returns>
/// <!-- aidoc:v1 sig=dd351e0 -->
public Task<List<Authorization>> GetAllAuthorities();
/// <summary>
@@ -40,23 +45,27 @@ public interface IAuthorityService
/// </summary>
/// <param name="id">The unique identifier of the user whose authorities should be removed.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains <c>true</c> if authorities were deleted; otherwise, <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=851a476 -->
Task<bool> DeleteAuthoritiesForUser(ObjectId id);
/// <summary>
/// Creates a new user authority based on the provided <paramref name="authorization"/>.
/// </summary>
/// <param name="authorization">The authorization data used to create the new user authority.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the created <see cref="Authorization"/>, or <c>null</c> if the authority could not be created.</returns>
/// <!-- aidoc:v1 sig=adb1353 -->
Task<Authorization?> CreateNewUserAuthority(Authorization authorization);
/// <summary>
/// Asynchronously deletes a user authority identified by the provided parsed identifier.
/// </summary>
/// <param name="userAuthorityIdParsed">The parsed identifier of the user authority to delete.</param>
/// <returns>A task that represents the asynchronous delete operation. The task result is <see langword="true"/> if the user authority was successfully deleted; otherwise, <see langword="false"/>.</returns>
/// <!-- aidoc:v1 sig=d8ae827 -->
Task<bool> DeleteUserAuthority(ObjectId userAuthorityIdParsed);
/// <summary>
/// Edits the user authority using the provided authorization data.
/// </summary>
/// <param name="authorization">The authorization object containing the user authority details to be updated.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the updated <see cref="Authorization"/>, or <c>null</c> if the authorization was not found.</returns>
/// <!-- aidoc:v1 sig=857d49a -->
Task<Authorization?> EditUserAuthority(Authorization authorization);
}
@@ -15,12 +15,14 @@ public interface ILoginService
/// <param name="username">The username of the user attempting to log in.</param>
/// <param name="password">The password associated with the username.</param>
/// <returns>A <see cref="Task{User}"/> representing the asynchronous operation, containing the authenticated <see cref="User"/>.</returns>
/// <!-- aidoc:v1 sig=a4700d5 -->
Task<User> Login(string username, string password);
/// <summary>
/// Authenticates a user based on the provided HTTP context.
/// </summary>
/// <param name="context">The HTTP context containing the request information used to perform the login.</param>
/// <returns>A task that represents the asynchronous login operation. The task result contains the authenticated <see cref="User"/>.</returns>
/// <!-- aidoc:v1 sig=ff09cb3 -->
Task<User> Login(HttpContext context);
/// <summary>
@@ -29,29 +31,34 @@ public interface ILoginService
/// <param name="username">The username of the user to authenticate.</param>
/// <param name="password">The password of the user to authenticate.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the authenticated <see cref="User"/>.</returns>
/// <!-- aidoc:v1 sig=a232f05 -->
Task<User> Authenticate(string username, string password);
/// <summary>
/// Retrieves a user by their unique identifier, returning null if no matching user is found.
/// </summary>
/// <param name="id">The unique identifier of the user to look up.</param>
/// <returns>The user with the specified identifier, or null if no user is found.</returns>
/// <!-- aidoc:v1 sig=ab4b64e -->
Task<User?> GetById(ObjectId id);
/// <summary>
/// Retrieves a user from the data store by their email address, or returns null if no matching user is found.
/// </summary>
/// <param name="email">The email address used to look up the user.</param>
/// <returns>A task that resolves to the <see cref="User"/> matching the provided email, or null if no user is found.</returns>
/// <!-- aidoc:v1 sig=aad49e4 -->
Task<User?> GetByEmail(string email);
/// <summary>
/// Retrieves a user by their username asynchronously, returning <c>null</c> if no matching user is found.
/// </summary>
/// <param name="username">The username to look up.</param>
/// <returns>A <see cref="Task{User}"/> that resolves to the matching <see cref="User"/>, or <c>null</c> if no user exists with the specified username.</returns>
/// <!-- aidoc:v1 sig=d91b767 -->
Task<User?> GetByUsername(string username);
/// <summary>
/// Asynchronously retrieves a list of all users in the system.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing a list of all <see cref="User"/> entities.</returns>
/// <!-- aidoc:v1 sig=850c090 -->
Task<List<User>> GetAllUsers();
}
@@ -13,12 +13,14 @@ public interface ITokenService
/// <param name="username">The username used to look up the security token.</param>
/// <param name="claims">The list of claims associated with the token.</param>
/// <returns>A <see cref="SecurityToken"/> if a matching token is found; otherwise, <see langword="null"/>.</returns>
/// <!-- aidoc:v1 sig=78fe633 -->
SecurityToken? GetToken(string username, List<Claim> claims);
/// <summary>
/// Retrieves the current refresh token used to obtain new access tokens for authentication.
/// </summary>
/// <returns>The refresh token as a string.</returns>
/// <!-- aidoc:v1 sig=575d89a -->
string GetRefreshToken();
/// <summary>
@@ -26,5 +28,6 @@ public interface ITokenService
/// </summary>
/// <param name="token">The security token to serialize.</param>
/// <returns>A string containing the serialized form of the security token.</returns>
/// <!-- aidoc:v1 sig=0e94bd2 -->
string Serialize(SecurityToken token);
}
@@ -20,6 +20,7 @@ public interface IUserService
/// <param name="username">The username of the user attempting to log in.</param>
/// <param name="password">The password associated with the specified username.</param>
/// <returns>A task that represents the asynchronous login operation, containing the <see cref="TokenResult"/> with the authentication token information.</returns>
/// <!-- aidoc:v1 sig=d557171 -->
Task<TokenResult> Login(string username, string password);
/// <summary>
/// Authenticates a user based on the provided username and password and returns the corresponding user.
@@ -27,12 +28,14 @@ public interface IUserService
/// <param name="username">The username of the user to authenticate.</param>
/// <param name="password">The password of the user to authenticate.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the authenticated <see cref="User"/>.</returns>
/// <!-- aidoc:v1 sig=b9ab570 -->
Task<User> GetUser(string username, string password);
/// <summary>
/// Refreshes an authentication token using the provided refresh token and returns the resulting token information.
/// </summary>
/// <param name="refreshToken">The refresh token used to obtain a new access token.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation, containing the <see cref="TokenResult"/> with the refreshed token details.</returns>
/// <!-- aidoc:v1 sig=c0187cf -->
Task<TokenResult> RefreshToken(string refreshToken);
/// <summary>
@@ -40,6 +43,7 @@ public interface IUserService
/// </summary>
/// <param name="token">The access token used to authenticate the user.</param>
/// <returns>A task that represents the asynchronous login operation, containing the <see cref="TokenResult"/> with the outcome of the authentication.</returns>
/// <!-- aidoc:v1 sig=b806672 -->
Task<TokenResult> LoginWithAccessToken(string token);
/// <summary>
/// Validates the specified token according to its type and outputs the corresponding <see cref="SecurityToken"/>.
@@ -47,6 +51,7 @@ public interface IUserService
/// <param name="token">The token string to be validated.</param>
/// <param name="tokenType">The type of the token, used to determine the appropriate validation strategy.</param>
/// <param name="securityToken">When the method returns, contains the validated <see cref="SecurityToken"/> if validation succeeds.</param>
/// <!-- aidoc:v1 sig=994e231 -->
void ValidateToken(string token, string tokenType, out SecurityToken securityToken);
/// <summary>
/// Retrieves the user associated with the provided JWT security token.
@@ -54,6 +59,7 @@ public interface IUserService
/// </summary>
/// <param name="jwtToken">The JWT security token used to identify the user. May be <c>null</c>.</param>
/// <returns>A task that resolves to the <see cref="User"/> associated with the token, or <c>null</c> if the token is invalid or no user matches.</returns>
/// <!-- aidoc:v1 sig=4e2d176 -->
Task<User?> GetUserByToken(JwtSecurityToken? jwtToken);
/// <summary>
/// Asynchronously retrieves a user by validating a CAS (Central Authentication Service) ticket against the specified service.
@@ -61,41 +67,48 @@ public interface IUserService
/// <param name="service">The service URL or identifier that the ticket was issued for and must be validated against.</param>
/// <param name="ticket">The CAS ticket string used to authenticate and identify the user.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the associated <see cref="User"/> if the ticket is valid, or <c>null</c> if the user cannot be found.</returns>
/// <!-- aidoc:v1 sig=35a0f18 -->
Task<User?> GetUserByCasTicket(string service, string ticket);
/// <summary>
/// Asynchronously generates a JSON Web Token (JWT) for the specified user and returns the token result.
/// </summary>
/// <param name="user">The user for whom the JWT is being generated.</param>
/// <returns>A task that represents the asynchronous operation, containing the <see cref="TokenResult"/> with the generated token details.</returns>
/// <!-- aidoc:v1 sig=87e065c -->
Task<TokenResult> GenerateJwt(User user);
/// <summary>
/// Asynchronously retrieves all users from the data store.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains a list of all <see cref="User"/> entities.</returns>
/// <!-- aidoc:v1 sig=52f36eb -->
Task<List<User>> GetAll();
/// <summary>
/// Asynchronously retrieves a user by their unique identifier, returning <c>null</c> when no user matches the provided id.
/// </summary>
/// <param name="id">The unique <see cref="ObjectId"/> identifier of the user to look up.</param>
/// <returns>A task that resolves to the matching <see cref="User"/>, or <c>null</c> if no user is found for the given id.</returns>
/// <!-- aidoc:v1 sig=9fe25b3 -->
Task<User?> GetUserById(ObjectId id);
/// <summary>
/// Asynchronously retrieves a user by their unique username, returning null when no matching user is found.
/// </summary>
/// <param name="name">The username used to look up the user.</param>
/// <returns>A task that represents the asynchronous operation, containing the matching user if found; otherwise, null.</returns>
/// <!-- aidoc:v1 sig=917e8bd -->
Task<User?> GetUserByUserName(string name);
/// <summary>
/// Asynchronously retrieves a user matching the specified name, returning <c>null</c> when no matching user is found.
/// </summary>
/// <param name="name">The name used to look up the user.</param>
/// <returns>A task that resolves to the matching <see cref="User"/>, or <c>null</c> if no user with the given name exists.</returns>
/// <!-- aidoc:v1 sig=ea58eab -->
Task<User?> GetUserByName(string name);
/// <summary>
/// Creates a user from the provided LDAP user entry.
/// </summary>
/// <param name="userEntryLdap">The LDAP user entry used to create the user.</param>
/// <returns>A task that represents the asynchronous create operation, containing the created <see cref="User"/> or <c>null</c>.</returns>
/// <!-- aidoc:v1 sig=aefe667 -->
Task<User?> CreateUser(User userEntryLdap);
/// <summary>
/// Creates a new user based on the provided user data, typically originating from an incoming request.
@@ -103,6 +116,7 @@ public interface IUserService
/// </summary>
/// <param name="user">The user data to use for creating the new user.</param>
/// <returns>A task that represents the asynchronous operation. The result is the created <see cref="User"/>, or <see langword="null"/> if creation failed.</returns>
/// <!-- aidoc:v1 sig=658bda4 -->
Task<User?> CreateNewUserByRequest(User user);
/// <summary>
@@ -110,6 +124,7 @@ public interface IUserService
/// </summary>
/// <param name="createUserWithAuthDto">The data transfer object containing the information required to create the user and its authorities.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the created <see cref="User"/> instance, or <c>null</c> if no user was created.</returns>
/// <!-- aidoc:v1 sig=f76bd48 -->
Task<User?> CreateNewUserWithAuthorities(CreateUserWithAuthDto createUserWithAuthDto);
/// <summary>
/// Updates an existing user along with the associated authorities, optionally updating the password.
@@ -117,6 +132,7 @@ public interface IUserService
/// <param name="createUserWithAuthDto">The data transfer object containing the user information and authorities to update.</param>
/// <param name="updatePass">A flag indicating whether the user's password should be updated as part of the operation.</param>
/// <returns>A task that represents the asynchronous operation, containing the updated <see cref="User"/> or <c>null</c> if the user was not found.</returns>
/// <!-- aidoc:v1 sig=a4bc265 -->
Task<User?> UpdateUserWithAuthorities(UpdateUserWithAuthDto createUserWithAuthDto, bool updatePass);
/// <summary>
/// Updates a user's information based on the provided user data, with an option to include password updates.
@@ -124,6 +140,7 @@ public interface IUserService
/// <param name="user">The user entity containing the updated information to be persisted.</param>
/// <param name="updatePass">A flag indicating whether the user's password should be updated during this operation.</param>
/// <returns>A task that returns the updated <see cref="User"/>, or null if the user could not be found.</returns>
/// <!-- aidoc:v1 sig=4c6a1ca -->
Task<User?> UpdateUsersByRequest(User user, bool updatePass);
/// <summary>
/// Updates the password for the user identified by the specified identifier, verifying the old password before applying the new one.
@@ -132,41 +149,48 @@ public interface IUserService
/// <param name="oldPassword">The user's current password, used to verify the request.</param>
/// <param name="newPassword">The new password to set for the user.</param>
/// <returns>A task that represents the asynchronous operation. The task result is <c>true</c> if the password was updated successfully; otherwise, <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=38635ef -->
Task<bool> UpdateUserPassword(ObjectId id, string oldPassword, string newPassword);
/// <summary>
/// Asynchronously deletes a user identified by the specified identifier.
/// </summary>
/// <param name="id">The unique identifier of the user to delete.</param>
/// <returns>A task that represents the asynchronous operation, containing a value indicating whether the user was successfully deleted.</returns>
/// <!-- aidoc:v1 sig=8d34422 -->
Task<bool> DeleteUser(ObjectId id);
/// <summary>
/// Retrieves a paginated list of users based on the provided pagination filter configuration.
/// </summary>
/// <param name="config">The pagination filter that defines paging parameters such as page number and page size.</param>
/// <returns>A task that represents the asynchronous operation, containing the paginated response of <see cref="User"/> entries.</returns>
/// <!-- aidoc:v1 sig=e324e87 -->
Task<PaginationResponse<User>> GetPaginatedUsers(PaginationFilter config);
/// <summary>
/// Asynchronously creates a new authority based on the provided authorization data.
/// </summary>
/// <param name="auth">The authorization information used to create the new authority.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the created <see cref="Authorization"/>.</returns>
/// <!-- aidoc:v1 sig=d636b5c -->
Task<Authorization> CreateNewAuthority(Authorization auth);
/// <summary>
/// Asynchronously deletes an authority identified by the specified identifier.
/// </summary>
/// <param name="id">The unique identifier of the authority to delete.</param>
/// <returns>A task that represents the asynchronous delete operation. The task result contains a boolean indicating whether the authority was successfully deleted.</returns>
/// <!-- aidoc:v1 sig=e85ede9 -->
Task<bool> DeleteAuthority(string id);
/// <summary>
/// Updates the authority information based on the provided authorization data.
/// </summary>
/// <param name="authorization">The authorization entity containing the authority details to be updated.</param>
/// <returns>A task that represents the asynchronous operation. The task result is <c>true</c> if the update was successful; otherwise, <c>false</c>.</returns>
/// <!-- aidoc:v1 sig=0295989 -->
Task<bool> UpdateAuthority(Authorization authorization);
/// <summary>
/// Authenticates the user using the provided access token and returns the resulting token information.
/// </summary>
/// <param name="token">The access token used to perform the login.</param>
/// <returns>A task that represents the asynchronous login operation, containing the token result.</returns>
/// <!-- aidoc:v1 sig=485f1d7 -->
Task<TokenResult> LoginWithGivenAccessToken(string token);
}
@@ -3,6 +3,7 @@
/// <summary>
/// Represents a container for login-related information.
/// </summary>
/// <!-- aidoc:v1 sig=3e9909a -->
public class LoginInfo
{
public string Username { get; set; } = null!;
+2
View File
@@ -6,6 +6,7 @@ namespace adas_core.Authentication.Models;
/// <summary>
/// Represents the result of a token-related operation, encapsulating the outcome and any associated token data.
/// </summary>
/// <!-- aidoc:v1 sig=3a6d234 -->
public class TokenResult
{
public string AccessToken { get; set; } = null!;
@@ -18,6 +19,7 @@ public class TokenResult
/// <summary>
/// Represents a panel UI component responsible for displaying token-related results.
/// </summary>
/// <!-- aidoc:v1 sig=2bd900f -->
public class TokenResultPanel
{
public TokenResult TokenResult { get; set; } = null!;
@@ -4,6 +4,7 @@
/// Represents a generic response in the Universal Chess Interface (UCI) protocol, encapsulating a payload of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the response payload.</typeparam>
/// <!-- aidoc:v1 sig=edd025b -->
public class UciResponse<T>
{
/// <summary>
@@ -40,6 +41,7 @@ public class UciResponse<T>
/// </summary>
/// <param name="entity">The entity to include in the response payload.</param>
/// <returns>A <see cref="UciResponse{T}"/> containing the provided entity.</returns>
/// <!-- aidoc:v1 sig=193e85b body=f1d7691 -->
public static UciResponse<T> FromSuccess(T entity)
{
return new UciResponse<T>(entity);
@@ -51,6 +53,7 @@ public class UciResponse<T>
/// </summary>
/// <param name="ex">The exception whose type name and message are used to populate the error response.</param>
/// <returns>A <see cref="UciResponse{T}"/> containing the cleaned exception type name and the exception message as the error details.</returns>
/// <!-- aidoc:v1 sig=f4afb5b body=80d56b7 -->
public static UciResponse<T> FromError(Exception ex)
{
var error = ex.GetType().Name;
@@ -65,6 +68,7 @@ public class UciResponse<T>
/// <param name="error">The error code or identifier describing the failure.</param>
/// <param name="message">An optional human-readable message providing additional context about the error.</param>
/// <returns>A <see cref="UciResponse{T}"/> with <c>Success</c> set to <c>false</c>, the specified <paramref name="error"/>, and the optional <paramref name="message"/>.</returns>
/// <!-- aidoc:v1 sig=43834ba body=b488e43 -->
public static UciResponse<T> FromError(string error, string? message = null)
{
return new UciResponse<T>
@@ -9,12 +9,14 @@ namespace adas_core.Authentication.RegistrationExtensions;
/// <remarks>
/// This static class serves as a container for registration-related authentication operations.
/// </remarks>
/// <!-- aidoc:v1 sig=adfaf21 -->
public static class AuthRegistration
{
/// <summary>
/// Registers authentication-related services in the dependency injection container, including a singleton <see cref="IUserService"/> and a lazy wrapper for deferred resolution.
/// </summary>
/// <param name="serviceCollection">The service collection to which the authentication services are added.</param>
/// <!-- aidoc:v1 sig=01f4b10 body=0bc4709 -->
public static void AddAuth(this IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IUserService, UserService>();
+34
View File
@@ -31,6 +31,7 @@ namespace adas_core.Authentication;
/// <remarks>
/// This class is the default service component responsible for handling user domain logic and delegating persistence or infrastructure concerns as required by the interface.
/// </remarks>
/// <!-- aidoc:v1 sig=ea2e8b0 -->
public class UserService : IUserService
{
private readonly ILocalAuditService _auditService;
@@ -109,6 +110,7 @@ public class UserService : IUserService
/// <returns>A <see cref="Task{TResult}"/> containing the generated <see cref="TokenResult"/> for the authenticated user.</returns>
/// <exception cref="UnauthorizedException">Thrown when no user matches the provided credentials, or when the user is not in the whitelist or valid groups.</exception>
/// <exception cref="ForbbidenException">Thrown when the user account is disabled or currently locked.</exception>
/// <!-- aidoc:v1 sig=1e8af8c body=0dd331a -->
public async Task<TokenResult> Login(string username, string password)
{
var user = await GetUser(username, password);
@@ -134,6 +136,7 @@ public class UserService : IUserService
/// <param name="tokenType">The type of the token being validated.</param>
/// <param name="validatedToken">When the method returns, contains the validated <see cref="SecurityToken"/> if validation succeeds.</param>
/// <exception cref="TokenException">Thrown when the token is invalid, is not signed with the HmacSha256 algorithm, or any other validation error occurs.</exception>
/// <!-- aidoc:v1 sig=cd90202 body=4ddd4b1 -->
public void ValidateToken(string token, string tokenType, out SecurityToken validatedToken)
{
try
@@ -176,6 +179,8 @@ public class UserService : IUserService
/// <param name="jwtToken">The <see cref="JwtSecurityToken"/> containing the user identity claims, or <see langword="null"/> to indicate no token was supplied.</param>
/// <returns>A <see cref="Task{User}"/> that resolves to the matching <see cref="User"/> with its password cleared and authorization loaded if required, or <see langword="null"/> when the token is missing or no user matches the extracted username.</returns>
/// <!-- aidoc:v1 sig=29f6fec body=a7970c3 -->
/// <!-- aidoc-review:v1 severity=low kind=wrong_returns
/// "The <see cref=\"Task{User}\"/> reference indicates a non-nullable Task<User>, but the method actually returns Task<User?> with a nullable User." -->
public async Task<User?> GetUserByToken(JwtSecurityToken? jwtToken)
{
User? user = null;
@@ -204,6 +209,8 @@ public class UserService : IUserService
/// <exception cref="LoginServicesException">Thrown when the CAS login method is not available, or when an unexpected error occurs during authentication.</exception>
/// <exception cref="UnauthorizedAccessException">Thrown when the authenticated user is not in the users whitelist and does not belong to any valid group.</exception>
/// <exception cref="BusinessException">Rethrown when a business-level error occurs during the login process.</exception>
/// <!-- aidoc-review:v1 severity=high kind=stale_summary
/// "Summary states the method 'authenticates a user using a CAS ticket', but the ticket parameter is never used in the active code path - authentication is performed via service.Login(_httpContextAccessor.HttpContext). The <returns> clause also references ticket validity ('if the ticket is valid'), which is not checked." -->
public async Task<User?> GetUserByCasTicket(string serviceUrl, string ticket)
{
var service = _availableLoginServices.FirstOrDefault(s => s.Method == UserEnum.LoginMethod.Cas);
@@ -250,6 +257,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="refreshToken">The refresh token used to generate a new JWT.</param>
/// <returns>A TokenResult containing the new JWT, or an empty result if the user associated with the token is not found.</returns>
/// <!-- aidoc:v1 sig=1902a2f body=bb5bba0 -->
public async Task<TokenResult> RefreshToken(string refreshToken)
{
ValidateToken(refreshToken, IUserService.TokenTypeRefresh, out var validatedToken);
@@ -269,6 +277,7 @@ public class UserService : IUserService
/// <param name="password">The password to authenticate against.</param>
/// <returns>The authenticated <see cref="User"/> returned by the first successful login service.</returns>
/// <exception cref="LoginServicesNotFoundException">Thrown when no configured login service successfully authenticates the user.</exception>
/// <!-- aidoc:v1 sig=b90d52e body=0ab820c -->
public async Task<User> GetUser(string username, string password)
{
//BusinessException? loginException = null;
@@ -297,6 +306,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="id">The unique identifier of the user to retrieve.</param>
/// <returns>A <see cref="Task{User}"/> containing the user with its authorization populated, or <c>null</c> if the user cannot be found or an exception is thrown during retrieval.</returns>
/// <!-- aidoc:v1 sig=6af9d13 body=5ffaaf2 -->
public async Task<User?> GetUserById(ObjectId id)
{
try
@@ -316,6 +326,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="name">The username to look up.</param>
/// <returns>The matching <see cref="User"/> with its <c>Authorization</c> property populated from the authority service, or <c>null</c> if no user is found.</returns>
/// <!-- aidoc:v1 sig=f6a5557 body=812fa43 -->
public async Task<User?> GetUserByUserName(string name)
{
var u = await _userRepository.GetByUserName(name);
@@ -330,6 +341,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="name">The name of the user to look up.</param>
/// <returns>A <see cref="User"/> instance with its <c>Authorization</c> populated when found; otherwise, <c>null</c>.</returns>
/// <!-- aidoc:v1 sig=5fce94d body=3ae1c25 -->
public async Task<User?> GetUserByName(string name)
{
try
@@ -349,6 +361,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="userEntryLdap">The user information sourced from LDAP to be created in the system.</param>
/// <returns>The created user retrieved by username, or null if the user cannot be found after insertion.</returns>
/// <!-- aidoc:v1 sig=191573e body=81f390b -->
public async Task<User?> CreateUser(User userEntryLdap)
{
CheckIfUserNameExists(userEntryLdap.UserName);
@@ -366,6 +379,7 @@ public class UserService : IUserService
/// <returns>The created user retrieved by username after insertion.</returns>
/// <exception cref="UnprocessableEntityException">Thrown when a local user's password is not considered strong.</exception>
/// <exception cref="NotFoundException">Thrown when the user cannot be retrieved by username after insertion.</exception>
/// <!-- aidoc:v1 sig=82465f8 body=d4f7721 -->
public async Task<User?> CreateNewUserByRequest(User user)
{
CheckIfEmailExists(user.Email);
@@ -391,6 +405,7 @@ public class UserService : IUserService
/// <param name="createUserWithAuthDto">The data transfer object containing the user details and the list of authorizations to associate with the new user.</param>
/// <returns>The newly created user, or <c>null</c> if creation fails (in which case an exception is thrown instead).</returns>
/// <exception cref="NotFoundException">Thrown when the user cannot be created by the underlying request, indicating a missing resource.</exception>
/// <!-- aidoc:v1 sig=b28ec32 body=6f231bc -->
public async Task<User?> CreateNewUserWithAuthorities(CreateUserWithAuthDto createUserWithAuthDto)
{
var user = createUserWithAuthDto.User;
@@ -413,6 +428,7 @@ public class UserService : IUserService
/// <param name="updatePass">Indicates whether the user's password should be updated and re-hashed.</param>
/// <returns>The updated user returned by the repository, or <c>null</c> if the user could not be found or updated.</returns>
/// <exception cref="UnprocessableEntityException">Thrown when <paramref name="updatePass"/> is <c>true</c> and the provided password does not meet the strong password policy.</exception>
/// <!-- aidoc:v1 sig=4a0b955 body=0eb37fa -->
public async Task<User?> UpdateUsersByRequest(User user, bool updatePass)
{
var oldUser = await _userRepository.GetByUserName(user.UserName);
@@ -443,6 +459,8 @@ public class UserService : IUserService
/// <param name="elementUpdated">The element that has been updated and should be transmitted to the subscribers.</param>
/// <param name="userName">The user name used to filter the subscribers that will receive the broadcast.</param>
/// <param name="operation">The type of operation (e.g., create, update, delete) associated with the broadcast.</param>
/// <!-- aidoc-review:v1 severity=medium kind=wrong_summary
/// "Summary says the broadcast notifies subscribers of 'an update operation', but the code passes through any OperationType (which the param description itself lists as create/update/delete), not just update operations." -->
private async Task SendBroadcastUser(object elementUpdated, string userName, OperationType operation)
{
var subscribers = _subscribersService.GetSubscribers().Where(s =>
@@ -459,6 +477,7 @@ public class UserService : IUserService
/// <param name="elementUpdated">The updated element to send as the notification payload to non-admin subscribers.</param>
/// <param name="userName">The username whose subscribers will receive the broadcast.</param>
/// <param name="operation">The operation type that describes the nature of the update.</param>
/// <!-- aidoc:v1 sig=4f775b6 body=1033854 -->
private async Task SendBroadcastPermissions(object elementUpdated, string userName, OperationType operation)
{
var subscribers = _subscribersService.GetSubscribers().Where(s =>
@@ -491,6 +510,7 @@ public class UserService : IUserService
/// <param name="updatePass">Flag indicating whether the user's password should be updated as part of the operation.</param>
/// <returns>The updated <see cref="User"/>.</returns>
/// <exception cref="ConflictException">Thrown when the underlying user update returns no result.</exception>
/// <!-- aidoc:v1 sig=99d7acb body=7cdfa30 -->
public async Task<User?> UpdateUserWithAuthorities(UpdateUserWithAuthDto updateUserWithAuthDto, bool updatePass)
{
var u = updateUserWithAuthDto.User;
@@ -525,6 +545,7 @@ public class UserService : IUserService
/// <exception cref="BadRequestException">Thrown when <paramref name="newPassword"/> is equal to <paramref name="oldPassword"/>.</exception>
/// <exception cref="NotFoundException">Thrown when no user exists for the supplied <paramref name="id"/>.</exception>
/// <exception cref="UnprocessableEntityException">Thrown when <paramref name="oldPassword"/> does not match the user's current password, or when <paramref name="newPassword"/> does not satisfy the strength policy.</exception>
/// <!-- aidoc:v1 sig=0243044 body=c52695d -->
public async Task<bool> UpdateUserPassword(ObjectId id, string oldPassword, string newPassword)
{
if (oldPassword == newPassword)
@@ -561,6 +582,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="id">The identifier of the user to delete.</param>
/// <returns>A task that resolves to <c>true</c> when the user is successfully deleted; otherwise, <c>false</c> if an error is encountered.</returns>
/// <!-- aidoc:v1 sig=72679b2 body=c3e6d4f -->
public async Task<bool> DeleteUser(ObjectId id)
{
try
@@ -582,6 +604,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="filter">The pagination filter containing the page number and page size used to skip, limit, and shape the result set.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a <see cref="PaginationResponse{User}"/> with the requested page of users and pagination details.</returns>
/// <!-- aidoc:v1 sig=0367293 body=27c2222 -->
public async Task<PaginationResponse<User>> GetPaginatedUsers(PaginationFilter filter)
{
var result = _userRepository.GetPaginatedUsers(filter);
@@ -603,6 +626,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="user">The user whose identity, email, IP address, and authorization claims will be embedded in the generated tokens.</param>
/// <returns>A <see cref="TokenResult"/> containing the serialized access and refresh tokens, their respective expiration times, and the associated user.</returns>
/// <!-- aidoc:v1 sig=67521e7 body=d87cd16 -->
public async Task<TokenResult> GenerateJwt(User user)
{
var claims = new List<Claim>
@@ -650,6 +674,7 @@ public class UserService : IUserService
/// <returns>A task that resolves to the list of <see cref="User"/> objects returned by the first successful login service.</returns>
/// <exception cref="LoginServicesException">Thrown when a non-business exception occurs while retrieving users from a service.</exception>
/// <exception cref="LoginServicesNotFoundException">Thrown when no login service matches the valid login methods or all attempts fail without a captured business exception.</exception>
/// <!-- aidoc:v1 sig=b9380e5 body=b90883c -->
public async Task<List<User>> GetAll()
{
BusinessException? loginException = null;
@@ -688,6 +713,7 @@ public class UserService : IUserService
/// <param name="token">The access token to validate and use for user authentication.</param>
/// <returns>A <see cref="TokenResult"/> containing the new access token, refresh token, their expiry times, and the authenticated user.</returns>
/// <exception cref="NotFoundException">Thrown when no user can be resolved from the provided token.</exception>
/// <!-- aidoc:v1 sig=4a410ea body=f486923 -->
public async Task<TokenResult> LoginWithGivenAccessToken(string token)
{
ValidateToken(token, IUserService.TokenTypeUser, out var validatedToken);
@@ -742,6 +768,7 @@ public class UserService : IUserService
/// <param name="token">The access token used to identify and authenticate the user.</param>
/// <returns>A <see cref="Task{TokenResult}"/> containing the generated JWT for the authenticated user.</returns>
/// <exception cref="NotFoundException">Thrown when no user is found that matches the validated access token.</exception>
/// <!-- aidoc:v1 sig=58a9e83 body=98c26eb -->
public async Task<TokenResult> LoginWithAccessToken(string token)
{
ValidateToken(token, IUserService.TokenTypeUser, out var validatedToken);
@@ -759,6 +786,7 @@ public class UserService : IUserService
/// <param name="auth">The authorization used to create the new user authority.</param>
/// <returns>The newly created <see cref="Authorization"/> instance.</returns>
/// <exception cref="ConflictException">Thrown when the authority service fails to create the user authority (returns null).</exception>
/// <!-- aidoc:v1 sig=b73cf48 body=71c9c28 -->
public async Task<Authorization> CreateNewAuthority(Authorization auth)
{
var newUserAuthority = await _authorityService.CreateNewUserAuthority(auth) ??
@@ -773,6 +801,7 @@ public class UserService : IUserService
/// <returns>A task that resolves to <c>true</c> when the authority is successfully deleted.</returns>
/// <exception cref="BadRequestException">Thrown when the provided <paramref name="id"/> is not a valid ObjectId format.</exception>
/// <exception cref="ConflictException">Thrown when the underlying delete operation fails.</exception>
/// <!-- aidoc:v1 sig=eaa5d92 body=8a73373 -->
public async Task<bool> DeleteAuthority(string id)
{
if (!ObjectId.TryParse(id, out var userAuthorityIdParsed))
@@ -788,6 +817,7 @@ public class UserService : IUserService
/// <param name="authorization">The authorization details used to update the user's authority.</param>
/// <returns>A task that resolves to <c>true</c> when the authority is successfully updated.</returns>
/// <exception cref="ConflictException">Thrown when the authority service returns a null response, indicating the update failed.</exception>
/// <!-- aidoc:v1 sig=544e59f body=c079022 -->
public async Task<bool> UpdateAuthority(Authorization authorization)
{
_ = await _authorityService.EditUserAuthority(authorization) ??
@@ -801,6 +831,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="authorities">The list of authorizations to convert into claims.</param>
/// <returns>A task that represents the asynchronous operation, containing the list of claims built from the supplied authorities.</returns>
/// <!-- aidoc:v1 sig=527b34e body=d91d2bf -->
public async Task<List<Claim>> ClaimsFromAuthorities(List<Authorization> authorities)
{
var claims = new List<Claim>();
@@ -828,6 +859,7 @@ public class UserService : IUserService
/// </summary>
/// <param name="auth">The authorization source whose role and display identifier are used to build the claim value.</param>
/// <param name="claims">The list of claims to which the new role claim is appended when not already present.</param>
/// <!-- aidoc:v1 sig=85111ea body=f681ceb -->
private static void AddClaim(Authorization auth, List<Claim> claims)
{
var newClaim = new Claim(ClaimTypes.Role,
@@ -844,6 +876,7 @@ public class UserService : IUserService
/// <param name="userName">The username to validate against existing users.</param>
/// <param name="userId">Optional identifier of the user being updated; when provided, that user is excluded from the duplicate check.</param>
/// <exception cref="BadRequestException">Thrown when another user with the same <paramref name="userName"/> already exists.</exception>
/// <!-- aidoc:v1 sig=cd63db7 body=c9563e3 -->
private void CheckIfUserNameExists(string userName, ObjectId? userId = null)
{
var users = GetAll().Result;
@@ -860,6 +893,7 @@ public class UserService : IUserService
/// <param name="email">The email address to verify for uniqueness in the user collection.</param>
/// <param name="userId">The optional ID of the current user; when set, it is excluded from the duplicate email check.</param>
/// <exception cref="BadRequestException">Thrown when the email is already registered to a different user.</exception>
/// <!-- aidoc:v1 sig=074e36e body=4e4b89e -->
private void CheckIfEmailExists(string email, ObjectId? userId = null)
{
var users = GetAll().Result;