using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using RSecurityBackend.Models.Auth.Db;
using RSecurityBackend.Models.Auth.ViewModels;
using RSecurityBackend.Models.Generic;
using RSecurityBackend.DbContext;
using RSecurityBackend.Models.Image;
using RSecurityBackend.Models.Auth.Memory;
using RSecurityBackend.Models.Audit.Db;
using Microsoft.Extensions.Configuration;
namespace RSecurityBackend.Services.Implementation
{
///
/// Authentication Service
///
public class AppUserService : IAppUserService
{
///
/// Login user, if failed return LoggedOnUserModel is null
///
///
///
///
public virtual async Task> Login(LoginViewModel loginViewModel, string clientIPAddress)
{
try
{
//we ignore loginViewModel in automatic auditing to prevent logging password data, so we would add a manual auditing to have enough data on login intrusion and ...
REvent log = new REvent()
{
EventType = "AppUser/Login (POST)(Manual)",
StartDate = DateTime.UtcNow,
UserName = loginViewModel.Username,
IpAddress = clientIPAddress
};
_context.AuditLogs.Add(log);
await _context.SaveChangesAsync();
RServiceResult checkUserExists = await EnsureDefaultUserExists();
if(!checkUserExists.Result)
{
return new RServiceResult(null, checkUserExists.ExceptionString);
}
RAppUser appUser = await _userManager.FindByNameAsync(loginViewModel.Username);
if (appUser == null)
{
appUser = await _userManager.FindByEmailAsync(loginViewModel.Username);
if(appUser == null)
{
return new RServiceResult(null, "Invalid username.");
}
}
var result = await _signInManager.CheckPasswordSignInAsync(appUser, loginViewModel.Password, true);
if (!result.Succeeded)
{
return new RServiceResult(null, "Invalid password or other error. Identity error details says: " + result.ToString());
}
if (appUser.Status == RAppUserStatus.Inactive)
{
return new RServiceResult(null, "User is disabled by an admin.");
}
RServiceResult securableItems = await GetUserSecurableItemsStatus(appUser.Id);
if (!string.IsNullOrEmpty(securableItems.ExceptionString))
return new RServiceResult(null, securableItems.ExceptionString);
RTemporaryUserSession userSession =
new RTemporaryUserSession()
{
RAppUserId = appUser.Id,
ClientIPAddress = clientIPAddress,
ClientAppName = loginViewModel.ClientAppName,
Language = loginViewModel.Language,
LoginTime = DateTime.Now,
LastRenewal = DateTime.Now,
ValidUntil = DateTime.Now + TimeSpan.FromSeconds(DefaultTokenExpirationInSeconds),
Token = ""
};
await _context.Sessions.AddAsync(userSession);
await _context.SaveChangesAsync();
RServiceResult userToken = await GenerateToken(loginViewModel.Username, appUser.Id, userSession.Id);
if(userToken.Result == null)
{
return new RServiceResult(null, userToken.ExceptionString);
}
userSession.Token = userToken.Result;
_context.Sessions.Update(userSession);
_context.SaveChanges();
return
new RServiceResult(
new LoggedOnUserModel()
{
SessionId = userSession.Id,
User = new PublicRAppUser()
{
Id = appUser.Id,
Username = appUser.UserName,
Email = appUser.Email,
FirstName = appUser.FirstName,
SureName = appUser.SureName,
PhoneNumber = appUser.PhoneNumber,
RImageId = appUser.RImageId,
Status = appUser.Status
},
Token = userToken.Result,
SecurableItem = securableItems.Result
}
);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// replace a (probably expired session) with a new one
///
///
///
///
public virtual async Task> ReLogin(Guid sessionId, string clientIPAddress)
{
try
{
RTemporaryUserSession oldSession = await _context.Sessions.Include(s => s.RAppUser).Where(s => s.Id == sessionId).SingleOrDefaultAsync();
if (oldSession == null)
{
return new RServiceResult(null, "Invalid session");
}
RAppUser appUser = oldSession.RAppUser;
if (appUser.Status == RAppUserStatus.Inactive)
{
return new RServiceResult(null, "User is disabled by an admin.");
}
RServiceResult securableItems = await GetUserSecurableItemsStatus(appUser.Id);
if (!string.IsNullOrEmpty(securableItems.ExceptionString))
return new RServiceResult(null, securableItems.ExceptionString);
RTemporaryUserSession newSession =
new RTemporaryUserSession()
{
RAppUserId = appUser.Id,
ClientIPAddress = clientIPAddress,
ClientAppName = oldSession.ClientAppName,
Language = oldSession.Language,
LoginTime = DateTime.Now,
LastRenewal = DateTime.Now,
ValidUntil = DateTime.Now + TimeSpan.FromSeconds(DefaultTokenExpirationInSeconds),
Token = ""
};
await _context.Sessions.AddAsync(newSession);
_context.Sessions.Remove(oldSession);
await _context.SaveChangesAsync();
RServiceResult userToken = await GenerateToken(appUser.UserName, appUser.Id, newSession.Id);
if (userToken.Result == null)
{
return new RServiceResult(null, userToken.ExceptionString);
}
newSession.Token = userToken.Result;
_context.Sessions.Update(newSession);
_context.SaveChanges();
return
new RServiceResult(
new LoggedOnUserModel()
{
SessionId = newSession.Id,
User = new PublicRAppUser()
{
Id = appUser.Id,
Username = appUser.UserName,
Email = appUser.Email,
FirstName = appUser.FirstName,
SureName = appUser.SureName,
PhoneNumber = appUser.PhoneNumber,
RImageId = appUser.RImageId,
Status = appUser.Status
},
Token = userToken.Result,
SecurableItem = securableItems.Result
}
);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// add user to role
///
///
///
///
public virtual async Task> AddUserToRole(Guid userId, string roleName)
{
try
{
RAppUser dbUserInfo =
await _userManager.Users.Where(u => u.Id == userId).SingleOrDefaultAsync();
if (dbUserInfo == null)
{
return new RServiceResult(false, $"کاربر مورد نظر با ایمیل {userId} پیدا نشد ");
}
if (!await _roleManager.RoleExistsAsync(roleName))
{
var identityResult = await _roleManager.CreateAsync(new RAppRole(roleName));
if (!identityResult.Succeeded)
{
return new RServiceResult(false, $"Error creating {roleName} role : " + ErrorsToString(identityResult.Errors));
}
}
var addToRoleResult = await _userManager.AddToRoleAsync(dbUserInfo, roleName);
if (!addToRoleResult.Succeeded)
{
return new RServiceResult(false, $"Error adding admin to {roleName} role : " + ErrorsToString(addToRoleResult.Errors));
}
return new RServiceResult(true);
}
catch(Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// Logout
///
///
///
///
public virtual async Task> Logout(Guid userId, Guid sessionId)
{
try
{
RTemporaryUserSession session =
await _context.Sessions
.Where(s => s.Id == sessionId && s.RAppUserId == userId)
.FirstOrDefaultAsync();
if (session == null)
return new RServiceResult(false, "session is invalid");
_context.Sessions.Remove(session);
await _context.SaveChangesAsync();
return new RServiceResult(true);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// Does Session exist?
///
///
///
///
public virtual async Task> SessionExists(Guid userId, Guid sessionId)
{
try
{
return new RServiceResult(
await _context.Sessions
.Where(s => s.RAppUserId == userId && s.Id == sessionId)
.FirstOrDefaultAsync() != null
);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// returns user information
///
///
/// PasswordHash becomes empty
///
///
///
public virtual async Task> GetUserInformation(Guid userId)
{
try
{
RAppUser appUser =
await _userManager.Users.Where(u => u.Id == userId).SingleOrDefaultAsync();
return new RServiceResult(
new PublicRAppUser()
{
Id = appUser.Id,
Username = appUser.UserName,
Email = appUser.Email,
FirstName = appUser.FirstName,
SureName = appUser.SureName,
PhoneNumber = appUser.PhoneNumber,
RImageId = appUser.RImageId,
Status = appUser.Status
});
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// all users informations
///
///
public virtual async Task> GetAllUsersInformation()
{
try
{
RAppUser[] appUsers = await _userManager.Users.ToArrayAsync();
List lstPublicUsersInfo = new List();
foreach(RAppUser appUser in appUsers)
{
lstPublicUsersInfo.Add(
new PublicRAppUser()
{
Id = appUser.Id,
Username = appUser.UserName,
Email = appUser.Email,
FirstName = appUser.FirstName,
SureName = appUser.SureName,
PhoneNumber = appUser.PhoneNumber,
RImageId = appUser.RImageId,
Status = appUser.Status
});
}
return new RServiceResult(lstPublicUsersInfo.ToArray());
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// Get User Sessions
///
/// if null is passed returns all sessions
///
public virtual async Task> GetUserSessions(Guid? userId)
{
try
{
List publicRUserSessions = new List();
RTemporaryUserSession[] sessions =
userId == null ?
await _context.Sessions.ToArrayAsync()
:
await _context.Sessions.Where(s => s.RAppUserId == userId).ToArrayAsync()
;
foreach (RTemporaryUserSession rUserSession in sessions)
publicRUserSessions.Add(
new PublicRUserSession()
{
Id = rUserSession.Id,
RAppUser = new PublicRAppUser()
{
Id = rUserSession.RAppUser.Id,
Username = rUserSession.RAppUser.UserName,
Email = rUserSession.RAppUser.Email,
FirstName = rUserSession.RAppUser.FirstName,
SureName = rUserSession.RAppUser.SureName,
PhoneNumber = rUserSession.RAppUser.PhoneNumber,
RImageId = rUserSession.RAppUser.RImageId,
Status = rUserSession.RAppUser.Status
},
ClientAppName = rUserSession.ClientAppName,
ClientIPAddress = rUserSession.ClientIPAddress,
Language = rUserSession.Language,
LastRenewal = rUserSession.LastRenewal,
LoginTime = rUserSession.LoginTime
}
);
return new RServiceResult(publicRUserSessions.ToArray());
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// is user admin?
///
///
///
public virtual async Task> IsAdmin(Guid userId)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(userId.ToString());
if (dbUserInfo == null)
{
return new RServiceResult(false, "کاربر مورد نظر یافت نشد");
}
bool res = await _userManager.IsInRoleAsync(dbUserInfo, _userRoleService.AdministratorRoleName);
return new RServiceResult(res);
}
catch(Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// is user in either of passed roles?
///
///
///
///
public virtual async Task> IsInRoles(Guid userId, string[] roleNames)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(userId.ToString());
if (dbUserInfo == null)
{
return new RServiceResult(false, "کاربر مورد نظر یافت نشد");
}
foreach(string roleName in roleNames)
{
if(await _userManager.IsInRoleAsync(dbUserInfo, roleName))
{
return new RServiceResult(true);
}
}
return new RServiceResult(false);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// Get User Roles
///
///
///
public virtual async Task>> GetUserRoles(Guid userId)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(userId.ToString());
if (dbUserInfo == null)
{
return new RServiceResult>(null, "کاربر مورد نظر یافت نشد");
}
return new RServiceResult>(await _userManager.GetRolesAsync(dbUserInfo));
}
catch (Exception exp)
{
return new RServiceResult>(null, exp.ToString());
}
}
///
/// remove user from role
///
/// user id
///
///
public virtual async Task> RemoveFromRole(Guid id, string role)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(id.ToString());
if (dbUserInfo == null)
{
return new RServiceResult(false, "کاربر مورد نظر یافت نشد");
}
IdentityResult result = await _userManager.RemoveFromRoleAsync(dbUserInfo, role);
if (!result.Succeeded)
{
return new RServiceResult(false, ErrorsToString(result.Errors));
}
return new RServiceResult(true);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// add user to role
///
/// user id
///
///
public virtual async Task> AddToRole(Guid id, string role)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(id.ToString());
if (dbUserInfo == null)
{
return new RServiceResult(false, "کاربر مورد نظر یافت نشد");
}
IdentityResult result = await _userManager.AddToRoleAsync(dbUserInfo, role);
if (!result.Succeeded)
{
return new RServiceResult(false, ErrorsToString(result.Errors));
}
return new RServiceResult(true);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// Lists user permissions
///
///
///
public virtual async Task> GetUserSecurableItemsStatus(Guid userId)
{
try
{
SecurableItem[] securableItems = _userRoleService.GetSecurableItems();
RServiceResult> roles = await GetUserRoles(userId);
if(!string.IsNullOrEmpty(roles.ExceptionString))
return new RServiceResult(null, roles.ExceptionString);
bool isAdmin = (await IsAdmin(userId)).Result;
foreach(SecurableItem securableItem in securableItems)
{
foreach(SecurableItemOperation operation in securableItem.Operations)
{
foreach(string role in roles.Result)
{
RServiceResult hasPermission = await _userRoleService.HasPermission(role, securableItem.ShortName, operation.ShortName);
if(!string.IsNullOrEmpty(hasPermission.ExceptionString))
return new RServiceResult(null, hasPermission.ExceptionString);
if (isAdmin || hasPermission.Result)
{
operation.Status = true;
}
}
}
}
return new RServiceResult(securableItems);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// Has user specified permission
///
///
///
///
///
public virtual async Task> HasPermission(Guid userId, string securableItemShortName, string operationShortName)
{
try
{
RServiceResult> roles = await GetUserRoles(userId);
if (!string.IsNullOrEmpty(roles.ExceptionString))
return new RServiceResult(false, roles.ExceptionString);
foreach (string role in roles.Result)
{
RServiceResult hasPermission = await _userRoleService.HasPermission(role, securableItemShortName, operationShortName);
if (!string.IsNullOrEmpty(hasPermission.ExceptionString))
return new RServiceResult(false, hasPermission.ExceptionString);
if (hasPermission.Result)
{
return new RServiceResult(true);
}
}
return
new RServiceResult
(
false
);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// all users having a certain permission
///
///
///
///
public virtual async Task> GetUsersHavingPermission(string securableItemShortName, string operationShortName)
{
try
{
var rolesResult = await _userRoleService.GetRolesHavingPermission(securableItemShortName, operationShortName);
if(!string.IsNullOrEmpty(rolesResult.ExceptionString))
{
return new RServiceResult(null, rolesResult.ExceptionString);
}
List lstPublicUsersInfo = new List();
RAppRole[] roles = rolesResult.Result;
foreach(RAppRole role in roles)
{
var usersInRole = _userManager.GetUsersInRoleAsync(role.Name);
foreach(var appUser in usersInRole.Result)
{
if(lstPublicUsersInfo.Where(u => u.Id == appUser.Id).FirstOrDefault() == null )
{
lstPublicUsersInfo.Add(
new PublicRAppUser()
{
Id = appUser.Id,
Username = appUser.UserName,
Email = appUser.Email,
FirstName = appUser.FirstName,
SureName = appUser.SureName,
PhoneNumber = appUser.PhoneNumber,
RImageId = appUser.RImageId,
Status = appUser.Status
});
}
}
}
return new RServiceResult(lstPublicUsersInfo.ToArray());
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// add a new user
///
///
public virtual async Task> AddUser(RegisterRAppUser newUserInfo)
{
try
{
if(!newUserInfo.IsAdmin)
{
RServiceResult checkUserExists = await EnsureDefaultUserExists();
if (!checkUserExists.Result)
{
return new RServiceResult(null, checkUserExists.ExceptionString);
}
}
RAppUser existingInfo = await _userManager.FindByNameAsync(newUserInfo.Username);
if (existingInfo != null)
{
return new RServiceResult(null, "username is already taken");
}
if(string.IsNullOrEmpty(newUserInfo.Password))
{
newUserInfo.Password = Guid.NewGuid().ToString();
}
RAppUser newDbUser =
new RAppUser()
{
UserName = newUserInfo.Username,
FirstName = newUserInfo.FirstName,
SureName = newUserInfo.SureName,
Email = newUserInfo.Email,
PhoneNumber = newUserInfo.PhoneNumber,
CreateDate = DateTime.Now,
Status = newUserInfo.Status
};
var result = await _userManager.CreateAsync(newDbUser, newUserInfo.Password);
if(!result.Succeeded)
{
return new RServiceResult(null, ErrorsToString(result.Errors));
}
newUserInfo.Id = newDbUser.Id;
if(newUserInfo.IsAdmin)
{
if (!await _roleManager.RoleExistsAsync(_userRoleService.AdministratorRoleName))
{
var roleCheckResult = await _roleManager.CreateAsync(new RAppRole(_userRoleService.AdministratorRoleName));
if (!roleCheckResult.Succeeded)
{
return new RServiceResult(null, "Error creating Administrator role : " + ErrorsToString(roleCheckResult.Errors));
}
}
var addToAdminRoleResult = await _userManager.AddToRoleAsync(newDbUser, _userRoleService.AdministratorRoleName);
if (!addToAdminRoleResult.Succeeded)
{
return new RServiceResult(null, $"Error adding {newDbUser.UserName} to Administrator role : " + ErrorsToString(addToAdminRoleResult.Errors));
}
}
return new RServiceResult(newDbUser);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// modify existing user
///
///
///
///
public virtual async Task> ModifyUser(Guid userId, RegisterRAppUser updateUserInfo)
{
try
{
RAppUser existingInfo = await _userManager.FindByIdAsync(userId.ToString());
if (existingInfo == null)
{
return new RServiceResult(false, "کاربر مورد نظر یافت نشد");
}
RServiceResult isAdmin = await IsAdmin(userId);
if(!string.IsNullOrEmpty(isAdmin.ExceptionString))
{
return new RServiceResult(false, isAdmin.ExceptionString);
}
if(isAdmin.Result && !updateUserInfo.IsAdmin)
{
List adminUsers = new List(await _userManager.GetUsersInRoleAsync(_userRoleService.AdministratorRoleName));
int nActiveAdminUsers = 0;
foreach(RAppUser adminUser in adminUsers)
{
if (adminUser.Status == RAppUserStatus.Active)
nActiveAdminUsers++;
}
if(nActiveAdminUsers <= 1)
{
return new RServiceResult(false, "You cannot reduce number of active admin users to 0.");
}
}
if (existingInfo.UserName != updateUserInfo.Username)
{
RAppUser anotheruserWithUserName = await _userManager.FindByNameAsync(updateUserInfo.Username);
if(anotheruserWithUserName != null)
{
return new RServiceResult(false, "کلمه عبور تکراری می باشد");
}
existingInfo.UserName = updateUserInfo.Username;
}
existingInfo.FirstName = updateUserInfo.FirstName;
existingInfo.SureName = updateUserInfo.SureName;
existingInfo.Email = updateUserInfo.Email;
existingInfo.PhoneNumber = updateUserInfo.PhoneNumber;
existingInfo.Status = updateUserInfo.Status;
if (!string.IsNullOrEmpty(updateUserInfo.Password))
{
foreach(var passwordValidator in _userManager.PasswordValidators)
{
var resPass = await passwordValidator.ValidateAsync(_userManager, existingInfo, updateUserInfo.Password);
if (!resPass.Succeeded)
{
return new RServiceResult(false, ErrorsToString(resPass.Errors));
}
}
existingInfo.PasswordHash = _userManager.PasswordHasher.HashPassword(existingInfo, updateUserInfo.Password);
}
var result = await _userManager.UpdateAsync(existingInfo);
if(!result.Succeeded)
{
return new RServiceResult(false, ErrorsToString(result.Errors));
}
//updating admin status is not supported
return new RServiceResult(true);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// change user password checking old password
///
///
///
///
///
public virtual async Task> ChangePassword(Guid userId, string oldPassword, string newPassword)
{
RAppUser appUser = await _userManager.FindByIdAsync(userId.ToString());
if (appUser == null)
{
return new RServiceResult(false, "کاربر مورد نظر یافت نشد");
}
var result = await _userManager.ChangePasswordAsync(appUser, oldPassword, newPassword);
if (!result.Succeeded)
{
return new RServiceResult(false, "Identity error details says: " + result.ToString());
}
return new RServiceResult(true);
}
///
/// delete user
///
///
/// true if succeeds
public virtual async Task> DeleteUser(Guid userId)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(userId.ToString());
if (dbUserInfo != null)
{
var result = await _userManager.DeleteAsync(dbUserInfo);
if(!result.Succeeded)
{
return new RServiceResult(false, ErrorsToString(result.Errors));
}
return new RServiceResult(true);
}
return new RServiceResult(false, "کاربر مورد نظر یافت نشد");
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// Set User Image
///
///
///
///
public virtual async Task> SetUserImage(Guid userId, IFormFileCollection files)
{
try
{
RAppUser user = await _userManager.FindByIdAsync(userId.ToString());
if(files.Count == 0)
{
user.RImageId = null;
}
else
{
if (files.Count != 1)
{
return new RServiceResult(null, "files.Count != 1");
}
IFormFile file = files[0];
int nImageWidth = 192;
using (Stream stream = file.OpenReadStream())
{
using (Image img = Image.FromStream(stream))
{
if (img.Width > nImageWidth)
{
using (Bitmap bmpPhase1 = new Bitmap(nImageWidth, nImageWidth))
{
using (Graphics g = Graphics.FromImage(bmpPhase1))
{
g.DrawImage(img, new Rectangle(0, 0, nImageWidth, img.Height * nImageWidth / img.Height));
}
using (Brush brush = new TextureBrush(bmpPhase1))
{
using (Bitmap bmpPhase2 = new Bitmap(nImageWidth, nImageWidth))
{
using (Graphics g = Graphics.FromImage(bmpPhase2))
{
g.FillEllipse(brush, new Rectangle(0, 0, nImageWidth, nImageWidth));
}
bmpPhase2.MakeTransparent();
using (MemoryStream ms = new MemoryStream())
{
bmpPhase2.Save(ms, ImageFormat.Png);
ms.Position = 0;
RServiceResult image = await _imageFileService.Add(null, ms, file.FileName, "UserProfiles");
if (!string.IsNullOrEmpty(image.ExceptionString))
{
return new RServiceResult(null, image.ExceptionString);
}
user.RImage = image.Result;
}
}
}
}
}
else
{
RServiceResult image = await _imageFileService.Add(null, stream, file.FileName, "UserProfiles");
if (!string.IsNullOrEmpty(image.ExceptionString))
{
return new RServiceResult(null, image.ExceptionString);
}
user.RImage = image.Result;
}
}
}
}
var result = await _userManager.UpdateAsync(user);
if(!result.Succeeded)
{
return new RServiceResult(null, ErrorsToString(result.Errors));
}
return new RServiceResult((Guid?)user.RImageId);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// Get User Image
///
///
///
public virtual async Task> GetUserImage(Guid userId)
{
try
{
RAppUser user = await _userManager.FindByIdAsync(userId.ToString());
if(user.RImageId != null)
{
return await _imageFileService.GetImage((Guid)user.RImageId);
}
return new RServiceResult(null);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// Start signup process using email
///
///
///
///
///
///
public virtual async Task> SignUp(string email, string clientIPAddress, string clientAppName, string langauge)
{
try
{
if(string.IsNullOrEmpty(clientIPAddress))
{
return new RServiceResult(null, "client ip address is empty");
}
if (string.IsNullOrEmpty(clientAppName))
{
return new RServiceResult(null, "client app name is empty");
}
RAppUser existingUser = await _userManager.FindByEmailAsync(email);
if (existingUser != null)
{
return new RServiceResult(null, "شما قبلا ثبت نام کردهاید. توجه بفرمایید که کاربران گنجینهٔ گنجور و پیشخان خوانشگران یکسانند و میتوانید با همان نام کاربری اینجا وارد شوید.");
}
existingUser = await _userManager.FindByNameAsync(email);
if (existingUser != null)
{
return new RServiceResult(null, "این نام کاربری قبلا استفاده شده است");
}
//checking this queue for previous signup attempts is unnecessary and is not done intentionally
RVerifyQueueItem item = new RVerifyQueueItem()
{
QueueType = RVerifyQueueType.SignUp,
Email = email,
DateTime = DateTime.Now,
ClientIPAddress = clientIPAddress,
ClientAppName = clientAppName,
Secret = $"{(new Random(DateTime.Now.Millisecond)).Next(0, 99999)}".PadLeft(6, '0'),
Language = langauge
};
while(
null !=
await _context.VerifyQueueItems.Where(i => i.Secret == item.Secret).SingleOrDefaultAsync()
)
{
item.Secret = $"{(new Random(DateTime.Now.Millisecond)).Next(0, 99999)}".PadLeft(6, '0');
}
await _context.VerifyQueueItems.AddAsync
(
item
);
await _context.SaveChangesAsync();
return new RServiceResult(item);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// verify signup / forgot password
///
///
///
/// associated email
public virtual async Task> RetrieveEmailFromQueueSecret(RVerifyQueueType verifyQueueType, string secret)
{
try
{
secret = secret.Trim();
RVerifyQueueItem item = await _context.VerifyQueueItems.Where(i => i.QueueType == verifyQueueType && i.Secret == secret).SingleOrDefaultAsync();
if(item == null)
{
return new RServiceResult("");
}
return new RServiceResult(item.Email);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// finalize signup process using email
///
///
///
///
///
///
///
public virtual async Task> FinalizeSignUp(string email, string secret, string password, string firstName, string sureName)
{
try
{
RAppUser existingUser = await _userManager.FindByEmailAsync(email);
if (existingUser != null)
{
return new RServiceResult(false, "این آدرس ایمیل قبلا استفاده شده است");
}
existingUser = await _userManager.FindByNameAsync(email);
if (existingUser != null)
{
return new RServiceResult(false, "این نام کاربری قبلا استفاده شده است");
}
if(
email
!=
(await RetrieveEmailFromQueueSecret(RVerifyQueueType.SignUp, secret)).Result
)
{
return new RServiceResult(false, "کلمه عبور اشتباه وارد شده است");
}
secret = secret.Trim();
RegisterRAppUser newUserInfo = new RegisterRAppUser()
{
Username = email,
Email = email,
Password = password,
Status = RAppUserStatus.Active,
IsAdmin = false,
FirstName = firstName,
SureName = sureName
};
RServiceResult userAddResult = await AddUser(newUserInfo);
if(userAddResult.Result == null)
{
return new RServiceResult(false, userAddResult.ExceptionString);
}
userAddResult.Result.EmailConfirmed = true;
await _userManager.UpdateAsync(userAddResult.Result);
RVerifyQueueItem[] failedQueue = await _context.VerifyQueueItems.Where(i => i.Email == email && i.Secret != secret && i.QueueType == RVerifyQueueType.SignUp).ToArrayAsync();
if(failedQueue.Length != 0)
{
_context.VerifyQueueItems.RemoveRange(failedQueue);
}
await _context.SaveChangesAsync();
return new RServiceResult(true);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// Start forgot password process using email
///
///
///
///
///
///
public virtual async Task> ForgotPassword(string email, string clientIPAddress, string clientAppName, string langauge)
{
try
{
if (string.IsNullOrEmpty(clientIPAddress))
{
return new RServiceResult(null, "client ip address is empty");
}
if (string.IsNullOrEmpty(clientAppName))
{
return new RServiceResult(null, "client app name is empty");
}
RAppUser rAppUser = await _userManager.FindByEmailAsync(email);
if (rAppUser == null)
{
return new RServiceResult(null, "کاربر مورد نظر یافت نشد");
}
//checking this queue for previous signup attempts is unnecessary and is not done intentionally
RVerifyQueueItem item = new RVerifyQueueItem()
{
QueueType = RVerifyQueueType.ForgotPassword,
Email = email,
DateTime = DateTime.Now,
ClientIPAddress = clientIPAddress,
ClientAppName = clientAppName,
Secret = $"{(new Random(DateTime.Now.Millisecond)).Next(0, 99999)}".PadLeft(6, '0'),
Language = langauge
};
while (
null !=
await _context.VerifyQueueItems.Where(i => i.Secret == item.Secret).SingleOrDefaultAsync()
)
{
item.Secret = $"{(new Random(DateTime.Now.Millisecond)).Next(0, 99999)}".PadLeft(6, '0');
}
await _context.VerifyQueueItems.AddAsync
(
item
);
await _context.SaveChangesAsync();
return new RServiceResult(item);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// reset password using email
///
///
///
///
///
///
public virtual async Task> ResetPassword(string email, string secret, string password, string clientIPAddress)
{
try
{
//we ignore input model in automatic auditing to prevent loginng password data, so we would add a manual auditing to have enough data on login intrusion and ...
REvent log = new REvent()
{
EventType = "AppUser/ResetPassword (POST)(Manual)",
StartDate = DateTime.UtcNow,
UserName = email,
IpAddress = clientIPAddress
};
_context.AuditLogs.Add(log);
await _context.SaveChangesAsync();
RAppUser existingUser = await _userManager.FindByEmailAsync(email);
if (existingUser == null)
{
return new RServiceResult(false, "کاربر مورد نظر با این آدرس ایمیل یافت نشد");
}
if (
email
!=
(await RetrieveEmailFromQueueSecret(RVerifyQueueType.ForgotPassword, secret)).Result
)
{
return new RServiceResult(false, "کلمه عبور اشتباه وارد شده است");
}
foreach (var passwordValidator in _userManager.PasswordValidators)
{
var resPass = await passwordValidator.ValidateAsync(_userManager, existingUser, password);
if (!resPass.Succeeded)
{
return new RServiceResult(false, ErrorsToString(resPass.Errors));
}
}
existingUser.PasswordHash = _userManager.PasswordHasher.HashPassword(existingUser, password);
await _userManager.UpdateAsync(existingUser);
RVerifyQueueItem[] failedQueue = await _context.VerifyQueueItems.Where(i => i.Email == email && i.QueueType == RVerifyQueueType.ForgotPassword).ToArrayAsync();
if (failedQueue.Length != 0)
{
_context.VerifyQueueItems.RemoveRange(failedQueue);
}
await _context.SaveChangesAsync();
return new RServiceResult(true);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// Find User By Email
///
///
///
public virtual async Task> FindUserByEmail(string email)
{
try
{
RAppUser appUser = await _userManager.FindByEmailAsync(email);
if (appUser == null)
return new RServiceResult(null);
return new RServiceResult(
new PublicRAppUser()
{
Id = appUser.Id,
Username = appUser.UserName,
Email = appUser.Email,
FirstName = appUser.FirstName,
SureName = appUser.SureName,
PhoneNumber = appUser.PhoneNumber,
RImageId = appUser.RImageId,
Status = appUser.Status
});
}
catch(Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// delete tenant
///
///
public RServiceResult DeleteTenant()
{
try
{
_context.DeleteDb();
return new RServiceResult(true);
}
catch(Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// EnsureDefaultUserExists
///
///
private async Task> EnsureDefaultUserExists()
{
try
{
//If no user exists create default one
if (_userManager.Users.Count() == 0)
{
RAppUser admin = new RAppUser()
{
UserName = "admin@ganjoor.net",
FirstName = "راهبر",
SureName = "سیستم",
Email = "admin@ganjoor.net",
PhoneNumber = "00989123456789",
CreateDate = DateTime.Now,
Status = RAppUserStatus.Active
};
var identityResult = await _userManager.CreateAsync(
admin, "Test!123"
);
if (!identityResult.Succeeded)
{
return new RServiceResult(false, "Error creating default user : " + ErrorsToString(identityResult.Errors));
}
if (!await _roleManager.RoleExistsAsync(_userRoleService.AdministratorRoleName))
{
identityResult = await _roleManager.CreateAsync(new RAppRole(_userRoleService.AdministratorRoleName));
if (!identityResult.Succeeded)
{
return new RServiceResult(false, "Error creating Administrator role : " + ErrorsToString(identityResult.Errors));
}
}
identityResult = await _userManager.AddToRoleAsync(admin, _userRoleService.AdministratorRoleName);
if (!identityResult.Succeeded)
{
return new RServiceResult(false, "Error adding admin to Administrator role : " + ErrorsToString(identityResult.Errors));
}
}
return new RServiceResult(true);
}
catch (Exception exp)
{
return new RServiceResult(false, exp.ToString());
}
}
///
/// secret used for generating Jwt token
///
public string TokenSecret { get { return $"{Configuration.GetSection("Security")["Secret"]}"; } }
///
/// JWT Tokens Expiration Time Out
///
public int DefaultTokenExpirationInSeconds { get { return int.Parse( $"{Configuration.GetSection("Security")["DefaultTokenExpirationInSeconds"]}"); } }
#region Internals
#region Token Generation
///
/// Token Generation
///
///
///
///
///
private async Task> GenerateToken(string username, Guid userId, Guid sessionId)
{
var user = await _userManager.FindByIdAsync(userId.ToString());
if (user == null)
return new RServiceResult(null, "کاربر مورد نظر یافت نشد");
var claims = new List
{
new Claim(ClaimTypes.Name, username),
new Claim("UserId", userId.ToString()),
new Claim("SessionId", sessionId.ToString()),
};
var userClaims = await _userManager.GetClaimsAsync(user);
claims.AddRange(userClaims);
var userRoles = await _userManager.GetRolesAsync(user);
foreach (var userRole in userRoles)
{
claims.Add(new Claim(ClaimTypes.Role, userRole));
var role = await _roleManager.FindByNameAsync(userRole);
if (role != null)
{
var roleClaims = await _roleManager.GetClaimsAsync(role);
foreach (Claim roleClaim in roleClaims)
{
claims.Add(roleClaim);
}
}
}
var token = new JwtSecurityToken(
issuer: "Ganjoor",
audience: "Everyone",
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddSeconds(DefaultTokenExpirationInSeconds),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenSecret)), SecurityAlgorithms.HmacSha256)
);
return new RServiceResult(new JwtSecurityTokenHandler().WriteToken(token));
}
///
/// Renew Expired token
///
///
///
private async Task> RegenerateToken(string token)
{
try
{
var principal = GetPrincipalFromExpiredToken(token);
//check refreshToken here to see if it is valid or not
//check user here, if is not valid return BadRequest();
ExtractTokenInfo(token, out string username, out Guid userId, out Guid sessionId);
return await GenerateToken(username, userId, sessionId);
}
catch (Exception exp)
{
return new RServiceResult(null, exp.ToString());
}
}
///
/// Extract Information From Token
///
///
///
///
///
public void ExtractTokenInfo(string token, out string username, out Guid userId, out Guid sessionId)
{
var principal = GetPrincipalFromExpiredToken(token);
username = principal.Identity.Name;
userId = new Guid(principal.Claims.FirstOrDefault(c => c.Type == "UserId").Value);
sessionId = new Guid(principal.Claims.FirstOrDefault(c => c.Type == "SessionId").Value);
}
///
///
///
///
///
private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidAudience = "Everyone",
ValidateIssuer = true,
ValidIssuer = "Ganjoor",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenSecret)),
ValidateLifetime = false, //important
ClockSkew = TimeSpan.Zero
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
#endregion
#region Identity Errors conversion
///
/// convert identity errors to string
///
///
///
protected static string ErrorsToString(IEnumerable errors)
{
StringBuilder sb = new StringBuilder();
foreach(IdentityError error in errors)
{
sb.AppendLine(error.Description);
}
return sb.ToString();
}
#endregion
#endregion
///
/// Main Database context
///
protected readonly RSecurityDbContext _context;
///
/// Image File Service
///
protected readonly IImageFileService _imageFileService;
///
/// User Role Service
///
protected readonly IUserRoleService _userRoleService;
///
/// Identity User Manageer
///
protected UserManager _userManager = null;
///
/// Identity SignIn Manager
///
protected SignInManager _signInManager = null;
///
/// Identity Role Manager
///
protected RoleManager _roleManager = null;
///
/// secret generator
///
protected readonly ISecretGenerator _secretGenerator;
///
/// Configuration
///
protected IConfiguration Configuration { get; }
///
/// constructor
///
///
///
///
///
///
///
///
///
public AppUserService(
RSecurityDbContext context,
UserManager userManager,
SignInManager signInManager,
RoleManager roleManager,
ISecretGenerator secretGenerator,
IImageFileService imageFileService,
IUserRoleService userRoleService,
IConfiguration configuration)
{
_context = context;
_userManager = userManager;
_signInManager = signInManager;
_roleManager = roleManager;
_secretGenerator = secretGenerator;
_imageFileService = imageFileService;
_userRoleService = userRoleService;
Configuration = configuration;
}
}
}