1737 lines
67 KiB
C#
1737 lines
67 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 virtual 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, "نام کاربری و/یا رمز نادرست است.");
|
||
}
|
||
}
|
||
|
||
var result = await _signInManager.CheckPasswordSignInAsync(appUser, loginViewModel.Password, true);
|
||
if(result.IsLockedOut)
|
||
{
|
||
return new RServiceResult<LoggedOnUserModel>(null, "نام کاربری شما به دلیل ورود متوالی ۵ بارهٔ رمزهای اشتباه قفل شده است. لطفاً ۵ دقیقهٔدیگر مجدداً تلاش کنید.");
|
||
}
|
||
|
||
if(result.IsNotAllowed || result.RequiresTwoFactor)
|
||
{
|
||
return new RServiceResult<LoggedOnUserModel>(null, result.ToString());
|
||
}
|
||
|
||
if (!result.Succeeded)
|
||
{
|
||
return new RServiceResult<LoggedOnUserModel>(null, "نام کاربری و/یا رمز نادرست است.");
|
||
}
|
||
|
||
if (appUser.Status == RAppUserStatus.Inactive)
|
||
{
|
||
return new RServiceResult<LoggedOnUserModel>(null, "نام کاربری شما غیرفعال شده است.");
|
||
}
|
||
|
||
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,
|
||
NickName = appUser.NickName,
|
||
Website = appUser.Website,
|
||
Bio = appUser.Bio
|
||
},
|
||
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 virtual 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,
|
||
NickName = appUser.NickName,
|
||
Website = appUser.Website,
|
||
Bio = appUser.Bio
|
||
},
|
||
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 virtual 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 virtual 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 virtual 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 virtual async Task<RServiceResult<PublicRAppUser>> GetUserInformation(Guid userId)
|
||
{
|
||
|
||
try
|
||
{
|
||
RAppUser appUser =
|
||
await _userManager.Users.Where(u => u.Id == userId).SingleOrDefaultAsync();
|
||
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,
|
||
NickName = appUser.NickName,
|
||
Website = appUser.Website,
|
||
Bio = appUser.Bio
|
||
});
|
||
}
|
||
catch (Exception exp)
|
||
{
|
||
return new RServiceResult<PublicRAppUser>(null, exp.ToString());
|
||
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// all users informations
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public virtual 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,
|
||
NickName = appUser.NickName,
|
||
Website = appUser.Website,
|
||
Bio = appUser.Bio
|
||
});
|
||
}
|
||
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 virtual 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()
|
||
{
|
||
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,
|
||
NickName = rUserSession.RAppUser.NickName,
|
||
Website = rUserSession.RAppUser.Website,
|
||
Bio = rUserSession.RAppUser.Bio
|
||
},
|
||
ClientAppName = rUserSession.ClientAppName,
|
||
ClientIPAddress = rUserSession.ClientIPAddress,
|
||
Language = rUserSession.Language,
|
||
LastRenewal = rUserSession.LastRenewal,
|
||
LoginTime = rUserSession.LoginTime
|
||
}
|
||
);
|
||
|
||
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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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,
|
||
NickName = appUser.NickName,
|
||
Website = appUser.Website,
|
||
Bio = appUser.Bio
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
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 virtual 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(isAdmin.Result != updateUserInfo.IsAdmin)
|
||
{
|
||
return new RServiceResult<bool>(false, "امکان تغییر وضعیت مدیر کاربر از طریق این تابع وجود ندارد.");
|
||
}
|
||
|
||
|
||
|
||
|
||
if (existingInfo.UserName != updateUserInfo.Username)
|
||
{
|
||
|
||
RAppUser anotheruserWithUserName = await _userManager.FindByNameAsync(updateUserInfo.Username);
|
||
|
||
if(anotheruserWithUserName != null)
|
||
{
|
||
return new RServiceResult<bool>(false, "کلمهٔ عبور تکراری می باشد");
|
||
}
|
||
|
||
existingInfo.UserName = updateUserInfo.Username;
|
||
}
|
||
|
||
if(updateUserInfo.RImageId != null && updateUserInfo.RImageId != Guid.Empty && updateUserInfo.RImageId != existingInfo.RImageId)
|
||
{
|
||
return new RServiceResult<bool>(false, "برای تغییر تصویر کاربر از تابع اختصاصی این کار استفاده کنید");
|
||
}
|
||
|
||
|
||
|
||
|
||
existingInfo.FirstName = updateUserInfo.FirstName;
|
||
existingInfo.SureName = updateUserInfo.SureName;
|
||
existingInfo.Email = updateUserInfo.Email;
|
||
existingInfo.PhoneNumber = updateUserInfo.PhoneNumber;
|
||
existingInfo.Status = updateUserInfo.Status;
|
||
existingInfo.Bio = updateUserInfo.Bio;
|
||
existingInfo.NickName = updateUserInfo.NickName;
|
||
existingInfo.Website = updateUserInfo.Website;
|
||
|
||
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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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>
|
||
/// verify email on signup
|
||
/// </summary>
|
||
public bool VerifyEmailOnSignUp
|
||
{
|
||
get
|
||
{
|
||
return bool.Parse(Configuration.GetSection("SignUp")["VerifyEmail"]);
|
||
}
|
||
}
|
||
|
||
/// <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 virtual 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, "این نام کاربری قبلا استفاده شده است");
|
||
}
|
||
|
||
bool verify = VerifyEmailOnSignUp;
|
||
|
||
if (verify)
|
||
{
|
||
if (
|
||
email
|
||
!=
|
||
(await RetrieveEmailFromQueueSecret(RVerifyQueueType.SignUp, secret)).Result
|
||
)
|
||
{
|
||
return new RServiceResult<bool>(false, "کد ارسالی به ایمیل اشتباه وارد شده است");
|
||
}
|
||
}
|
||
|
||
secret = secret.Trim();
|
||
|
||
firstName = firstName.Trim();
|
||
sureName = sureName.Trim();
|
||
|
||
if (string.IsNullOrEmpty(firstName) && string.IsNullOrEmpty(sureName))
|
||
{
|
||
return new RServiceResult<bool>(false, "لطفاً حداقل یکی از اطلاعات نام یا نام خانوادگی را وارد کنید.");
|
||
}
|
||
|
||
RegisterRAppUser newUserInfo = new RegisterRAppUser()
|
||
{
|
||
Username = email,
|
||
Email = email,
|
||
Password = password,
|
||
Status = RAppUserStatus.Active,
|
||
IsAdmin = false,
|
||
FirstName = firstName,
|
||
SureName = sureName,
|
||
NickName = $"{firstName} {sureName}".Trim()
|
||
};
|
||
|
||
RServiceResult<RAppUser> userAddResult = await AddUser(newUserInfo);
|
||
|
||
if(userAddResult.Result == null)
|
||
{
|
||
return new RServiceResult<bool>(false, userAddResult.ExceptionString);
|
||
}
|
||
if(verify)
|
||
{
|
||
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 virtual 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 virtual 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 virtual 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,
|
||
NickName = appUser.NickName,
|
||
Website = appUser.Website,
|
||
Bio = appUser.Bio
|
||
});
|
||
}
|
||
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@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<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>
|
||
/// 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();
|
||
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken securityToken);
|
||
if (!(securityToken is JwtSecurityToken jwtSecurityToken) || !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;
|
||
}
|
||
}
|
||
}
|