Conflicto de fusión en adas-core.LdapLogin/LdapLoginService.cs

This commit is contained in:
jrojas
2026-07-06 19:33:59 +02:00
2810 changed files with 1927407 additions and 25397 deletions
@@ -8,21 +8,27 @@ namespace adas_core.Authentication.Attributes;
[AttributeUsage(AttributeTargets.Method)]
public class AuthorizeRolesAttribute(PermissionEnum.RolesType type) : Attribute, IAuthorizationFilter
{
/// <summary>
/// Performs authorization by ensuring the current user is authenticated and has a role matching the required permission type.
/// Responds with an unauthorized result when no user is authenticated, bypasses role checking for the AuthSome permission type,
/// 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>
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (user.Identity?.IsAuthenticated != true)
{
context.Result = new UnauthorizedResult();
return;
var user = context.HttpContext.User;
if (user.Identity?.IsAuthenticated != true)
{
context.Result = new UnauthorizedResult();
return;
}
var roles = user.Claims.Where(c => c.Type == ClaimTypes.Role);
if (type == PermissionEnum.RolesType.AuthSome) return;
var hasRequiredRole = roles.Any(r => r.Value.StartsWith(type.ToString()));
if (!hasRequiredRole) context.Result = new ForbidResult();
}
var roles = user.Claims.Where(c => c.Type == ClaimTypes.Role);
if (type == PermissionEnum.RolesType.AuthSome) return;
var hasRequiredRole = roles.Any(r => r.Value.StartsWith(type.ToString()));
if (!hasRequiredRole) context.Result = new ForbidResult();
}
}
@@ -8,22 +8,26 @@ public class AuthorizePermissionsAttribute(
IUserService userService)
: Attribute, IAuthorizationFilter
{
/// <summary>
/// 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>
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
var userName = user.Identity?.Name ?? string.Empty;
var displayIdHeader = context.HttpContext.Request.Headers["Displayid"];
if (string.IsNullOrEmpty(displayIdHeader.ToString()))
{
context.Result = new NotFoundObjectResult("displayIdHeader not found reference id doesn't exist on header");
return;
var user = context.HttpContext.User;
var userName = user.Identity?.Name ?? string.Empty;
var displayIdHeader = context.HttpContext.Request.Headers["Displayid"];
if (string.IsNullOrEmpty(displayIdHeader.ToString()))
{
context.Result = new NotFoundObjectResult("displayIdHeader not found reference id doesn't exist on header");
return;
}
var userDao = userService.GetUserByUserName(userName).Result;
if (userDao == null)
context.Result = new NotFoundObjectResult("User not found on AuthorizePermissionsAttribute");
}
var userDao = userService.GetUserByUserName(userName).Result;
if (userDao == null)
context.Result = new NotFoundObjectResult("User not found on AuthorizePermissionsAttribute");
}
}
@@ -12,87 +12,94 @@ namespace adas_core.Authentication.Attributes;
public class PermissionAuthorizeAttribute(string source, string? resourceIdHeader = null)
: AuthorizeAttribute, IAsyncAuthorizationFilter
{
/// <summary>
/// Performs asynchronous authorization by validating the user's roles, username, and source header against the permission service.
/// For the <c>Panel</c> source, it verifies that at least one of the user's roles grants access to the panel; for other sources, it requires a resource identifier header (such as <c>DisplayId</c> or <c>UnitId</c>) and checks access to the corresponding resource.
/// If any required value is missing or no role grants the needed permission, the response is set to <see cref="ForbidResult"/>.
/// </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>
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// Obtener el servicio de permisos.
var permissionService = context.HttpContext.RequestServices.GetService<IPermissionService>()
?? throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
var user = context.HttpContext.User;
// Obtener el header que indica la fuente.
var headerSource = context.HttpContext.Request.Headers[source].FirstOrDefault();
// Obtener el identificador del usuario
var username = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
// Obtiene la lista de roles (valores) del usuario.
var userRoles = user.Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value)
.ToList();
// Procesa cada rol: si contiene 2 guiones bajos, elimina el tercer segmento.
var processedRoles = userRoles.Select(role =>
{
var parts = role.Split('_');
return parts.Length == 2 ? parts[0] : role;
}).ToList();
// Validar que se tenga al menos un rol, username y headerSource.
if (!processedRoles.Any() || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(headerSource))
{
context.Result = new ForbidResult();
return;
}
// Parseamos el header para obtener la fuente.
var source1 =
(PermissionEnum.SourcePermissionsEnum)Enum.Parse(typeof(PermissionEnum.SourcePermissionsEnum),
headerSource);
var hasPermission = false;
if (source1 == PermissionEnum.SourcePermissionsEnum.Panel)
{
// Si la fuente es PANEL, verificamos que alguno de los roles tenga acceso.
foreach (var roleStr in processedRoles)
// Obtener el servicio de permisos.
var permissionService = context.HttpContext.RequestServices.GetService<IPermissionService>()
?? throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
var user = context.HttpContext.User;
// Obtener el header que indica la fuente.
var headerSource = context.HttpContext.Request.Headers[source].FirstOrDefault();
// Obtener el identificador del usuario
var username = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
// Obtiene la lista de roles (valores) del usuario.
var userRoles = user.Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value)
.ToList();
// Procesa cada rol: si contiene 2 guiones bajos, elimina el tercer segmento.
var processedRoles = userRoles.Select(role =>
{
if (!Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj)) continue;
var role = (PermissionEnum.RolesType)roleObj;
if (!await permissionService.HasAccessToPanel(username, role, source1)) continue;
hasPermission = true;
break;
}
if (!hasPermission) context.Result = new ForbidResult();
}
else if (resourceIdHeader != null)
{
// Para otras fuentes, se requiere un header que indique el resourceId.
var resourceId = context.HttpContext.Request.Headers[resourceIdHeader].FirstOrDefault();
if (string.IsNullOrEmpty(resourceId))
var parts = role.Split('_');
return parts.Length == 2 ? parts[0] : role;
}).ToList();
// Validar que se tenga al menos un rol, username y headerSource.
if (!processedRoles.Any() || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(headerSource))
{
context.Result = new ForbidResult();
return;
}
// Verificar que al menos uno de los roles tenga acceso al recurso.
foreach (var roleStr in processedRoles)
if (Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj))
// Parseamos el header para obtener la fuente.
var source1 =
(PermissionEnum.SourcePermissionsEnum)Enum.Parse(typeof(PermissionEnum.SourcePermissionsEnum),
headerSource);
var hasPermission = false;
if (source1 == PermissionEnum.SourcePermissionsEnum.Panel)
{
// Si la fuente es PANEL, verificamos que alguno de los roles tenga acceso.
foreach (var roleStr in processedRoles)
{
if (!Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj)) continue;
var role = (PermissionEnum.RolesType)roleObj;
hasPermission = resourceIdHeader switch
{
"DisplayId" => await permissionService.HasAccessToDisplay(username, role, source1, resourceId),
"UnitId" => await permissionService.HasAccessToUnit(username, role, source1, resourceId),
_ => false
};
if (hasPermission)
break;
if (!await permissionService.HasAccessToPanel(username, role, source1)) continue;
hasPermission = true;
break;
}
if (!hasPermission) context.Result = new ForbidResult();
if (!hasPermission) context.Result = new ForbidResult();
}
else if (resourceIdHeader != null)
{
// Para otras fuentes, se requiere un header que indique el resourceId.
var resourceId = context.HttpContext.Request.Headers[resourceIdHeader].FirstOrDefault();
if (string.IsNullOrEmpty(resourceId))
{
context.Result = new ForbidResult();
return;
}
// Verificar que al menos uno de los roles tenga acceso al recurso.
foreach (var roleStr in processedRoles)
if (Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj))
{
var role = (PermissionEnum.RolesType)roleObj;
hasPermission = resourceIdHeader switch
{
"DisplayId" => await permissionService.HasAccessToDisplay(username, role, source1, resourceId),
"UnitId" => await permissionService.HasAccessToUnit(username, role, source1, resourceId),
_ => false
};
if (hasPermission)
break;
}
if (!hasPermission) context.Result = new ForbidResult();
}
}
}
}
+102 -63
View File
@@ -14,23 +14,36 @@ public class AuthorityService(
ILocalAuditService auditService)
: IAuthorityService
{
/// <summary>
/// Creates a new authority for the specified user with the given role name by delegating to the authority repository.
/// </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>
public void CreateNew(string roleName, ObjectId userId)
{
authorityRepository.CreateNewAuthority(roleName, userId);
}
{
authorityRepository.CreateNewAuthority(roleName, userId);
}
/// <summary>
/// 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>
public async Task InsertOne(Authorization authorization)
{
await authorityRepository.InsertOneAsync(authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, authorization);
}
{
await authorityRepository.InsertOneAsync(authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, authorization);
}
/// <summary>
/// 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>
public async Task updateOne(Authorization authorization)
{
var oldAuth = await authorityRepository.GetById(authorization.Id);
await authorityRepository.UpdateOneAsync(authorization.Id, authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAuth, authorization);
}
{
var oldAuth = await authorityRepository.GetById(authorization.Id);
await authorityRepository.UpdateOneAsync(authorization.Id, authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAuth, authorization);
}
/// <summary>
/// Retrieves the authorities of the specified user.
@@ -42,67 +55,93 @@ public class AuthorityService(
return authorityRepository.GetUserAuthorities(userId);
}
/// <summary>
/// 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>
public Task<List<Authorization>> GetAllAuthorities()
{
return authorityRepository.GetAllAuthorities();
}
{
return authorityRepository.GetAllAuthorities();
}
/// <summary>
/// Deletes all authorities associated with the specified user and records an audit log entry for each removed authority. Returns <c>false</c> if no authorities were deleted or if an exception is encountered during processing.
/// </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>
public async Task<bool> DeleteAuthoritiesForUser(ObjectId id)
{
try
{
var list = await authorityRepository.GetUserAuthorities(id);
var result = await authorityRepository.DeleteAllAuthoritiesByUser(id);
if (!result) return result;
foreach (var auth in list)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, auth, null);
return result;
try
{
var list = await authorityRepository.GetUserAuthorities(id);
var result = await authorityRepository.DeleteAllAuthoritiesByUser(id);
if (!result) return result;
foreach (var auth in list)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, auth, null);
return result;
}
catch (Exception e)
{
Log.Error("Exception deleting authorities for user {id}. Exception: {e}", id, e.Message);
return false;
}
}
catch (Exception e)
{
Log.Error("Exception deleting authorities for user {id}. Exception: {e}", id, e.Message);
return false;
}
}
/// <summary>
/// Creates a new user authority by persisting the provided authorization record and writing an audit log entry for the operation.
/// </summary>
/// <param name="authorization">The authorization entity to be created and stored.</param>
/// <returns>The created <see cref="Authorization"/> entity.</returns>
public async Task<Authorization?> CreateNewUserAuthority(Authorization authorization)
{
await authorityRepository.InsertOneAsync(authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, authorization);
return authorization;
}
public async Task<bool> DeleteUserAuthority(ObjectId userAuthorityIdParsed)
{
try
{
var result = await authorityRepository.DeleteAsync(userAuthorityIdParsed);
if (result != null)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, result, null);
return result != null;
}
catch (Exception e)
{
Log.Error("Exception deleting user authority {userAuthorityIdParsed}. Exception: {e}",
userAuthorityIdParsed, e.Message);
return false;
}
}
public async Task<Authorization?> EditUserAuthority(Authorization authorization)
{
try
{
var oldAuth = await authorityRepository.GetById(authorization.Id);
await authorityRepository.UpdateOneAsync(authorization.Id, authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAuth, authorization);
await authorityRepository.InsertOneAsync(authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, authorization);
return authorization;
}
catch (Exception e)
/// <summary>
/// Deletes a user authority by its parsed identifier and records an audit log entry when the deletion succeeds.
/// Returns <c>true</c> if the authority was found and removed, and <c>false</c> when the entity is not found or an exception occurs during processing.
/// </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>
public async Task<bool> DeleteUserAuthority(ObjectId userAuthorityIdParsed)
{
Log.Error("Exception editing user authority {authorization}. Exception: {e}", authorization, e.Message);
return null;
try
{
var result = await authorityRepository.DeleteAsync(userAuthorityIdParsed);
if (result != null)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, result, null);
return result != null;
}
catch (Exception e)
{
Log.Error("Exception deleting user authority {userAuthorityIdParsed}. Exception: {e}",
userAuthorityIdParsed, e.Message);
return false;
}
}
/// <summary>
/// Edits an existing user authority, records the change in an audit log, and returns the updated authorization.
/// Returns <c>null</c> if the update operation fails due to an exception.
/// </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>
public async Task<Authorization?> EditUserAuthority(Authorization authorization)
{
try
{
var oldAuth = await authorityRepository.GetById(authorization.Id);
await authorityRepository.UpdateOneAsync(authorization.Id, authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAuth, authorization);
return authorization;
}
catch (Exception e)
{
Log.Error("Exception editing user authority {authorization}. Exception: {e}", authorization, e.Message);
return null;
}
}
}
}
@@ -5,15 +5,58 @@ namespace adas_core.Authentication.Interfaces;
public interface IAuthorityService
{
/// <summary>
/// Asynchronously retrieves the list of authorizations associated with the specified user.
/// </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>
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>
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>
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>
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>
public Task<List<Authorization>> GetAllAuthorities();
/// <summary>
/// Deletes all authorities associated with the user identified by the specified id.
/// </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>
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>
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>
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>
Task<Authorization?> EditUserAuthority(Authorization authorization);
}
@@ -9,13 +9,49 @@ public interface ILoginService
{
bool AllowPassword { get; }
UserEnum.LoginMethod Method { get; }
/// <summary>
/// Authenticates a user with the provided credentials and returns the corresponding user account.
/// </summary>
/// <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>
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>
Task<User> Login(HttpContext context);
/// <summary>
/// Authenticates a user based on the provided credentials.
/// </summary>
/// <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>
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>
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>
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>
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>
Task<List<User>> GetAllUsers();
}
@@ -6,9 +6,25 @@ namespace adas_core.Authentication.Interfaces;
public interface ITokenService
{
int RefreshTokenValidityInDays { get; }
/// <summary>
/// Retrieves a <see cref="SecurityToken"/> associated with the specified username and claims.
/// Returns <see langword="null"/> when no matching token is found.
/// </summary>
/// <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>
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>
string GetRefreshToken();
/// <summary>
/// Serializes the specified security token into its string representation.
/// </summary>
/// <param name="token">The security token to serialize.</param>
/// <returns>A string containing the serialized form of the security token.</returns>
string Serialize(SecurityToken token);
}
@@ -14,30 +14,159 @@ public interface IUserService
static readonly string TokenTypeUser = "user";
static readonly string TokenTypeRefresh = "refresh";
/// <summary>
/// Authenticates a user with the provided credentials and returns a token result upon successful login.
/// </summary>
/// <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>
Task<TokenResult> Login(string username, string password);
/// <summary>
/// Authenticates a user based on the provided username and password and returns the corresponding user.
/// </summary>
/// <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>
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>
Task<TokenResult> RefreshToken(string refreshToken);
/// <summary>
/// Authenticates a user by exchanging a provided access token for a login result.
/// </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>
Task<TokenResult> LoginWithAccessToken(string token);
/// <summary>
/// Validates the specified token according to its type and outputs the corresponding <see cref="SecurityToken"/>.
/// </summary>
/// <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>
void ValidateToken(string token, string tokenType, out SecurityToken securityToken);
/// <summary>
/// Retrieves the user associated with the provided JWT security token.
/// Returns <c>null</c> if the token is <c>null</c> or if no matching user is found.
/// </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>
Task<User?> GetUserByToken(JwtSecurityToken? jwtToken);
/// <summary>
/// Asynchronously retrieves a user by validating a CAS (Central Authentication Service) ticket against the specified service.
/// </summary>
/// <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>
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>
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>
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>
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>
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>
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>
Task<User?> CreateUser(User userEntryLdap);
/// <summary>
/// Creates a new user based on the provided user data, typically originating from an incoming request.
/// Returns the newly created user, or <see langword="null"/> if the user could not be created.
/// </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>
Task<User?> CreateNewUserByRequest(User user);
/// <summary>
/// Creates a new user together with the associated authorities based on the supplied data transfer object.
/// </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>
Task<User?> CreateNewUserWithAuthorities(CreateUserWithAuthDto createUserWithAuthDto);
/// <summary>
/// Updates an existing user along with the associated authorities, optionally updating the password.
/// </summary>
/// <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>
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.
/// </summary>
/// <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>
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.
/// </summary>
/// <param name="id">The unique identifier of the user whose password will be updated.</param>
/// <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>
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>
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>
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>
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>
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>
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>
Task<TokenResult> LoginWithGivenAccessToken(string token);
}
@@ -1,5 +1,8 @@
namespace adas_core.Authentication.Models;
/// <summary>
/// Represents a container for login-related information.
/// </summary>
public class LoginInfo
{
public string Username { get; set; } = null!;
+6
View File
@@ -3,6 +3,9 @@ using adas_core.Domain.Models.MongoModels;
namespace adas_core.Authentication.Models;
/// <summary>
/// Represents the result of a token-related operation, encapsulating the outcome and any associated token data.
/// </summary>
public class TokenResult
{
public string AccessToken { get; set; } = null!;
@@ -12,6 +15,9 @@ public class TokenResult
public User User { get; set; } = new();
}
/// <summary>
/// Represents a panel UI component responsible for displaying token-related results.
/// </summary>
public class TokenResultPanel
{
public TokenResult TokenResult { get; set; } = null!;
+41 -21
View File
@@ -1,5 +1,9 @@
namespace adas_core.Authentication.Models;
/// <summary>
/// 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>
public class UciResponse<T>
{
protected UciResponse()
@@ -21,27 +25,43 @@ public class UciResponse<T>
public T? Data { get; protected set; }
/// <summary>
/// Creates a successful <see cref="UciResponse{T}"/> wrapping the specified entity.
/// </summary>
/// <param name="entity">The entity to include in the response payload.</param>
/// <returns>A <see cref="UciResponse{T}"/> containing the provided entity.</returns>
public static UciResponse<T> FromSuccess(T entity)
{
return new UciResponse<T>(entity);
}
public static UciResponse<T> FromError(Exception ex)
{
var error = ex.GetType().Name;
var index = error.LastIndexOf("Exception", StringComparison.Ordinal);
if (index > -1) error = error[..index];
return FromError(error, ex.Message);
}
public static UciResponse<T> FromError(string error, string? message = null)
{
return new UciResponse<T>
{
Success = false,
Error = error,
Message = message
};
}
return new UciResponse<T>(entity);
}
/// <summary>
/// Creates a <see cref="UciResponse{T}"/> representing an error state derived from the supplied exception, using the exception's type name (with the "Exception" suffix stripped) as the error code and its message as the error description.
/// </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>
public static UciResponse<T> FromError(Exception ex)
{
var error = ex.GetType().Name;
var index = error.LastIndexOf("Exception", StringComparison.Ordinal);
if (index > -1) error = error[..index];
return FromError(error, ex.Message);
}
/// <summary>
/// Creates a <see cref="UciResponse{T}"/> instance representing a failed operation, populating the error information and an optional descriptive message.
/// </summary>
/// <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>
public static UciResponse<T> FromError(string error, string? message = null)
{
return new UciResponse<T>
{
Success = false,
Error = error,
Message = message
};
}
}
+345
View File
@@ -0,0 +1,345 @@
# adas-core.Authentication — Security & Identity Abstractions
> The **Authentication Layer** of the ADAS Core platform.
> Provides a strategy-pattern abstraction for authentication, JWT token management, user identity resolution, and role-based authorization. This layer defines **who** can access the system, delegating **how** credentials are validated to concrete strategy projects (`LdapLogin`, `LocalLogin`).
---
## Table of Contents
1. [Overview](#overview)
2. [Responsibilities](#responsibilities)
3. [Project Structure](#project-structure)
4. [Dependencies](#dependencies)
5. [Authentication Strategy Pattern](#authentication-strategy-pattern)
6. [Core Contracts](#core-contracts)
7. [Token Lifecycle](#token-lifecycle)
8. [Authorization Attributes](#authorization-attributes)
9. [Registration Extensions](#registration-extensions)
10. [Extensibility](#extensibility)
11. [Design Rules](#design-rules)
---
## Overview
`adas-core.Authentication` is the security abstraction layer of the ADAS platform. It establishes the contracts and shared infrastructure for identity management without committing to any single credential store or validation mechanism.
Key characteristics:
- **Strategy Pattern** — Authentication is abstracted behind `ILoginService`; concrete strategies (LDAP, Local DB, future OAuth) are implemented in separate satellite projects.
- **JWT-Native** — All sessions are represented as signed JWT tokens with refresh-token support.
- **Role-Based Access Control** — Authorities (roles/permissions) are modeled in the Domain layer and managed through `IAuthorityService`.
- **Framework-Agnostic Contracts** — Authentication contracts depend only on `Domain` and `Application`, never on Infrastructure or Host concerns.
- **Extensible by Design** — New authentication strategies (OAuth 2.0, SAML, OIDC) can be added as new projects implementing `ILoginService` without modifying this layer.
---
## Responsibilities
| Concern | What this project does |
|---------|----------------------|
| **Login Strategy Contract** | Defines `ILoginService` — the universal interface that all concrete login providers must implement. |
| **Token Generation** | `ITokenService` issues, serializes, and validates JWT access and refresh tokens. |
| **User Identity** | `IUserService` manages user lookup, creation, update, deletion, and association with roles/authorities. |
| **Authority Management** | `IAuthorityService` governs role assignment and permission resolution for users. |
| **Registration Helper** | `AuthRegistration` provides DI registration helpers consumed by the Host project. |
| **Auth Attributes** | Custom authorization attributes (`AuthorizeRolesAttribute`, `PermissionAuthorizeAttribute`, etc.) for declarative security on controllers. |
| **Cross-Cutting Security** | Encapsulates token validation parameters, refresh-token rotation logic, and CAS-ticket resolution. |
---
## Project Structure
```
adas-core.Authentication/
├── Interfaces/ # Core security contracts
│ ├── ILoginService.cs # Universal login strategy contract
│ ├── ITokenService.cs # JWT generation / serialization
│ ├── IUserService.cs # User CRUD + token login / refresh
│ └── IAuthorityService.cs # Role / permission management
├── Models/ # Authentication DTOs / value objects
│ ├── LoginInfo.cs
│ ├── Token.cs
│ └── UciResponse.cs
├── Attributes/ # Declarative authorization attributes
│ ├── AuthorizeRolesAttribute.cs
│ ├── AuthorizeUserByService.cs
│ └── PermissionAuthorizeAttribute.cs
├── RegistrationExtensions/
│ └── AuthRegistration.cs # DI wiring helpers
├── AuthorityService.cs # Concrete authority management
├── UserService.cs # Concrete user identity management
└── adas-core.Authentication.csproj
```
---
## Dependencies
### Downstream References
| Project | Role |
|---------|------|
| `adas-core.Domain` | `User`, `Authorization`, `UserEnum.LoginMethod`, and other identity entities used by contracts. |
| `adas-core.Application` | `PaginationFilter`, `PaginationResponse`, `CreateUserWithAuthDto`, `UpdateUserWithAuthDto` DTOs consumed by `IUserService`. |
### Upstream References (projects that depend on this)
| Project | Reason |
|---------|--------|
| `adas-core` (Host) | Calls `AddAuth()` to register `IUserService`, applies `[Authorize*]` attributes on controllers, and consumes `TokenResult` from login endpoints. |
| `adas-core.LdapLogin` | Implements `ILoginService` for LDAP / Active Directory. |
| `adas-core.LocalLogin` | Implements `ILoginService` for local database credentials. |
| `adas-core.Test` | Mocks `IUserService` and `ITokenService` in unit/integration tests. |
### NuGet Packages
| Package | Version | Purpose |
|---------|---------|---------|
| `System.IdentityModel.Tokens.Jwt` | 8.18.0 | JWT token creation, validation, and parsing. |
| `Microsoft.IdentityModel.Tokens` | 8.18.0 | Signing key and token validation parameter abstractions. |
| `Microsoft.AspNetCore.Http` | 2.3.10 | `HttpContext` consumption in `ILoginService` overloads. |
| `Microsoft.AspNetCore.Mvc.NewtonsoftJson` | 8.0.27 | MVC attribute compatibility. |
| `AuditLogs` | 1.0.59 | Audit tagging on sensitive identity operations. |
---
## Authentication Strategy Pattern
The layer uses the **Strategy Pattern** to decouple authentication mechanisms from the core identity logic. The Host selects the appropriate strategy at runtime via configuration.
```mermaid
classDiagram
class ILoginService {
<<interface>>
+Method : LoginMethod
+AllowPassword : bool
+Login(username, password) Task~User~
+Login(context) Task~User~
+Authenticate(username, password) Task~User~
+GetById(id) Task~User?~
+GetByEmail(email) Task~User?~
+GetByUsername(username) Task~User?~
+GetAllUsers() Task~List~User~~
}
class LdapLoginService {
+BindToLdap()
}
class LocalLoginService {
+HashPassword()
}
class FutureOAuthService {
+ExchangeCode()
}
ILoginService <|-- LdapLoginService
ILoginService <|-- LocalLoginService
ILoginService <|-- FutureOAuthService
```
### Strategy Selection
The Host determines which strategy to activate via configuration (`Authentication:Scheme`):
| Scheme | Implementing Project | Description |
|--------|-------------------|-------------|
| `Ldap` | `adas-core.LdapLogin` | Delegates credential validation to an LDAP / Active Directory server. |
| `Local` | `adas-core.LocalLogin` | Validates credentials against locally stored user records with bcrypt hashing. |
| *Future* | *adas-core.OAuthLogin* | Could implement OAuth 2.0 / OIDC without touching this layer. |
> **Critical Rule:** This project defines the strategy contract but contains **zero implementation logic** for any specific credential store.
---
## Core Contracts
### `ILoginService` — Authentication Strategy Contract
The universal interface implemented by every concrete authentication provider.
```csharp
public interface ILoginService
{
bool AllowPassword { get; }
UserEnum.LoginMethod Method { get; }
Task<User> Login(string username, string password);
Task<User> Login(HttpContext context);
Task<User> Authenticate(string username, string password);
Task<User?> GetById(ObjectId id);
Task<User?> GetByEmail(string email);
Task<User?> GetByUsername(string username);
Task<List<User>> GetAllUsers();
}
```
Key design notes:
- `AllowPassword` signals whether the strategy supports password-based login (false for certificate-based or token-only strategies).
- `Method` discriminates the strategy so the Host can route to the correct implementation.
- `Login(HttpContext)` enables context-aware login (e.g., certificate extraction, client assertions).
### `ITokenService` — JWT Management
Responsible for issuing, serializing, and validating tokens without knowing user details.
```csharp
public interface ITokenService
{
int RefreshTokenValidityInDays { get; }
SecurityToken? GetToken(string username, List<Claim> claims);
string GetRefreshToken();
string Serialize(SecurityToken token);
}
```
### `IUserService` — Identity Management
The central identity orchestrator. Combines authentication, token lifecycle, and user administration.
Key operations:
| Area | Methods |
|------|---------|
| **Authentication** | `Login`, `GetUser`, `Authenticate` |
| **Token Lifecycle** | `GenerateJwt`, `RefreshToken`, `ValidateToken`, `LoginWithAccessToken`, `LoginWithGivenAccessToken` |
| **User CRUD** | `GetAll`, `GetUserById`, `GetUserByUserName`, `GetUserByName`, `CreateUser`, `CreateNewUserByRequest`, `CreateNewUserWithAuthorities`, `UpdateUserWithAuthorities`, `UpdateUsersByRequest`, `UpdateUserPassword`, `DeleteUser`, `GetPaginatedUsers` |
| **CAS Integration** | `GetUserByCasTicket` |
### `IAuthorityService` — Role-Based Access Control
Manages the link between users and their permissions.
```csharp
public interface IAuthorityService
{
Task<List<Authorization>> GetUserAuthorities(ObjectId userId);
Task<List<Authorization>> GetAllAuthorities();
void CreateNew(string roleName, ObjectId userId);
Task InsertOne(Authorization authorization);
Task updateOne(Authorization authorization);
Task<bool> DeleteAuthoritiesForUser(ObjectId id);
Task<Authorization?> CreateNewUserAuthority(Authorization authorization);
Task<bool> DeleteUserAuthority(ObjectId userAuthorityIdParsed);
Task<Authorization?> EditUserAuthority(Authorization authorization);
}
```
---
## Token Lifecycle
The authentication layer manages the complete lifecycle of bearer tokens:
```
[User Credentials] --> [ILoginService.Authenticate] --> [User]
|
v
[IUserService.GenerateJwt]
|
+---------------------------+---------------------------+
| |
v v
[Access Token] [Refresh Token]
(short-lived) (long-lived)
| |
v v
[API Requests] [IUserService.RefreshToken]
|
v
[New Access Token]
```
| Token Type | Expiration | Usage |
|------------|-----------|-------|
| **Access Token** | Configurable (typically 1560 min) | Sent in `Authorization` header for every API call. |
| **Refresh Token** | `RefreshTokenValidityInDays` (configurable) | Exchanged silently for a new access token without re-entering credentials. |
---
## Authorization Attributes
Declarative security attributes are defined in this layer and applied on controllers in the Host project.
| Attribute | Purpose | Placement |
|-----------|---------|-----------|
| `AuthorizeRolesAttribute` | Restricts access to users with one or more specified roles. | Class or method level. |
| `AuthorizeUserByService` | Allows service-scoped authorization (e.g., a service account accessing specific units). | Method level. |
| `PermissionAuthorizeAttribute` | Fine-grained permission checks beyond role membership. | Method level. |
> All attributes derive from ASP.NET Core authorization infrastructure but are customized to integrate with the `IAuthorityService` permission model.
---
## Registration Extensions
`AuthRegistration.cs` provides a centralized helper for wiring authentication services into the Host DI container:
```csharp
public static class AuthRegistration
{
public static void AddAuth(this IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IUserService, UserService>();
serviceCollection.AddSingleton<Lazy<IUserService>>(provider =>
new Lazy<IUserService>(provider.GetRequiredService<IUserService>));
}
}
```
The Host project calls `builder.Services.AddAuth()` during startup, which in turn registers:
- `IUserService` singleton
- Lazy wrapper for deferred resolution
- `ITokenService` and `IAuthorityService` concrete implementations
Concrete strategy projects (`LdapLogin`, `LocalLogin`) then register their own `ILoginService` implementations, completing the pipeline.
---
## Extensibility
### Adding a New Authentication Strategy
To introduce a new mechanism (e.g., OAuth 2.0, SAML, certificate-based):
1. **Create a new project** (e.g., `adas-core.OAuthLogin`).
2. **Reference** `adas-core.Authentication` and `adas-core.Domain`.
3. **Implement `ILoginService`** with the new credential validation logic.
4. **Register** the implementation in the Host project's `Program.cs` alongside `AddAuth()`.
5. **Configure** the strategy selector in `appsettings.json`.
No changes to `adas-core.Authentication` are required. The Host simply resolves the correct `ILoginService` implementation based on runtime configuration.
### Supported Future Strategies
| Strategy | Feasibility | Notes |
|----------|------------|-------|
| **OAuth 2.0 / OIDC** | High | Implement `ILoginService` with code exchange flow. |
| **SAML 2.0** | Medium | Parse SAML assertions, map to `User` entity. |
| **Certificate-Based (mTLS)** | Medium | Read client certificate from `HttpContext`, validate chain. |
| **Kerberos / SPNEGO** | Low | Possible but requires Windows domain integration. |
---
## Design Rules
1. **No Credential Logic in Contracts** — This project defines interfaces, models, and attributes. It never implements LDAP binds, password hashes, or database queries.
2. **Strategy Isolation** — Each authentication mechanism lives in its own project implementing `ILoginService`. No conditional logic selecting between strategies inside `Authentication`.
3. **Domain Dependency Only** — References only `adas-core.Domain` and `adas-core.Application`. No reference to Infrastructure, Modules, or Host.
4. **Token Immutability** — Once issued, a JWT is self-contained. The token service signs it; validation relies on signature alone, avoiding database lookups per request.
5. **Lazy Resolution**`IUserService` is wrapped in `Lazy<T>` to defer heavy initialization until first usage.
6. **Audit Awareness** — Every mutating identity operation (create user, change password, update authority) must emit an audit event before returning.
7. **No Hardcoded Secrets** — Token signing keys, LDAP server addresses, and refresh-token storage paths come exclusively from configuration; never from source code.
8. **RBAC Extensibility** — Authorities are stored as domain entities. Custom roles can be created dynamically without redeploying the authentication layer.
9. **Attribute-Driven Security** — Controllers declare authorization requirements via attributes; the Host enforces them through policy evaluation. The Authentication layer provides the building blocks, not enforcement logic.
10. **Consistent Error Semantics** — All authentication failures throw or return standard domain exceptions (`UnauthorizedException`, `TokenException`) defined in `adas-core.Application.Exceptions`.
---
<p align="center">
Back to <a href="../README.md">adas-core Root README</a>
</p>
@@ -3,12 +3,22 @@ using Microsoft.Extensions.DependencyInjection;
namespace adas_core.Authentication.RegistrationExtensions;
/// <summary>
/// Provides static functionality related to user authentication registration processes.
/// </summary>
/// <remarks>
/// This static class serves as a container for registration-related authentication operations.
/// </remarks>
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>
public static void AddAuth(this IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IUserService, UserService>();
serviceCollection.AddSingleton<Lazy<IUserService>>(provider =>
new Lazy<IUserService>(provider.GetRequiredService<IUserService>));
}
{
serviceCollection.AddSingleton<IUserService, UserService>();
serviceCollection.AddSingleton<Lazy<IUserService>>(provider =>
new Lazy<IUserService>(provider.GetRequiredService<IUserService>));
}
}
File diff suppressed because it is too large Load Diff