359 lines
15 KiB
C#
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.Status431RequestHeaderFieldsTooLarge, 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.Status431RequestHeaderFieldsTooLarge, 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;
|
|
}
|
|
}
|
|
}
|
|
} |