divan/RSecurityBackend/Services/Implementation/AppUserService.cs
2021-02-04 09:23:21 +03:30

1679 lines
64 KiB
C#

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
{
/// <summary>
/// Authentication Service
/// </summary>
public class AppUserService : IAppUserService
{
/// <summary>
/// Login user, if failed return LoggedOnUserModel is null
/// </summary>
/// <param name="loginViewModel"></param>
/// <param name="clientIPAddress"></param>
/// <returns></returns>
public async Task<RServiceResult<LoggedOnUserModel>> 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<bool> checkUserExists = await EnsureDefaultUserExists();
if(!checkUserExists.Result)
{
return new RServiceResult<LoggedOnUserModel>(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<LoggedOnUserModel>(null, "Invalid username.");
}
}
var result = await _signInManager.CheckPasswordSignInAsync(appUser, loginViewModel.Password, true);
if (!result.Succeeded)
{
return new RServiceResult<LoggedOnUserModel>(null, "Invalid password or other error. Identity error details says: " + result.ToString());
}
if (appUser.Status == RAppUserStatus.Inactive)
{
return new RServiceResult<LoggedOnUserModel>(null, "User is disabled by an admin.");
}
RServiceResult<SecurableItem[]> securableItems = await GetUserSecurableItemsStatus(appUser.Id);
if (!string.IsNullOrEmpty(securableItems.ExceptionString))
return new RServiceResult<LoggedOnUserModel>(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<string> userToken = await GenerateToken(loginViewModel.Username, appUser.Id, userSession.Id);
if(userToken.Result == null)
{
return new RServiceResult<LoggedOnUserModel>(null, userToken.ExceptionString);
}
userSession.Token = userToken.Result;
_context.Sessions.Update(userSession);
_context.SaveChanges();
return
new RServiceResult<LoggedOnUserModel>(
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<LoggedOnUserModel>(null, exp.ToString());
}
}
/// <summary>
/// replace a (probably expired session) with a new one
/// </summary>
/// <param name="sessionId"></param>
/// <param name="clientIPAddress"></param>
/// <returns></returns>
public async Task<RServiceResult<LoggedOnUserModel>> 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<LoggedOnUserModel>(null, "Invalid session");
}
RAppUser appUser = oldSession.RAppUser;
if (appUser.Status == RAppUserStatus.Inactive)
{
return new RServiceResult<LoggedOnUserModel>(null, "User is disabled by an admin.");
}
RServiceResult<SecurableItem[]> securableItems = await GetUserSecurableItemsStatus(appUser.Id);
if (!string.IsNullOrEmpty(securableItems.ExceptionString))
return new RServiceResult<LoggedOnUserModel>(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<string> userToken = await GenerateToken(appUser.UserName, appUser.Id, newSession.Id);
if (userToken.Result == null)
{
return new RServiceResult<LoggedOnUserModel>(null, userToken.ExceptionString);
}
newSession.Token = userToken.Result;
_context.Sessions.Update(newSession);
_context.SaveChanges();
return
new RServiceResult<LoggedOnUserModel>(
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<LoggedOnUserModel>(null, exp.ToString());
}
}
/// <summary>
/// add user to role
/// </summary>
/// <param name="userId"></param>
/// <param name="roleName"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> AddUserToRole(Guid userId, string roleName)
{
try
{
RAppUser dbUserInfo =
await _userManager.Users.Where(u => u.Id == userId).SingleOrDefaultAsync();
if (dbUserInfo == null)
{
return new RServiceResult<bool>(false, $"کاربر مورد نظر با ایمیل {userId} پیدا نشد ");
}
if (!await _roleManager.RoleExistsAsync(roleName))
{
var identityResult = await _roleManager.CreateAsync(new RAppRole(roleName));
if (!identityResult.Succeeded)
{
return new RServiceResult<bool>(false, $"Error creating {roleName} role : " + ErrorsToString(identityResult.Errors));
}
}
var addToRoleResult = await _userManager.AddToRoleAsync(dbUserInfo, roleName);
if (!addToRoleResult.Succeeded)
{
return new RServiceResult<bool>(false, $"Error adding admin to {roleName} role : " + ErrorsToString(addToRoleResult.Errors));
}
return new RServiceResult<bool>(true);
}
catch(Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// Logout
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> 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<bool>(false, "session is invalid");
_context.Sessions.Remove(session);
await _context.SaveChangesAsync();
return new RServiceResult<bool>(true);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// Does Session exist?
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> SessionExists(Guid userId, Guid sessionId)
{
try
{
return new RServiceResult<bool>(
await _context.Sessions
.Where(s => s.RAppUserId == userId && s.Id == sessionId)
.FirstOrDefaultAsync() != null
);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// returns user information
/// </summary>
/// <remarks>
/// PasswordHash becomes empty
/// </remarks>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<RServiceResult<PublicRAppUser>> GetUserInformation(Guid userId)
{
try
{
RAppUser appUser =
await _userManager.Users.Where(u => u.Id == userId).SingleOrDefaultAsync();
return new RServiceResult<PublicRAppUser>(
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<PublicRAppUser>(null, exp.ToString());
}
}
/// <summary>
/// all users informations
/// </summary>
/// <returns></returns>
public async Task<RServiceResult<PublicRAppUser[]>> GetAllUsersInformation()
{
try
{
RAppUser[] appUsers = await _userManager.Users.ToArrayAsync();
List<PublicRAppUser> lstPublicUsersInfo = new List<PublicRAppUser>();
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<PublicRAppUser[]>(lstPublicUsersInfo.ToArray());
}
catch (Exception exp)
{
return new RServiceResult<PublicRAppUser[]>(null, exp.ToString());
}
}
/// <summary>
/// Get User Sessions
/// </summary>
/// <param name="userId">if null is passed returns all sessions</param>
/// <returns></returns>
public async Task<RServiceResult<PublicRUserSession[]>> GetUserSessions(Guid? userId)
{
try
{
List<PublicRUserSession> publicRUserSessions = new List<PublicRUserSession>();
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(rUserSession));
return new RServiceResult<PublicRUserSession[]>(publicRUserSessions.ToArray());
}
catch (Exception exp)
{
return new RServiceResult<PublicRUserSession[]>(null, exp.ToString());
}
}
/// <summary>
/// is user admin?
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> IsAdmin(Guid userId)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(userId.ToString());
if (dbUserInfo == null)
{
return new RServiceResult<bool>(false, "کاربر مورد نظر یافت نشد");
}
bool res = await _userManager.IsInRoleAsync(dbUserInfo, _userRoleService.AdministratorRoleName);
return new RServiceResult<bool>(res);
}
catch(Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// is user in either of passed roles?
/// </summary>
/// <param name="userId"></param>
/// <param name="roleNames"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> IsInRoles(Guid userId, string[] roleNames)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(userId.ToString());
if (dbUserInfo == null)
{
return new RServiceResult<bool>(false, "کاربر مورد نظر یافت نشد");
}
foreach(string roleName in roleNames)
{
if(await _userManager.IsInRoleAsync(dbUserInfo, roleName))
{
return new RServiceResult<bool>(true);
}
}
return new RServiceResult<bool>(false);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// Get User Roles
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<RServiceResult<IList<string>>> GetUserRoles(Guid userId)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(userId.ToString());
if (dbUserInfo == null)
{
return new RServiceResult<IList<string>>(null, "کاربر مورد نظر یافت نشد");
}
return new RServiceResult<IList<string>>(await _userManager.GetRolesAsync(dbUserInfo));
}
catch (Exception exp)
{
return new RServiceResult<IList<string>>(null, exp.ToString());
}
}
/// <summary>
/// remove user from role
/// </summary>
/// <param name="id">user id</param>
/// <param name="role"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> RemoveFromRole(Guid id, string role)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(id.ToString());
if (dbUserInfo == null)
{
return new RServiceResult<bool>(false, "کاربر مورد نظر یافت نشد");
}
IdentityResult result = await _userManager.RemoveFromRoleAsync(dbUserInfo, role);
if (!result.Succeeded)
{
return new RServiceResult<bool>(false, ErrorsToString(result.Errors));
}
return new RServiceResult<bool>(true);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// add user to role
/// </summary>
/// <param name="id">user id</param>
/// <param name="role"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> AddToRole(Guid id, string role)
{
try
{
RAppUser dbUserInfo = await _userManager.FindByIdAsync(id.ToString());
if (dbUserInfo == null)
{
return new RServiceResult<bool>(false, "کاربر مورد نظر یافت نشد");
}
IdentityResult result = await _userManager.AddToRoleAsync(dbUserInfo, role);
if (!result.Succeeded)
{
return new RServiceResult<bool>(false, ErrorsToString(result.Errors));
}
return new RServiceResult<bool>(true);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// Lists user permissions
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<RServiceResult<SecurableItem[]>> GetUserSecurableItemsStatus(Guid userId)
{
try
{
SecurableItem[] securableItems = _userRoleService.GetSecurableItems();
RServiceResult<IList<string>> roles = await GetUserRoles(userId);
if(!string.IsNullOrEmpty(roles.ExceptionString))
return new RServiceResult<SecurableItem[]>(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<bool> hasPermission = await _userRoleService.HasPermission(role, securableItem.ShortName, operation.ShortName);
if(!string.IsNullOrEmpty(hasPermission.ExceptionString))
return new RServiceResult<SecurableItem[]>(null, hasPermission.ExceptionString);
if (isAdmin || hasPermission.Result)
{
operation.Status = true;
}
}
}
}
return new RServiceResult<SecurableItem[]>(securableItems);
}
catch (Exception exp)
{
return new RServiceResult<SecurableItem[]>(null, exp.ToString());
}
}
/// <summary>
/// Has user specified permission
/// </summary>
/// <param name="userId"></param>
/// <param name="securableItemShortName"></param>
/// <param name="operationShortName"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> HasPermission(Guid userId, string securableItemShortName, string operationShortName)
{
try
{
RServiceResult<IList<string>> roles = await GetUserRoles(userId);
if (!string.IsNullOrEmpty(roles.ExceptionString))
return new RServiceResult<bool>(false, roles.ExceptionString);
foreach (string role in roles.Result)
{
RServiceResult<bool> hasPermission = await _userRoleService.HasPermission(role, securableItemShortName, operationShortName);
if (!string.IsNullOrEmpty(hasPermission.ExceptionString))
return new RServiceResult<bool>(false, hasPermission.ExceptionString);
if (hasPermission.Result)
{
return new RServiceResult<bool>(true);
}
}
return
new RServiceResult<bool>
(
false
);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// all users having a certain permission
/// </summary>
/// <param name="securableItemShortName"></param>
/// <param name="operationShortName"></param>
/// <returns></returns>
public async Task<RServiceResult<PublicRAppUser[]>> GetUsersHavingPermission(string securableItemShortName, string operationShortName)
{
try
{
var rolesResult = await _userRoleService.GetRolesHavingPermission(securableItemShortName, operationShortName);
if(!string.IsNullOrEmpty(rolesResult.ExceptionString))
{
return new RServiceResult<PublicRAppUser[]>(null, rolesResult.ExceptionString);
}
List<PublicRAppUser> lstPublicUsersInfo = new List<PublicRAppUser>();
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<PublicRAppUser[]>(lstPublicUsersInfo.ToArray());
}
catch (Exception exp)
{
return new RServiceResult<PublicRAppUser[]>(null, exp.ToString());
}
}
/// <summary>
/// add a new user
/// </summary>
/// <returns></returns>
public virtual async Task<RServiceResult<RAppUser>> AddUser(RegisterRAppUser newUserInfo)
{
try
{
if(!newUserInfo.IsAdmin)
{
RServiceResult<bool> checkUserExists = await EnsureDefaultUserExists();
if (!checkUserExists.Result)
{
return new RServiceResult<RAppUser>(null, checkUserExists.ExceptionString);
}
}
RAppUser existingInfo = await _userManager.FindByNameAsync(newUserInfo.Username);
if (existingInfo != null)
{
return new RServiceResult<RAppUser>(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<RAppUser>(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<RAppUser>(null, "Error creating Administrator role : " + ErrorsToString(roleCheckResult.Errors));
}
}
var addToAdminRoleResult = await _userManager.AddToRoleAsync(newDbUser, _userRoleService.AdministratorRoleName);
if (!addToAdminRoleResult.Succeeded)
{
return new RServiceResult<RAppUser>(null, $"Error adding {newDbUser.UserName} to Administrator role : " + ErrorsToString(addToAdminRoleResult.Errors));
}
}
return new RServiceResult<RAppUser>(newDbUser);
}
catch (Exception exp)
{
return new RServiceResult<RAppUser>(null, exp.ToString());
}
}
/// <summary>
/// modify existing user
/// </summary>
/// <param name="userId"></param>
/// <param name="updateUserInfo"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> ModifyUser(Guid userId, RegisterRAppUser updateUserInfo)
{
try
{
RAppUser existingInfo = await _userManager.FindByIdAsync(userId.ToString());
if (existingInfo == null)
{
return new RServiceResult<bool>(false, "کاربر مورد نظر یافت نشد");
}
RServiceResult<bool> isAdmin = await IsAdmin(userId);
if(!string.IsNullOrEmpty(isAdmin.ExceptionString))
{
return new RServiceResult<bool>(false, isAdmin.ExceptionString);
}
if(isAdmin.Result && !updateUserInfo.IsAdmin)
{
List<RAppUser> adminUsers = new List<RAppUser>(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<bool>(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<bool>(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<bool>(false, ErrorsToString(resPass.Errors));
}
}
existingInfo.PasswordHash = _userManager.PasswordHasher.HashPassword(existingInfo, updateUserInfo.Password);
}
var result = await _userManager.UpdateAsync(existingInfo);
if(!result.Succeeded)
{
return new RServiceResult<bool>(false, ErrorsToString(result.Errors));
}
//updating admin status is not supported
return new RServiceResult<bool>(true);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// change user password checking old password
/// </summary>
/// <param name="userId"></param>
/// <param name="oldPassword"></param>
/// <param name="newPassword"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> ChangePassword(Guid userId, string oldPassword, string newPassword)
{
RAppUser appUser = await _userManager.FindByIdAsync(userId.ToString());
if (appUser == null)
{
return new RServiceResult<bool>(false, "کاربر مورد نظر یافت نشد");
}
var result = await _userManager.ChangePasswordAsync(appUser, oldPassword, newPassword);
if (!result.Succeeded)
{
return new RServiceResult<bool>(false, "Identity error details says: " + result.ToString());
}
return new RServiceResult<bool>(true);
}
/// <summary>
/// delete user
/// </summary>
/// <param name="userId"></param>
/// <returns>true if succeeds</returns>
public async Task<RServiceResult<bool>> 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<bool>(false, ErrorsToString(result.Errors));
}
return new RServiceResult<bool>(true);
}
return new RServiceResult<bool>(false, "کاربر مورد نظر یافت نشد");
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// Set User Image
/// </summary>
/// <param name="userId"></param>
/// <param name="files"></param>
/// <returns></returns>
public async Task<RServiceResult<Guid?>> 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<Guid?>(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<RImage> image = await _imageFileService.Add(null, ms, file.FileName, "UserProfiles");
if (!string.IsNullOrEmpty(image.ExceptionString))
{
return new RServiceResult<Guid?>(null, image.ExceptionString);
}
user.RImage = image.Result;
}
}
}
}
}
else
{
RServiceResult<RImage> image = await _imageFileService.Add(null, stream, file.FileName, "UserProfiles");
if (!string.IsNullOrEmpty(image.ExceptionString))
{
return new RServiceResult<Guid?>(null, image.ExceptionString);
}
user.RImage = image.Result;
}
}
}
}
var result = await _userManager.UpdateAsync(user);
if(!result.Succeeded)
{
return new RServiceResult<Guid?>(null, ErrorsToString(result.Errors));
}
return new RServiceResult<Guid?>((Guid?)user.RImageId);
}
catch (Exception exp)
{
return new RServiceResult<Guid?>(null, exp.ToString());
}
}
/// <summary>
/// Get User Image
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<RServiceResult<RImage>> 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<RImage>(null);
}
catch (Exception exp)
{
return new RServiceResult<RImage>(null, exp.ToString());
}
}
/// <summary>
/// Start signup process using email
/// </summary>
/// <param name="email"></param>
/// <param name="clientIPAddress"></param>
/// <param name="clientAppName"></param>
/// <param name="langauge"></param>
/// <returns></returns>
public async Task<RServiceResult<RVerifyQueueItem>> SignUp(string email, string clientIPAddress, string clientAppName, string langauge)
{
try
{
if(string.IsNullOrEmpty(clientIPAddress))
{
return new RServiceResult<RVerifyQueueItem>(null, "client ip address is empty");
}
if (string.IsNullOrEmpty(clientAppName))
{
return new RServiceResult<RVerifyQueueItem>(null, "client app name is empty");
}
RAppUser existingUser = await _userManager.FindByEmailAsync(email);
if (existingUser != null)
{
return new RServiceResult<RVerifyQueueItem>(null, "شما قبلا ثبت نام کرده‌اید. توجه بفرمایید که کاربران گنجینهٔ گنجور و پیشخان خوانشگران یکسانند و می‌توانید با همان نام کاربری اینجا وارد شوید.");
}
existingUser = await _userManager.FindByNameAsync(email);
if (existingUser != null)
{
return new RServiceResult<RVerifyQueueItem>(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<RVerifyQueueItem>(item);
}
catch (Exception exp)
{
return new RServiceResult<RVerifyQueueItem>(null, exp.ToString());
}
}
/// <summary>
/// verify signup / forgot password
/// </summary>
/// <param name="verifyQueueType"></param>
/// <param name="secret"></param>
/// <returns>associated email</returns>
public async Task<RServiceResult<string>> 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<string>("");
}
return new RServiceResult<string>(item.Email);
}
catch (Exception exp)
{
return new RServiceResult<string>(null, exp.ToString());
}
}
/// <summary>
/// finalize signup process using email
/// </summary>
/// <param name="email"></param>
/// <param name="secret"></param>
/// <param name="password"></param>
/// <param name="firstName"></param>
/// <param name="sureName"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> FinalizeSignUp(string email, string secret, string password, string firstName, string sureName)
{
try
{
RAppUser existingUser = await _userManager.FindByEmailAsync(email);
if (existingUser != null)
{
return new RServiceResult<bool>(false, "این آدرس ایمیل قبلا استفاده شده است");
}
existingUser = await _userManager.FindByNameAsync(email);
if (existingUser != null)
{
return new RServiceResult<bool>(false, "این نام کاربری قبلا استفاده شده است");
}
if(
email
!=
(await RetrieveEmailFromQueueSecret(RVerifyQueueType.SignUp, secret)).Result
)
{
return new RServiceResult<bool>(false, "کلمه عبور اشتباه وارد شده است");
}
secret = secret.Trim();
RegisterRAppUser newUserInfo = new RegisterRAppUser()
{
Username = email,
Email = email,
Password = password,
Status = RAppUserStatus.Active,
IsAdmin = false,
FirstName = firstName,
SureName = sureName
};
RServiceResult<RAppUser> userAddResult = await AddUser(newUserInfo);
if(userAddResult.Result == null)
{
return new RServiceResult<bool>(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<bool>(true);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// Start forgot password process using email
/// </summary>
/// <param name="email"></param>
/// <param name="clientIPAddress"></param>
/// <param name="clientAppName"></param>
/// <param name="langauge"></param>
/// <returns></returns>
public async Task<RServiceResult<RVerifyQueueItem>> ForgotPassword(string email, string clientIPAddress, string clientAppName, string langauge)
{
try
{
if (string.IsNullOrEmpty(clientIPAddress))
{
return new RServiceResult<RVerifyQueueItem>(null, "client ip address is empty");
}
if (string.IsNullOrEmpty(clientAppName))
{
return new RServiceResult<RVerifyQueueItem>(null, "client app name is empty");
}
RAppUser rAppUser = await _userManager.FindByEmailAsync(email);
if (rAppUser == null)
{
return new RServiceResult<RVerifyQueueItem>(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<RVerifyQueueItem>(item);
}
catch (Exception exp)
{
return new RServiceResult<RVerifyQueueItem>(null, exp.ToString());
}
}
/// <summary>
/// reset password using email
/// </summary>
/// <param name="email"></param>
/// <param name="secret"></param>
/// <param name="password"></param>
/// <param name="clientIPAddress"></param>
/// <returns></returns>
public async Task<RServiceResult<bool>> 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<bool>(false, "کاربر مورد نظر با این آدرس ایمیل یافت نشد");
}
if (
email
!=
(await RetrieveEmailFromQueueSecret(RVerifyQueueType.ForgotPassword, secret)).Result
)
{
return new RServiceResult<bool>(false, "کلمه عبور اشتباه وارد شده است");
}
foreach (var passwordValidator in _userManager.PasswordValidators)
{
var resPass = await passwordValidator.ValidateAsync(_userManager, existingUser, password);
if (!resPass.Succeeded)
{
return new RServiceResult<bool>(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<bool>(true);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// Find User By Email
/// </summary>
/// <param name="email"></param>
/// <returns></returns>
public async Task<RServiceResult<PublicRAppUser>> FindUserByEmail(string email)
{
try
{
RAppUser appUser = await _userManager.FindByEmailAsync(email);
if (appUser == null)
return new RServiceResult<PublicRAppUser>(null);
return new RServiceResult<PublicRAppUser>(
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<PublicRAppUser>(null, exp.ToString());
}
}
/// <summary>
/// delete tenant
/// </summary>
/// <returns></returns>
public RServiceResult<bool> DeleteTenant()
{
try
{
_context.DeleteDb();
return new RServiceResult<bool>(true);
}
catch(Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// EnsureDefaultUserExists
/// </summary>
/// <returns></returns>
private async Task<RServiceResult<bool>> EnsureDefaultUserExists()
{
try
{
//If no user exists create default one
if (_userManager.Users.Count() == 0)
{
RAppUser admin = new RAppUser()
{
UserName = "admin",
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<bool>(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<bool>(false, "Error creating Administrator role : " + ErrorsToString(identityResult.Errors));
}
}
identityResult = await _userManager.AddToRoleAsync(admin, _userRoleService.AdministratorRoleName);
if (!identityResult.Succeeded)
{
return new RServiceResult<bool>(false, "Error adding admin to Administrator role : " + ErrorsToString(identityResult.Errors));
}
}
return new RServiceResult<bool>(true);
}
catch (Exception exp)
{
return new RServiceResult<bool>(false, exp.ToString());
}
}
/// <summary>
/// secret used for generating Jwt token
/// </summary>
public string TokenSecret { get { return $"{Configuration.GetSection("Security")["Secret"]}"; } }
/// <summary>
/// JWT Tokens Expiration Time Out
/// </summary>
public int DefaultTokenExpirationInSeconds { get { return int.Parse( $"{Configuration.GetSection("Security")["DefaultTokenExpirationInSeconds"]}"); } }
#region Internals
#region Token Generation
/// <summary>
/// Token Generation
/// </summary>
/// <param name="username"></param>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
private async Task<RServiceResult<string>> GenerateToken(string username, Guid userId, Guid sessionId)
{
var user = await _userManager.FindByIdAsync(userId.ToString());
if (user == null)
return new RServiceResult<string>(null, "کاربر مورد نظر یافت نشد");
var claims = new List<Claim>
{
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<string>(new JwtSecurityTokenHandler().WriteToken(token));
}
/// <summary>
/// Renew Expired token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
private async Task<RServiceResult<string>> 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<string>(null, exp.ToString());
}
}
/// <summary>
/// Extract Information From Token
/// </summary>
/// <param name="token"></param>
/// <param name="username"></param>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
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);
}
/// <summary>
///
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
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
/// <summary>
/// convert identity errors to string
/// </summary>
/// <param name="errors"></param>
/// <returns></returns>
protected static string ErrorsToString(IEnumerable<IdentityError> errors)
{
StringBuilder sb = new StringBuilder();
foreach(IdentityError error in errors)
{
sb.AppendLine(error.Description);
}
return sb.ToString();
}
#endregion
#endregion
/// <summary>
/// Main Database context
/// </summary>
protected readonly RSecurityDbContext<RAppUser, RAppRole, Guid> _context;
/// <summary>
/// Image File Service
/// </summary>
protected readonly IImageFileService _imageFileService;
/// <summary>
/// User Role Service
/// </summary>
protected readonly IUserRoleService _userRoleService;
/// <summary>
/// Identity User Manageer
/// </summary>
protected UserManager<RAppUser> _userManager = null;
/// <summary>
/// Identity SignIn Manager
/// </summary>
protected SignInManager<RAppUser> _signInManager = null;
/// <summary>
/// Identity Role Manager
/// </summary>
protected RoleManager<RAppRole> _roleManager = null;
/// <summary>
/// secret generator
/// </summary>
protected readonly ISecretGenerator _secretGenerator;
/// <summary>
/// Configuration
/// </summary>
protected IConfiguration Configuration { get; }
/// <summary>
/// constructor
/// </summary>
/// <param name="context"></param>
/// <param name="userManager"></param>
/// <param name="signInManager"></param>
/// <param name="roleManager"></param>
/// <param name="secretGenerator"></param>
/// <param name="imageFileService"></param>
/// <param name="userRoleService"></param>
/// <param name="configuration"></param>
public AppUserService(
RSecurityDbContext<RAppUser, RAppRole, Guid> context,
UserManager<RAppUser> userManager,
SignInManager<RAppUser> signInManager,
RoleManager<RAppRole> 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;
}
}
}