Merge pull request #2039 from sbwalker/dev
Allow user identity password and lockout configuration to be customized. Included additional environment information in System Info.
This commit is contained in:
commit
3f2095870d
|
@ -27,9 +27,27 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="serverpath" HelpText="Server Path" ResourceKey="ServerPath">Server Path: </Label>
|
||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="serverpath" class="form-control" @bind="@_serverpath" readonly />
|
||||
<input id="machinename" class="form-control" @bind="@_machinename" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
|
@ -38,6 +56,18 @@
|
|||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tickcount" HelpText="Amount Of Time The Service Has Been Available And Operational" ResourceKey="TickCount">Service Uptime: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tickcount" class="form-control" @bind="@_tickcount" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="workingset" class="form-control" @bind="@_workingset" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
|
||||
<div class="col-sm-9">
|
||||
|
@ -119,8 +149,13 @@
|
|||
private string _version = string.Empty;
|
||||
private string _clrversion = string.Empty;
|
||||
private string _osversion = string.Empty;
|
||||
private string _serverpath = string.Empty;
|
||||
private string _machinename = string.Empty;
|
||||
private string _ipaddress = string.Empty;
|
||||
private string _contentrootpath = string.Empty;
|
||||
private string _webrootpath = string.Empty;
|
||||
private string _servertime = string.Empty;
|
||||
private string _tickcount = string.Empty;
|
||||
private string _workingset = string.Empty;
|
||||
private string _installationid = string.Empty;
|
||||
|
||||
private string _detailederrors = string.Empty;
|
||||
|
@ -133,33 +168,42 @@
|
|||
{
|
||||
_version = Constants.Version;
|
||||
|
||||
Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync();
|
||||
Dictionary<string, object> systeminfo = await SystemService.GetSystemInfoAsync("environment");
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_clrversion = systeminfo["clrversion"];
|
||||
_osversion = systeminfo["osversion"];
|
||||
_serverpath = systeminfo["serverpath"];
|
||||
_servertime = systeminfo["servertime"] + " UTC";
|
||||
_installationid = systeminfo["installationid"];
|
||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||
_osversion = systeminfo["OSVersion"].ToString();
|
||||
_machinename = systeminfo["MachineName"].ToString();
|
||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||
_tickcount = TimeSpan.FromMilliseconds(Convert.ToInt64(systeminfo["TickCount"].ToString())).ToString();
|
||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||
}
|
||||
|
||||
_detailederrors = systeminfo["detailederrors"];
|
||||
_logginglevel = systeminfo["logginglevel"];
|
||||
_notificationlevel = systeminfo["notificationlevel"];
|
||||
_swagger = systeminfo["swagger"];
|
||||
_packageservice = systeminfo["packageservice"];
|
||||
}
|
||||
}
|
||||
systeminfo = await SystemService.GetSystemInfoAsync();
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_installationid = systeminfo["InstallationId"].ToString();
|
||||
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||
_swagger = systeminfo["UseSwagger"].ToString();
|
||||
_packageservice = systeminfo["PackageService"].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = new Dictionary<string, string>();
|
||||
settings.Add("detailederrors", _detailederrors);
|
||||
settings.Add("logginglevel", _logginglevel);
|
||||
settings.Add("notificationlevel", _notificationlevel);
|
||||
settings.Add("swagger", _swagger);
|
||||
settings.Add("packageservice", _packageservice);
|
||||
var settings = new Dictionary<string, object>();
|
||||
settings.Add("DetailedErrors", _detailederrors);
|
||||
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
||||
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
||||
settings.Add("UseSwagger", _swagger);
|
||||
settings.Add("PackageService", _packageservice);
|
||||
await SystemService.UpdateSystemInfoAsync(settings);
|
||||
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@inject IUserService UserService
|
||||
@inject ISettingService SettingService
|
||||
@inject ISiteService SiteService
|
||||
@inject ISystemService SystemService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
|
@ -64,6 +65,74 @@ else
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<br />
|
||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="minimumlength" class="form-control" @bind="@_minimumlength" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requiredigit" class="form-select" @bind="@_requiredigit" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireupper" class="form-select" @bind="@_requireupper" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirelower" class="form-select" @bind="@_requirelower" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="maximum" class="form-control" @bind="@_maximumfailures" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
|
@ -72,79 +141,104 @@ else
|
|||
}
|
||||
|
||||
@code {
|
||||
private List<UserRole> allroles;
|
||||
private List<UserRole> userroles;
|
||||
private string _search;
|
||||
private List<UserRole> allroles;
|
||||
private List<UserRole> userroles;
|
||||
private string _search;
|
||||
|
||||
private string _allowregistration;
|
||||
private string _minimumlength = "6";
|
||||
private string _uniquecharacters = "1";
|
||||
private string _requiredigit = "true";
|
||||
private string _requireupper = "true";
|
||||
private string _requirelower = "true";
|
||||
private string _requirepunctuation = "true";
|
||||
private string _maximumfailures = "5";
|
||||
private string _lockoutduration = "5";
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
|
||||
await LoadSettingsAsync();
|
||||
userroles = Search(_search);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
|
||||
await LoadSettingsAsync();
|
||||
userroles = Search(_search);
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString();
|
||||
}
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
Dictionary<string, object> systeminfo = await SystemService.GetSystemInfoAsync();
|
||||
if (systeminfo != null)
|
||||
{
|
||||
_minimumlength = systeminfo["Password:RequiredLength"].ToString();
|
||||
_uniquecharacters = systeminfo["Password:RequiredUniqueChars"].ToString();
|
||||
_requiredigit = systeminfo["Password:RequireDigit"].ToString();
|
||||
_requireupper = systeminfo["Password:RequireUppercase"].ToString();
|
||||
_requirelower = systeminfo["Password:RequireLowercase"].ToString();
|
||||
_requirepunctuation = systeminfo["Password:RequireNonAlphanumeric"].ToString();
|
||||
_maximumfailures = systeminfo["Lockout:MaxFailedAccessAttempts"].ToString();
|
||||
_lockoutduration = TimeSpan.Parse(systeminfo["Lockout:DefaultLockoutTimeSpan"].ToString()).TotalMinutes.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<UserRole> Search(string search)
|
||||
{
|
||||
var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)));
|
||||
private List<UserRole> Search(string search)
|
||||
{
|
||||
var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)));
|
||||
|
||||
if (!string.IsNullOrEmpty(_search))
|
||||
{
|
||||
results = results.Where(item =>
|
||||
(
|
||||
item.User.Username.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
||||
item.User.Email.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
||||
item.User.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
);
|
||||
}
|
||||
return results.ToList();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_search))
|
||||
{
|
||||
results = results.Where(item =>
|
||||
(
|
||||
item.User.Username.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
||||
item.User.Email.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
||||
item.User.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
);
|
||||
}
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
private async Task OnSearch()
|
||||
{
|
||||
userroles = Search(_search);
|
||||
await UpdateSettingsAsync();
|
||||
}
|
||||
private async Task OnSearch()
|
||||
{
|
||||
userroles = Search(_search);
|
||||
await UpdateSettingsAsync();
|
||||
}
|
||||
|
||||
private async Task DeleteUser(UserRole UserRole)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
||||
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
|
||||
userroles = Search(_search);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
private async Task DeleteUser(UserRole UserRole)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
||||
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
|
||||
userroles = Search(_search);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private string settingSearch = "AU-search";
|
||||
private string settingSearch = "AU-search";
|
||||
|
||||
private async Task LoadSettingsAsync()
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
_search = SettingService.GetSetting(settings, settingSearch, "");
|
||||
}
|
||||
private async Task LoadSettingsAsync()
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
_search = SettingService.GetSetting(settings, settingSearch, "");
|
||||
}
|
||||
|
||||
private async Task UpdateSettingsAsync()
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
SettingService.SetSetting(settings, settingSearch, _search);
|
||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
||||
}
|
||||
private async Task UpdateSettingsAsync()
|
||||
{
|
||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
SettingService.SetSetting(settings, settingSearch, _search);
|
||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
|
@ -153,7 +247,25 @@ else
|
|||
var site = PageState.Site;
|
||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
var settings = new Dictionary<string, object>();
|
||||
settings.Add("Password:RequiredLength", _minimumlength);
|
||||
settings.Add("Password:RequiredUniqueChars", _uniquecharacters);
|
||||
settings.Add("Password:RequireDigit", _requiredigit);
|
||||
settings.Add("Password:RequireUppercase", _requireupper);
|
||||
settings.Add("Password:RequireLowercase", _requirelower);
|
||||
settings.Add("Password:RequireNonAlphanumeric", _requirepunctuation);
|
||||
settings.Add("Lockout:MaxFailedAccessAttempts", _maximumfailures);
|
||||
settings.Add("Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString());
|
||||
await SystemService.UpdateSystemInfoAsync(settings);
|
||||
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -157,10 +157,10 @@
|
|||
<value>Verification Failed. Please Ensure You Entered The Code Exactly In The Form Provided In Your Email. If You Wish To Request A New Verification Code Please Select The Cancel Option And Sign In Again. </value>
|
||||
</data>
|
||||
<data name="Message.TwoFactor" xml:space="preserve">
|
||||
<value>A Secure Verification Code Has Been Sent To Your Email Address. Please Enter The Code That You Received. If You Do Not Receive The Code Within 10 Minutes Or You Have Lost Access To Your Email, Please Contact Your Administrator.</value>
|
||||
<value>A Secure Verification Code Has Been Sent To Your Email Address. Please Enter The Code That You Received. If You Do Not Receive The Code Or You Have Lost Access To Your Email, Please Contact Your Administrator.</value>
|
||||
</data>
|
||||
<data name="Password.HelpText" xml:space="preserve">
|
||||
<value>Please Enter The Password Related To Your Account. Remember That Passwords Are Case Sensitive. If Your Sign In Attempts Fail 3 Times In Succession, Your Account Will Be Locked Out For 10 Minutes.</value>
|
||||
<value>Please Enter The Password Related To Your Account. Remember That Passwords Are Case Sensitive. If You Attempt Unsuccessfully To Log In To Your Account Multiple Times, You Will Be Locked Out For A Period Of Time.</value>
|
||||
</data>
|
||||
<data name="Password.Placeholder" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
|
|
|
@ -129,8 +129,8 @@
|
|||
<data name="OSVersion.HelpText" xml:space="preserve">
|
||||
<value>Operating System Version</value>
|
||||
</data>
|
||||
<data name="ServerPath.HelpText" xml:space="preserve">
|
||||
<value>Server Path</value>
|
||||
<data name="ContentRootPath.HelpText" xml:space="preserve">
|
||||
<value>Server Root Path</value>
|
||||
</data>
|
||||
<data name="ServerTime.HelpText" xml:space="preserve">
|
||||
<value>Server Date/Time (in UTC)</value>
|
||||
|
@ -144,8 +144,8 @@
|
|||
<data name="OSVersion.Text" xml:space="preserve">
|
||||
<value>OS Version: </value>
|
||||
</data>
|
||||
<data name="ServerPath.Text" xml:space="preserve">
|
||||
<value>Server Path: </value>
|
||||
<data name="ContentRootPath.Text" xml:space="preserve">
|
||||
<value>Root Path: </value>
|
||||
</data>
|
||||
<data name="ServerTime.Text" xml:space="preserve">
|
||||
<value>Server Date/Time: </value>
|
||||
|
@ -240,4 +240,34 @@
|
|||
<data name="NotificationLevel.Text" xml:space="preserve">
|
||||
<value>Notification Level:</value>
|
||||
</data>
|
||||
<data name="IPAddress.HelpText" xml:space="preserve">
|
||||
<value>Server IP Address</value>
|
||||
</data>
|
||||
<data name="IPAddress.Text" xml:space="preserve">
|
||||
<value>IP Address:</value>
|
||||
</data>
|
||||
<data name="MachineName.HelpText" xml:space="preserve">
|
||||
<value>Server Machine Name</value>
|
||||
</data>
|
||||
<data name="MachineName.Text" xml:space="preserve">
|
||||
<value>Machine Name:</value>
|
||||
</data>
|
||||
<data name="TickCount.HelpText" xml:space="preserve">
|
||||
<value>Amount Of Time The Service Has Been Available And Operational</value>
|
||||
</data>
|
||||
<data name="TickCount.Text" xml:space="preserve">
|
||||
<value>Service Uptime:</value>
|
||||
</data>
|
||||
<data name="WebRootPath.HelpText" xml:space="preserve">
|
||||
<value>Server Web Root Path</value>
|
||||
</data>
|
||||
<data name="WebRootPath.Text" xml:space="preserve">
|
||||
<value>Web Path:</value>
|
||||
</data>
|
||||
<data name="WorkingSet.HelpText" xml:space="preserve">
|
||||
<value>Memory Allocation Of Service (in MB)</value>
|
||||
</data>
|
||||
<data name="WorkingSet.Text" xml:space="preserve">
|
||||
<value>Memory Allocation:</value>
|
||||
</data>
|
||||
</root>
|
|
@ -153,4 +153,55 @@
|
|||
<data name="Roles.Text" xml:space="preserve">
|
||||
<value>Roles</value>
|
||||
</data>
|
||||
<data name="LockoutDuration.HelpText" xml:space="preserve">
|
||||
<value>The Number Of Minutes A User Should Be Locked Out</value>
|
||||
</data>
|
||||
<data name="LockoutDuration.Text" xml:space="preserve">
|
||||
<value>Lockout Duration:</value>
|
||||
</data>
|
||||
<data name="MaximumFailures.HelpText" xml:space="preserve">
|
||||
<value>The Maximum Number Of Sign In Attempts Before A User Is Locked Out</value>
|
||||
</data>
|
||||
<data name="MaximumFailures.Text" xml:space="preserve">
|
||||
<value>Maximum Failures:</value>
|
||||
</data>
|
||||
<data name="RequireDigit.HelpText" xml:space="preserve">
|
||||
<value>Indicate If Passwords Must Contain A Digit</value>
|
||||
</data>
|
||||
<data name="RequireDigit.Text" xml:space="preserve">
|
||||
<value>Require Digit?</value>
|
||||
</data>
|
||||
<data name="RequiredLength.HelpText" xml:space="preserve">
|
||||
<value>The Minimum Length For A Password</value>
|
||||
</data>
|
||||
<data name="RequiredLength.Text" xml:space="preserve">
|
||||
<value>Minimum Length:</value>
|
||||
</data>
|
||||
<data name="RequireLower.HelpText" xml:space="preserve">
|
||||
<value>Indicate If Passwords Must Contain A Lower Case Character</value>
|
||||
</data>
|
||||
<data name="RequireLower.Text" xml:space="preserve">
|
||||
<value>Require Lowercase?</value>
|
||||
</data>
|
||||
<data name="RequirePunctuation.HelpText" xml:space="preserve">
|
||||
<value>Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)</value>
|
||||
</data>
|
||||
<data name="RequirePunctuation.Text" xml:space="preserve">
|
||||
<value>Require Punctuation?</value>
|
||||
</data>
|
||||
<data name="RequireUpper.HelpText" xml:space="preserve">
|
||||
<value>Indicate If Passwords Must Contain An Upper Case Character</value>
|
||||
</data>
|
||||
<data name="RequireUpper.Text" xml:space="preserve">
|
||||
<value>Require Uppercase?</value>
|
||||
</data>
|
||||
<data name="Success.UpdateConfig.Restart" xml:space="preserve">
|
||||
<value>Configuration Updated. Please Select Restart Application For These Changes To Be Activated.</value>
|
||||
</data>
|
||||
<data name="UniqueCharacters.HelpText" xml:space="preserve">
|
||||
<value>The Minimum Number Of Unique Characters Which A Password Must Contain</value>
|
||||
</data>
|
||||
<data name="UniqueCharacters.Text" xml:space="preserve">
|
||||
<value>Unique Characters:</value>
|
||||
</data>
|
||||
</root>
|
|
@ -9,16 +9,34 @@ namespace Oqtane.Services
|
|||
public interface ISystemService
|
||||
{
|
||||
/// <summary>
|
||||
/// returns a key-value directory with the current system information (os-version, clr-version, etc.)
|
||||
/// returns a key-value directory with the current system configuration information
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, string>> GetSystemInfoAsync();
|
||||
Task<Dictionary<string, object>> GetSystemInfoAsync();
|
||||
|
||||
/// <summary>
|
||||
/// returns a key-value directory with the current system information - "environment" or "configuration"
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, object>> GetSystemInfoAsync(string type);
|
||||
|
||||
/// <summary>
|
||||
/// returns a config value
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<object> GetSystemInfoAsync(string settingKey, object defaultValue);
|
||||
|
||||
/// <summary>
|
||||
/// Updates system information
|
||||
/// </summary>
|
||||
/// <param name="settings"></param>
|
||||
/// <returns></returns>
|
||||
Task UpdateSystemInfoAsync(Dictionary<string, string> settings);
|
||||
Task UpdateSystemInfoAsync(Dictionary<string, object> settings);
|
||||
|
||||
/// <summary>
|
||||
/// updates a config value
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task UpdateSystemInfoAsync(string settingKey, object settingValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,28 @@ namespace Oqtane.Services
|
|||
|
||||
private string Apiurl => CreateApiUrl("System", _siteState.Alias);
|
||||
|
||||
public async Task<Dictionary<string, string>> GetSystemInfoAsync()
|
||||
public async Task<Dictionary<string, object>> GetSystemInfoAsync()
|
||||
{
|
||||
return await GetJsonAsync<Dictionary<string, string>>(Apiurl);
|
||||
return await GetSystemInfoAsync("configuration");
|
||||
}
|
||||
|
||||
public async Task UpdateSystemInfoAsync(Dictionary<string, string> settings)
|
||||
public async Task<Dictionary<string, object>> GetSystemInfoAsync(string type)
|
||||
{
|
||||
return await GetJsonAsync<Dictionary<string, object>>($"{Apiurl}?type={type}");
|
||||
}
|
||||
|
||||
public async Task<object> GetSystemInfoAsync(string settingKey, object defaultValue)
|
||||
{
|
||||
return await GetJsonAsync<object>($"{Apiurl}/{settingKey}/{defaultValue}");
|
||||
}
|
||||
|
||||
public async Task UpdateSystemInfoAsync(Dictionary<string, object> settings)
|
||||
{
|
||||
await PostJsonAsync(Apiurl, settings);
|
||||
}
|
||||
public async Task UpdateSystemInfoAsync(string settingKey, object settingValue)
|
||||
{
|
||||
await PutJsonAsync($"{Apiurl}/{settingKey}/{settingValue}", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using Oqtane.Shared;
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Oqtane.Infrastructure;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
|
@ -20,62 +21,76 @@ namespace Oqtane.Controllers
|
|||
_configManager = configManager;
|
||||
}
|
||||
|
||||
// GET: api/<controller>
|
||||
// GET: api/<controller>?type=x
|
||||
[HttpGet]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public Dictionary<string, string> Get()
|
||||
public Dictionary<string, object> Get(string type)
|
||||
{
|
||||
Dictionary<string, string> systeminfo = new Dictionary<string, string>();
|
||||
Dictionary<string, object> systeminfo = new Dictionary<string, object>();
|
||||
|
||||
systeminfo.Add("clrversion", Environment.Version.ToString());
|
||||
systeminfo.Add("osversion", Environment.OSVersion.ToString());
|
||||
systeminfo.Add("machinename", Environment.MachineName);
|
||||
systeminfo.Add("serverpath", _environment.ContentRootPath);
|
||||
systeminfo.Add("servertime", DateTime.UtcNow.ToString());
|
||||
systeminfo.Add("installationid", _configManager.GetInstallationId());
|
||||
|
||||
systeminfo.Add("runtime", _configManager.GetSetting("Runtime", "Server"));
|
||||
systeminfo.Add("rendermode", _configManager.GetSetting("RenderMode", "ServerPrerendered"));
|
||||
systeminfo.Add("detailederrors", _configManager.GetSetting("DetailedErrors", "false"));
|
||||
systeminfo.Add("logginglevel", _configManager.GetSetting("Logging:LogLevel:Default", "Information"));
|
||||
systeminfo.Add("notificationlevel", _configManager.GetSetting("Logging:LogLevel:Notify", "Error"));
|
||||
systeminfo.Add("swagger", _configManager.GetSetting("UseSwagger", "true"));
|
||||
systeminfo.Add("packageservice", _configManager.GetSetting("PackageService", "true"));
|
||||
switch (type.ToLower())
|
||||
{
|
||||
case "environment":
|
||||
systeminfo.Add("CLRVersion", Environment.Version.ToString());
|
||||
systeminfo.Add("OSVersion", Environment.OSVersion.ToString());
|
||||
systeminfo.Add("MachineName", Environment.MachineName);
|
||||
systeminfo.Add("WorkingSet", Environment.WorkingSet.ToString());
|
||||
systeminfo.Add("TickCount", Environment.TickCount64.ToString());
|
||||
systeminfo.Add("ContentRootPath", _environment.ContentRootPath);
|
||||
systeminfo.Add("WebRootPath", _environment.WebRootPath);
|
||||
systeminfo.Add("ServerTime", DateTime.UtcNow.ToString());
|
||||
var feature = HttpContext.Features.Get<IHttpConnectionFeature>();
|
||||
systeminfo.Add("IPAddress", feature?.LocalIpAddress?.ToString());
|
||||
break;
|
||||
case "configuration":
|
||||
systeminfo.Add("InstallationId", _configManager.GetInstallationId());
|
||||
systeminfo.Add("Runtime", _configManager.GetSetting("Runtime", "Server"));
|
||||
systeminfo.Add("RenderMode", _configManager.GetSetting("RenderMode", "ServerPrerendered"));
|
||||
systeminfo.Add("DetailedErrors", _configManager.GetSetting("DetailedErrors", "false"));
|
||||
systeminfo.Add("Logging:LogLevel:Default", _configManager.GetSetting("Logging:LogLevel:Default", "Information"));
|
||||
systeminfo.Add("Logging:LogLevel:Notify", _configManager.GetSetting("Logging:LogLevel:Notify", "Error"));
|
||||
systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true"));
|
||||
systeminfo.Add("PackageService", _configManager.GetSetting("PackageService", "true"));
|
||||
systeminfo.Add("Password:RequiredLength", _configManager.GetSetting("Password:RequiredLength", "6"));
|
||||
systeminfo.Add("Password:RequiredUniqueChars", _configManager.GetSetting("Password:RequiredUniqueChars", "1"));
|
||||
systeminfo.Add("Password:RequireDigit", _configManager.GetSetting("Password:RequireDigit", "true"));
|
||||
systeminfo.Add("Password:RequireUppercase", _configManager.GetSetting("Password:RequireUppercase", "true"));
|
||||
systeminfo.Add("Password:RequireLowercase", _configManager.GetSetting("Password:RequireLowercase", "true"));
|
||||
systeminfo.Add("Password:RequireNonAlphanumeric", _configManager.GetSetting("Password:RequireNonAlphanumeric", "true"));
|
||||
systeminfo.Add("Lockout:MaxFailedAccessAttempts", _configManager.GetSetting("Lockout:MaxFailedAccessAttempts", "5"));
|
||||
systeminfo.Add("Lockout:DefaultLockoutTimeSpan", _configManager.GetSetting("Lockout:DefaultLockoutTimeSpan", "00:05:00"));
|
||||
break;
|
||||
}
|
||||
|
||||
return systeminfo;
|
||||
}
|
||||
|
||||
|
||||
// GET: api/<controller>
|
||||
[HttpGet("{key}/{value}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public object Get(string key, object value)
|
||||
{
|
||||
return _configManager.GetSetting(key, value);
|
||||
}
|
||||
|
||||
// POST: api/<controller>
|
||||
[HttpPost]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public void Post([FromBody] Dictionary<string, string> settings)
|
||||
public void Post([FromBody] Dictionary<string, object> settings)
|
||||
{
|
||||
foreach(KeyValuePair<string, string> kvp in settings)
|
||||
foreach(KeyValuePair<string, object> kvp in settings)
|
||||
{
|
||||
switch (kvp.Key)
|
||||
{
|
||||
case "runtime":
|
||||
_configManager.AddOrUpdateSetting("Runtime", kvp.Value, false);
|
||||
break;
|
||||
case "rendermode":
|
||||
_configManager.AddOrUpdateSetting("RenderMode", kvp.Value, false);
|
||||
break;
|
||||
case "detailederrors":
|
||||
_configManager.AddOrUpdateSetting("DetailedErrors", kvp.Value, false);
|
||||
break;
|
||||
case "logginglevel":
|
||||
_configManager.AddOrUpdateSetting("Logging:LogLevel:Default", kvp.Value, false);
|
||||
break;
|
||||
case "notificationlevel":
|
||||
_configManager.AddOrUpdateSetting("Logging:LogLevel:Notify", kvp.Value, false);
|
||||
break;
|
||||
case "swagger":
|
||||
_configManager.AddOrUpdateSetting("UseSwagger", kvp.Value, false);
|
||||
break;
|
||||
case "packageservice":
|
||||
_configManager.AddOrUpdateSetting("PackageService", kvp.Value, false);
|
||||
break;
|
||||
}
|
||||
_configManager.AddOrUpdateSetting(kvp.Key, kvp.Value, false);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT: api/<controller>
|
||||
[HttpPut("{key}/{value}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public void Put(string key, object value)
|
||||
{
|
||||
_configManager.AddOrUpdateSetting(key, value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -397,7 +397,7 @@ namespace Oqtane.Controllers
|
|||
user = _users.GetUser(user.Username);
|
||||
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
|
||||
string url = HttpContext.Request.Scheme + "://" + _alias.Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string body = "Dear " + user.DisplayName + ",\n\nYou attempted 3 times unsuccessfully to log in to your account and it is now locked out. Please wait 10 minutes and then try again... or use the link below to reset your password:\n\n" + url +
|
||||
string body = "Dear " + user.DisplayName + ",\n\nYou attempted multiple times unsuccessfully to log in to your account and it is now locked out. Please wait a few minutes and then try again... or use the link below to reset your password:\n\n" + url +
|
||||
"\n\nPlease note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site." +
|
||||
"\n\nThank You!";
|
||||
var notification = new Notification(loginUser.SiteId, user, "User Lockout", body);
|
||||
|
|
|
@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
@ -129,26 +130,36 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCollection services)
|
||||
public static IServiceCollection ConfigureOqtaneIdentityOptions(this IServiceCollection services, IConfigurationRoot Configuration)
|
||||
{
|
||||
// default settings
|
||||
services.Configure<IdentityOptions>(options =>
|
||||
{
|
||||
// Password settings
|
||||
options.Password.RequireDigit = false;
|
||||
options.Password.RequireDigit = true;
|
||||
options.Password.RequiredLength = 6;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Password.RequireUppercase = false;
|
||||
options.Password.RequireLowercase = false;
|
||||
options.Password.RequireNonAlphanumeric = true;
|
||||
options.Password.RequireUppercase = true;
|
||||
options.Password.RequireLowercase = true;
|
||||
options.Password.RequiredUniqueChars = 1;
|
||||
|
||||
// Lockout settings
|
||||
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
|
||||
options.Lockout.MaxFailedAccessAttempts = 3;
|
||||
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
|
||||
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||
options.Lockout.AllowedForNewUsers = false;
|
||||
|
||||
// SignIn settings
|
||||
options.SignIn.RequireConfirmedEmail = true;
|
||||
options.SignIn.RequireConfirmedPhoneNumber = false;
|
||||
|
||||
// User settings
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
|
||||
});
|
||||
|
||||
// overrides defined in appsettings
|
||||
services.Configure<IdentityOptions>(Configuration);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace Oqtane.Infrastructure
|
|||
switch (action)
|
||||
{
|
||||
case "set":
|
||||
jsonObj[currentSection] = value;
|
||||
jsonObj[currentSection] = JToken.FromObject(value);
|
||||
break;
|
||||
case "remove":
|
||||
if (jsonObj.Property(currentSection) != null)
|
||||
|
|
|
@ -86,7 +86,7 @@ namespace Oqtane
|
|||
.AddDefaultTokenProviders()
|
||||
.AddClaimsPrincipalFactory<ClaimsPrincipalFactory<IdentityUser>>(); // role claims
|
||||
|
||||
services.ConfigureOqtaneIdentityOptions();
|
||||
services.ConfigureOqtaneIdentityOptions(Configuration);
|
||||
|
||||
services.AddAuthentication(Constants.AuthenticationScheme)
|
||||
.AddCookie(Constants.AuthenticationScheme);
|
||||
|
|
Loading…
Reference in New Issue
Block a user