OnlineSalesAutoCrop/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationAuthController.cs

359 lines
15 KiB
C#

using System;
using System.DirectoryServices;
using System.IdentityModel.Tokens.Jwt;
using System.Runtime.Versioning;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
namespace OnlineSalesAutoCrop.CoreAPI.Controllers
{
/// <summary>
///
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="service"></param>
/// <param name="appSettings"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ValidateAntiForgeryToken]
[Route("api/v{version:apiVersion}/IntegrationAuth")]
public class IntegrationAuthController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<IntegrationAuthController> logger, IRefreshTokenService refreshTokenService) : ControllerBase
{
private readonly ILogger _logger = logger;
private readonly IEaseCache _cache = cache;
private readonly IUserService _service = service;
private readonly IRefreshTokenService _refreshTokenService = refreshTokenService;
private readonly AppSettings _appSettings = appSettings?.Value;
private readonly DateTimeOffset _options = Helper.CreateEaseCacheOptions();
[HttpGet]
[Route("GetServerDateTime")]
[AllowAnonymous]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> GetServerDateTime()
{
return Ok(DateTime.Now.ToString());
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>If login successful ValidUser: true</returns>
/// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
[HttpPost("login")]
[AllowAnonymous]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationLoginResponse))]
public async Task<IActionResult> Login([FromBody] IntegrationLoginRequest request)
{
ArgumentNullException.ThrowIfNull(request);
IntegrationLoginResponse loginReponse = new();
LoginResponse response = new();
if (string.IsNullOrEmpty(request.LoginId))
{
loginReponse.ReturnStatus = StatusCodes.Status417ExpectationFailed;
loginReponse.ReturnMessage.Add("Login ID is required.");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
if (string.IsNullOrEmpty(request.Password))
{
loginReponse.ReturnStatus = StatusCodes.Status417ExpectationFailed;
loginReponse.ReturnMessage.Add("Password is required.");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
string ipAddress = string.Empty;
try
{
#region Decrypt LoginID
string cipherSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.CipherSecretKey);
#endregion
bool checkPwd = true;
#region If AD (Active Directory) authentication is enabled do validate
if (_appSettings.ADConfig.Enabled)
{
checkPwd = false;
#pragma warning disable CA1416 // Validate platform compatibility
int adLoginStatus = GetADLoginStatus(loginId: request.LoginId, password: request.Password);
#pragma warning restore CA1416 // Validate platform compatibility
if (adLoginStatus != 1)
{
response.LoginStatus = EnumLoginStatus.Unsuccessful;
response.ReturnStatus = StatusCodes.Status403Forbidden;
if (adLoginStatus == 2)
response.ReturnMessage.Add("Active Directory User is DISABLED.");
else if (adLoginStatus == 3)
response.ReturnMessage.Add("Active Directory User's Password has EXPIRED.");
else
response.ReturnMessage.Add("Active Directory User/Password is INVALID.");
return StatusCode(StatusCodes.Status417ExpectationFailed, response);
}
}
#endregion
ipAddress = Request.HttpContext.GetIpAddress();
User user = await _service.IntegrationLoginAsync(request: request, ipAddress: ipAddress, checkPwd: checkPwd);
if (user == null || user.UserId == 0)
{
loginReponse.LoginStatus = EnumLoginStatus.Error;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.ReturnMessage.Add(checkPwd ? "Login ID/Password is invalid." : "You are not Authorized to login into the System");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
if (user.LoginStatus == EnumLoginStatus.Unsuccessful)
{
string usm = string.Empty;
if (user != null && !string.IsNullOrEmpty(user.UnsuccessfulMsg))
usm = $" ({user.UnsuccessfulMsg})";
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.ReturnMessage.Add(checkPwd ? $"Login ID/Password is invalid{usm}." : "You are not Authorized to login into the System");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
if (user.IsLocked)
{
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
if (!user.NextLoginTime.HasValue)
loginReponse.ReturnMessage.Add("You are locked, please contact Head office.");
else
loginReponse.ReturnMessage.Add($"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
if (user.Status != EnumStatus.Authorized)
{
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.ReturnMessage.Add("You are not Authorized to Login into the System, Please contact with System Administrator.");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
response.ValidUser = user.LoginStatus == EnumLoginStatus.Success;
if (!response.ValidUser)
{
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
loginReponse.ReturnMessage.Add("Unknown error.");
return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
}
response.Map(user);
response.ValidUser = true;
response.ReturnStatus = StatusCodes.Status200OK;
string pwdSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.PwdSecretKey);
string userPwd = Ease.NetCore.Utility.Global.CipherFunctions.EncryptByAES(privateKey: pwdSecretKey, publicKey: pwdSecretKey, data: request.Password);
byte[] key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey);
JwtSecurityTokenHandler tokenHandler = new();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(
[
Helper.CreateClaim("LoginId", user.LoginId),
Helper.CreateClaim("Email", user.EmailAddress),
Helper.CreateClaim("AuthKey", $"{user.AuthKey}"),
Helper.CreateClaim("HashKey", Guid.NewGuid().ToString())
]),
Expires = DateTime.UtcNow.AddHours(12),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
string userToken = tokenHandler.WriteToken(token);
GenerateRefreshTokenRequest refreshTokenRequest = new GenerateRefreshTokenRequest()
{
UserId = user.UserId,
IpAddress = ipAddress,
RawRefreshToken = string.Empty
};
var refreshToken =await _refreshTokenService.GenerateRefreshTokenByUserAsync(refreshTokenRequest);
//If token length is greater than or equal to 4096 (4KB) then return error
//because cookie can not store more than 4KB data and we are storing this token in cookie for authentication
if (userToken.Length >= 4096) //4Kb
{
loginReponse.LoginStatus = user.LoginStatus;
loginReponse.ReturnStatus = StatusCodes.Status431RequestHeaderFieldsTooLarge;
loginReponse.ReturnMessage.Add("Authentication Token is too large for cookie.");
return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, loginReponse);
}
loginReponse.ReturnStatus = response.ReturnStatus;
loginReponse.ReturnMessage = response.ReturnMessage;
loginReponse.LoginId = response.LoginId;
loginReponse.AccessToken = userToken;
loginReponse.RefreshToken = refreshToken.RefreshToken;
loginReponse.AccessTokenExpiry = refreshToken.ExpireTime;
return StatusCode(StatusCodes.Status200OK, loginReponse);
}
catch (Exception ex)
{
string msg = $"{request?.LoginId}~{ipAddress}";
_logger.LogError(exception: ex, message: msg);
loginReponse.ReturnStatus = StatusCodes.Status500InternalServerError;
loginReponse.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, loginReponse);
}
}
/// <summary>
/// Login using your credential data retrieve from SqlServer
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <returns>If login successful ValidUser: true</returns>
/// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
[HttpPost("RefreshToken")]
[AllowAnonymous]
[IgnoreAntiforgeryToken]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationLoginResponse))]
public async Task<IActionResult> GetRefreshToken([FromBody] IntegrationRefreshTokenRequest request)
{
IntegrationRrefreshTokenResponse response = new();
try
{
var userRefreshToken = await _refreshTokenService.GetUserByRefreshTokenAsync(request.RefreshToken);
if(!userRefreshToken.IsActive)
{
string msg = $"Refresh Token is not active: {request?.RefreshToken}";
_logger.LogError(message: msg);
response.ReturnStatus = StatusCodes.Status401Unauthorized;
response.ReturnMessage.Add(msg);
return StatusCode(StatusCodes.Status401Unauthorized, response);
}
byte[] key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey);
JwtSecurityTokenHandler tokenHandler = new();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(
[
Helper.CreateClaim("LoginId", userRefreshToken.LoginId),
Helper.CreateClaim("Email", userRefreshToken.EmailAddress),
Helper.CreateClaim("AuthKey", $"{userRefreshToken.AuthKey}"),
Helper.CreateClaim("HashKey", Guid.NewGuid().ToString())
]),
Expires = DateTime.UtcNow.AddHours(12),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
string userToken = tokenHandler.WriteToken(token);
string ipAddress = Request.HttpContext.GetIpAddress();
var refreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(request.RefreshToken, ipAddress);
response.AccessToken = userToken;
response.RefreshToken = refreshToken;
return StatusCode(StatusCodes.Status200OK, response);
}
catch (Exception ex)
{
string msg = $"{request?.RefreshToken}";
_logger.LogError(exception: ex, message: msg);
response.ReturnStatus = StatusCodes.Status500InternalServerError;
response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, response);
}
return Ok();
}
/// <summary>
///
/// </summary>
/// <param name="loginId"></param>
/// <param name="password"></param>
/// <returns></returns>
[SupportedOSPlatform("windows")]
private int GetADLoginStatus(string loginId, string password)
{
try
{
const string displayNameAttribute = "DisplayName";
const string samAccountNameAttribute = "SAMAccountName";
const string userAccountControlAttribute = "useraccountcontrol";
string username = (string.IsNullOrEmpty(_appSettings.ADConfig.Domain) || string.IsNullOrWhiteSpace(_appSettings.ADConfig.Domain)) ? loginId : $"{loginId}@{_appSettings.ADConfig.Domain}";
using DirectoryEntry entry = new(path: _appSettings.ADConfig.Path, username: username, password: password);
using DirectorySearcher searcher = new(searchRoot: entry);
searcher.Filter = $"({samAccountNameAttribute}={loginId})";
searcher.PropertiesToLoad.Add(value: displayNameAttribute);
searcher.PropertiesToLoad.Add(value: samAccountNameAttribute);
searcher.PropertiesToLoad.Add(value: userAccountControlAttribute);
var result = searcher.FindOne();
if (result == null)
return 0;
ResultPropertyValueCollection displayName = result.Properties[name: displayNameAttribute];
ResultPropertyValueCollection samAccountName = result.Properties[name: samAccountNameAttribute];
ResultPropertyValueCollection userAccountControl = result.Properties[name: userAccountControlAttribute];
int uacFlag = (userAccountControl != null && userAccountControl.Count > 0) ? Convert.ToInt32(userAccountControl[0]) : 0;
if ((uacFlag & 0x000002) == 0x000002) //Disabled
return 2;
else if ((uacFlag & 0x800000) == 0x800000) //Password expired
return 3;
if (displayName != null && displayName.Count > 0 && samAccountName != null && samAccountName.Count > 0)
return 1;
else
return 0;
}
catch (Exception ex)
{
_logger.LogError(ex);
return 0;
}
}
}
}