using Oqtane.Shared; using Oqtane.Models; using System.Net.Http; using System.Threading.Tasks; using Oqtane.Documentation; using System.Net; using System.Collections.Generic; using Microsoft.Extensions.Localization; namespace Oqtane.Services { /// /// Manage (get / update) user information /// public interface IUserService { /// /// Get a of a specific site /// /// ID of a /// ID of a /// Task GetUserAsync(int userId, int siteId); /// /// Get a of a specific site /// /// Username / login of a /// ID of a /// Task GetUserAsync(string username, int siteId); /// /// Get a of a specific site /// /// Username / login of a /// email address of a /// ID of a /// Task GetUserAsync(string username, string email, int siteId); /// /// Save a user to the Database. /// The object contains all the information incl. what it belongs to. /// /// /// Task AddUserAsync(User user); /// /// Update an existing user in the database. /// /// /// Task UpdateUserAsync(User user); /// /// Delete / remove a user in the database /// /// ID-reference to the /// ID-reference to the /// Task DeleteUserAsync(int userId, int siteId); /// /// Will login the specified . /// /// Note that this will probably not be a real User, but a user object where the `Username` and `Password` have been filled. /// /// A object which should have at least the and set. /// Determines if the login cookie should be set (only relevant for Hybrid scenarios) /// Determines if the login cookie should be persisted for a long time. /// Task LoginUserAsync(User user, bool setCookie, bool isPersistent); /// /// Logout a /// /// /// Task LogoutUserAsync(User user); /// /// Logout a /// /// /// Task LogoutUserEverywhereAsync(User user); /// /// Update e-mail verification status of a user. /// /// The we're verifying /// A Hash value in the URL which verifies this user got the e-mail (containing this token) /// Task VerifyEmailAsync(User user, string token); /// /// Trigger a forgot-password e-mail for this . /// /// /// Task ForgotPasswordAsync(User user); /// /// Reset the password of this /// /// /// /// Task ResetPasswordAsync(User user, string token); /// /// Verify the two factor verification code /// /// /// /// Task VerifyTwoFactorAsync(User user, string token); /// /// Validate identity user info. /// /// /// /// /// Task ValidateUserAsync(string username, string email, string password); /// /// Validate a users password against the password policy /// /// /// Task ValidatePasswordAsync(string password); /// /// Get token for current user /// /// Task GetTokenAsync(); /// /// Get personal access token for current user (administrators only) /// /// Task GetPersonalAccessTokenAsync(); /// /// Link an external login with a local user account /// /// The we're verifying /// A Hash value in the URL which verifies this user got the e-mail (containing this token) /// External Login provider type /// External Login provider key /// External Login provider display name /// Task LinkUserAsync(User user, string token, string type, string key, string name); /// /// Get password requirements for site /// /// ID of a /// Task GetPasswordRequirementsAsync(int siteId); /// /// Bulk import of users /// /// ID of a /// ID of a /// Indicates if new users should be notified by email /// Task> ImportUsersAsync(int siteId, int fileId, bool notify); } [PrivateApi("Don't show in the documentation, as everything should use the Interface")] public class UserService : ServiceBase, IUserService { private readonly IStringLocalizer _localizer; public UserService(IStringLocalizer localizer, HttpClient http, SiteState siteState) : base(http, siteState) { _localizer = localizer; } private string Apiurl => CreateApiUrl("User"); public async Task GetUserAsync(int userId, int siteId) { return await GetJsonAsync($"{Apiurl}/{userId}?siteid={siteId}"); } public async Task GetUserAsync(string username, int siteId) { return await GetJsonAsync($"{Apiurl}/username/{username}?siteid={siteId}"); } public async Task GetUserAsync(string username, string email, int siteId) { return await GetJsonAsync($"{Apiurl}/name/{(!string.IsNullOrEmpty(username) ? username : "-")}/{(!string.IsNullOrEmpty(email) ? email : "-")}/?siteid={siteId}"); } public async Task AddUserAsync(User user) { return await PostJsonAsync(Apiurl, user); } public async Task UpdateUserAsync(User user) { return await PutJsonAsync($"{Apiurl}/{user.UserId}", user); } public async Task DeleteUserAsync(int userId, int siteId) { await DeleteAsync($"{Apiurl}/{userId}?siteid={siteId}"); } public async Task LoginUserAsync(User user, bool setCookie, bool isPersistent) { return await PostJsonAsync($"{Apiurl}/login?setcookie={setCookie}&persistent={isPersistent}", user); } public async Task LogoutUserAsync(User user) { await PostJsonAsync($"{Apiurl}/logout", user); } public async Task LogoutUserEverywhereAsync(User user) { await PostJsonAsync($"{Apiurl}/logouteverywhere", user); } public async Task VerifyEmailAsync(User user, string token) { return await PostJsonAsync($"{Apiurl}/verify?token={token}", user); } public async Task ForgotPasswordAsync(User user) { await PostJsonAsync($"{Apiurl}/forgot", user); } public async Task ResetPasswordAsync(User user, string token) { return await PostJsonAsync($"{Apiurl}/reset?token={token}", user); } public async Task VerifyTwoFactorAsync(User user, string token) { return await PostJsonAsync($"{Apiurl}/twofactor?token={token}", user); } public async Task ValidateUserAsync(string username, string email, string password) { return await GetJsonAsync($"{Apiurl}/validateuser?username={WebUtility.UrlEncode(username)}&email={WebUtility.UrlEncode(email)}&password={WebUtility.UrlEncode(password)}"); } public async Task ValidatePasswordAsync(string password) { return await GetJsonAsync($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}"); } public async Task GetTokenAsync() { return await GetStringAsync($"{Apiurl}/token"); } public async Task GetPersonalAccessTokenAsync() { return await GetStringAsync($"{Apiurl}/personalaccesstoken"); } public async Task LinkUserAsync(User user, string token, string type, string key, string name) { return await PostJsonAsync($"{Apiurl}/link?token={token}&type={type}&key={key}&name={name}", user); } public async Task GetPasswordRequirementsAsync(int siteId) { var requirements = await GetJsonAsync>($"{Apiurl}/passwordrequirements/{siteId}"); var minimumlength = (requirements.ContainsKey("IdentityOptions:Password:RequiredLength")) ? requirements["IdentityOptions:Password:RequiredLength"] : "6"; var uniquecharacters = (requirements.ContainsKey("IdentityOptions:Password:RequiredUniqueChars")) ? requirements["IdentityOptions:Password:RequiredUniqueChars"] : "1"; var requiredigit = bool.Parse((requirements.ContainsKey("IdentityOptions:Password:RequireDigit")) ? requirements["IdentityOptions:Password:RequireDigit"] : "true"); var requireupper = bool.Parse((requirements.ContainsKey("IdentityOptions:Password:RequireUppercase")) ? requirements["IdentityOptions:Password:RequireUppercase"] : "true"); var requirelower = bool.Parse((requirements.ContainsKey("IdentityOptions:Password:RequireLowercase")) ? requirements["IdentityOptions:Password:RequireLowercase"] : "true"); var requirepunctuation = bool.Parse((requirements.ContainsKey("IdentityOptions:Password:RequireNonAlphanumeric")) ? requirements["IdentityOptions:Password:RequireNonAlphanumeric"] : "true"); // replace the placeholders with the setting values string digitRequirement = requiredigit ? _localizer["Password.DigitRequirement"] + ", " : ""; string uppercaseRequirement = requireupper ? _localizer["Password.UppercaseRequirement"] + ", " : ""; string lowercaseRequirement = requirelower ? _localizer["Password.LowercaseRequirement"] + ", " : ""; string punctuationRequirement = requirepunctuation ? _localizer["Password.PunctuationRequirement"] + ", " : ""; string passwordValidationCriteriaTemplate = _localizer["Password.ValidationCriteria"]; // format requirements return string.Format(passwordValidationCriteriaTemplate, minimumlength, uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement); } public async Task> ImportUsersAsync(int siteId, int fileId, bool notify) { return await PostJsonAsync>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}¬ify={notify}", null); } } }