6.1.4 Release
6.1.4 Release
This commit is contained in:
@ -53,8 +53,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ISyncService, SyncService>();
|
||||
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||
|
||||
// providers
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||
|
@ -56,7 +56,7 @@
|
||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
|
||||
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,7 +69,7 @@
|
||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
|
||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -82,7 +82,7 @@
|
||||
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
|
||||
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" required="@(_nextDate.HasValue)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -176,10 +176,18 @@
|
||||
{
|
||||
job.Interval = int.Parse(_interval);
|
||||
}
|
||||
job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay));
|
||||
job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay));
|
||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||
job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay));
|
||||
job.StartDate = _startDate.HasValue && _startTime.HasValue
|
||||
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
|
||||
job.EndDate = _endDate.HasValue && _endTime.HasValue
|
||||
? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
|
||||
job.NextExecution = _nextDate.HasValue && _nextTime.HasValue
|
||||
? LocalToUtc(_nextDate.GetValueOrDefault().Date.Add(_nextTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||
|
||||
try
|
||||
{
|
||||
@ -198,5 +206,4 @@
|
||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,9 +21,7 @@ else
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
@ -49,15 +47,11 @@ else
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<br /><br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ else
|
||||
<div class="row mb-3 align-items-center">
|
||||
<div class="col-sm-6">
|
||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
|
||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ms-1" />
|
||||
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||
|
@ -3,10 +3,10 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@ -115,7 +115,7 @@
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
_initialized = true;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@
|
||||
<Row>
|
||||
<div class="search-item mb-2">
|
||||
<h4 class="mb-1"><a href="@context.Url">@context.Title</a></h4>
|
||||
<p class="mb-0 text-muted">@((MarkupString)context.Snippet)</p>
|
||||
<p class="mb-0 text-body-secondary">@((MarkupString)context.Snippet)</p>
|
||||
</div>
|
||||
</Row>
|
||||
</Pager>
|
||||
|
@ -194,80 +194,125 @@
|
||||
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
</div>
|
||||
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SmtpEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="host" class="form-control" @bind="@_smtphost" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="port" class="form-control" @bind="@_smtpport" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
|
||||
<select id="smtpenabled" class="form-select" value="@_smtpenabled" @onchange="(e => SMTPEnabledChanged(e))">
|
||||
<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="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off"/>
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
||||
@if (_smtpenabled == "True")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmtpSender">Email Sender: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="sender" class="form-control" @bind="@_smtpsender" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="host" class="form-control" @bind="@_smtphost" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="port" class="form-control" @bind="@_smtpport" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="SmtpSSL">SSL Required: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpssl" class="form-select" @bind="@_smtpssl" >
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpauthentication" HelpText="Specify the SMTP authentication type" ResourceKey="SMTPAuthentication">Authentication: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="smtpauthentication" class="form-select" value="@_smtpauthentication" @onchange="(e => SMTPAuthenticationChanged(e))">
|
||||
<option value="Basic">@Localizer["Basic"]</option>
|
||||
<option value="OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
||||
<br /><br />
|
||||
@if (_smtpauthentication == "Basic")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified below." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpauthority" HelpText="The Authority Url for the SMTP provider" ResourceKey="SmtpAuthority">Authority Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="smtpauthority" class="form-control" @bind="@_smtpauthority" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpclientid" HelpText="The Client ID for the SMTP provider" ResourceKey="SmtpClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="smtpclientid" class="form-control" @bind="@_smtpclientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpclientsecret" HelpText="The Client Secret for the SMTP provider" ResourceKey="SmtpClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_smtpclientsecrettype" id="smtpclientsecret" class="form-control" @bind="@_smtpclientsecret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSmtpClientSecret">@_togglesmtpclientsecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="smtpscopes" HelpText="A list of Scopes for the SMTP provider (separated by commas)" ResourceKey="SmtpScopes">Scopes:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="smtpscopes" class="form-control" @bind="@_smtpscopes" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="sender" HelpText="Enter the email address which emails will be sent from. Please note that this email address usually needs to be authorized with the SMTP provider." ResourceKey="SmtpSender">Email Sender: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="sender" class="form-control" @bind="@_smtpsender" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
|
||||
<br /><br />
|
||||
}
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
|
||||
@ -455,16 +500,23 @@
|
||||
private string _headcontent = string.Empty;
|
||||
private string _bodycontent = string.Empty;
|
||||
|
||||
private string _smtpenabled = "False";
|
||||
private string _smtpauthentication = "Basic";
|
||||
private string _smtphost = string.Empty;
|
||||
private string _smtpport = string.Empty;
|
||||
private string _smtpssl = "False";
|
||||
private string _smtpssl = "True";
|
||||
private string _smtpusername = string.Empty;
|
||||
private string _smtppassword = string.Empty;
|
||||
private string _smtppasswordtype = "password";
|
||||
private string _togglesmtppassword = string.Empty;
|
||||
private string _smtpauthority = string.Empty;
|
||||
private string _smtpclientid = string.Empty;
|
||||
private string _smtpclientsecret = string.Empty;
|
||||
private string _smtpclientsecrettype = "password";
|
||||
private string _togglesmtpclientsecret = string.Empty;
|
||||
private string _smtpscopes = string.Empty;
|
||||
private string _smtpsender = string.Empty;
|
||||
private string _smtprelay = "False";
|
||||
private string _smtpenabled = "True";
|
||||
private int _retention = 30;
|
||||
|
||||
private string _pwaisenabled;
|
||||
@ -508,7 +560,7 @@
|
||||
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
|
||||
if (site != null)
|
||||
{
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
@ -556,15 +608,21 @@
|
||||
_bodycontent = site.BodyContent;
|
||||
|
||||
// SMTP
|
||||
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "False");
|
||||
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
|
||||
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
|
||||
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
|
||||
_smtpauthentication = SettingService.GetSetting(settings, "SMTPAuthentication", "Basic");
|
||||
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
|
||||
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
|
||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||
_smtpauthority = SettingService.GetSetting(settings, "SMTPAuthority", string.Empty);
|
||||
_smtpclientid = SettingService.GetSetting(settings, "SMTPClientId", string.Empty);
|
||||
_smtpclientsecret = SettingService.GetSetting(settings, "SMTPClientSecret", string.Empty);
|
||||
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||
_smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty);
|
||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
||||
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||
|
||||
// PWA
|
||||
@ -745,8 +803,13 @@
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthority", _smtpauthority, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientId", _smtpclientid, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
||||
@ -813,6 +876,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void SMTPAuthenticationChanged(ChangeEventArgs e)
|
||||
{
|
||||
_smtpauthentication = (string)e.Value;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void SMTPEnabledChanged(ChangeEventArgs e)
|
||||
{
|
||||
_smtpenabled = (string)e.Value;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ToggleSMTPPassword()
|
||||
{
|
||||
if (_smtppasswordtype == "password")
|
||||
{
|
||||
_smtppasswordtype = "text";
|
||||
_togglesmtppassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_smtppasswordtype = "password";
|
||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSmtpClientSecret()
|
||||
{
|
||||
if (_smtpclientsecrettype == "password")
|
||||
{
|
||||
_smtpclientsecrettype = "text";
|
||||
_togglesmtpclientsecret = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_smtpclientsecrettype = "password";
|
||||
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendEmail()
|
||||
{
|
||||
if (_smtphost != "" && _smtpport != "" && _smtpsender != "")
|
||||
@ -823,8 +926,13 @@
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthentication", _smtpauthentication, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPAuthority", _smtpauthority, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientId", _smtpclientid, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
await logger.LogInformation("Site SMTP Settings Saved");
|
||||
@ -845,20 +953,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSMTPPassword()
|
||||
{
|
||||
if (_smtppasswordtype == "password")
|
||||
{
|
||||
_smtppasswordtype = "text";
|
||||
_togglesmtppassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_smtppasswordtype = "password";
|
||||
_togglesmtppassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetAliases()
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
|
@ -15,8 +15,8 @@
|
||||
else
|
||||
{
|
||||
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
|
||||
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ms-1" />
|
||||
<button type="button" class="btn btn-secondary ms-1" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||
|
||||
<Pager Items="@_themes">
|
||||
<Header>
|
||||
|
@ -367,7 +367,6 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private string _username = string.Empty;
|
||||
@ -381,6 +380,7 @@
|
||||
private string _displayname = string.Empty;
|
||||
private FileManager _filemanager;
|
||||
private int _folderid = -1;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private string _timezoneid = string.Empty;
|
||||
private int _photofileid = -1;
|
||||
private File _photo = null;
|
||||
@ -404,7 +404,7 @@
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
|
||||
if (PageState.User != null)
|
||||
{
|
||||
@ -414,11 +414,6 @@
|
||||
_displayname = PageState.User.DisplayName;
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
|
||||
if (string.IsNullOrEmpty(_email))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
|
||||
}
|
||||
|
||||
// get user folder
|
||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||
if (folder != null)
|
||||
|
@ -28,6 +28,15 @@
|
||||
<input id="email" class="form-control" @bind="@_email" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||
<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="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
@ -120,6 +129,7 @@
|
||||
private bool _initialized = false;
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = "True";
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _notify = "True";
|
||||
@ -133,7 +143,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
_settings = new Dictionary<string, string>();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
@ -169,6 +179,7 @@
|
||||
user.Username = _username;
|
||||
user.Password = ""; // will be auto generated
|
||||
user.Email = _email;
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.PhotoFileId = null;
|
||||
|
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
||||
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Verified?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
@ -159,7 +159,7 @@
|
||||
}
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
||||
{
|
||||
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||
}
|
||||
<br /><br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
@ -204,7 +204,7 @@
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||
{
|
||||
|
@ -74,10 +74,19 @@ else
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireconfirmedemail" class="form-select" @bind="@_requireconfirmedemail">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor?</Label>
|
||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor Authentication?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
@ -490,6 +499,7 @@ else
|
||||
private string _allowregistration;
|
||||
private string _registerurl;
|
||||
private string _profileurl;
|
||||
private string _requireconfirmedemail;
|
||||
private string _twofactor;
|
||||
private string _cookiename;
|
||||
private string _cookieexpiration;
|
||||
@ -560,6 +570,7 @@ else
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
@ -685,6 +696,7 @@ else
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
|
@ -157,6 +157,9 @@
|
||||
[Parameter]
|
||||
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
||||
|
||||
[Parameter]
|
||||
public bool AnonymizeUploadFilenames { get; set; } = false; // optional - indicate if file names should be anonymized on upload - default false
|
||||
|
||||
[Parameter]
|
||||
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||
|
||||
@ -408,7 +411,7 @@
|
||||
}
|
||||
|
||||
// upload files
|
||||
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
|
||||
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, AnonymizeUploadFilenames, tokenSource.Token);
|
||||
|
||||
// reset progress indicators
|
||||
if (ShowProgress)
|
||||
|
@ -9,62 +9,26 @@
|
||||
|
||||
@if (_permissions != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="col">@Localizer["Role"]</th>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||
}
|
||||
</tr>
|
||||
@foreach (Role role in _roles)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>@role.Name</td>
|
||||
<th scope="col">@Localizer["Role"]</th>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<td style="text-align: center;">
|
||||
<TriStateCheckBox Value=@GetPermissionValue(permissionname, role.Name, -1) Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
|
||||
</td>
|
||||
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@if (_users.Count != 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">@Localizer["User"]</th>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (User user in _users)
|
||||
@foreach (Role role in _roles)
|
||||
{
|
||||
<tr>
|
||||
<td>@user.DisplayName (@user.Username)</td>
|
||||
<td>@role.Name</td>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<td style="text-align: center; width: 1px;">
|
||||
<TriStateCheckBox Value=@GetPermissionValue(permissionname, "", user.UserId) Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
||||
<td style="text-align: center;">
|
||||
<TriStateCheckBox Value="@GetPermissionValue(permissionname, role.Name, -1)" Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
@ -72,200 +36,242 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@if (_users.Count != 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">@Localizer["User"]</th>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (User user in _users)
|
||||
{
|
||||
<tr>
|
||||
<td>@user.DisplayName (@user.Username)</td>
|
||||
@foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
<td style="text-align: center; width: 1px;">
|
||||
<TriStateCheckBox Value="@GetPermissionValue(permissionname, "", user.UserId)" Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-11">
|
||||
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-11">
|
||||
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<string> _permissionnames;
|
||||
private List<Permission> _permissions;
|
||||
private List<Role> _roles;
|
||||
private List<User> _users = new List<User>();
|
||||
private AutoComplete _user;
|
||||
private string _message = string.Empty;
|
||||
private List<string> _permissionnames;
|
||||
private List<Permission> _permissions;
|
||||
private List<Role> _roles;
|
||||
private List<User> _users = new List<User>();
|
||||
private AutoComplete _user;
|
||||
private string _message = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string EntityName { get; set; }
|
||||
[Parameter]
|
||||
public string EntityName { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string PermissionNames { get; set; }
|
||||
[Parameter]
|
||||
public string PermissionNames { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||
[Parameter]
|
||||
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||
|
||||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; }
|
||||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||
}
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||
}
|
||||
|
||||
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
||||
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
_roles.RemoveAll(item => item.Name == RoleNames.Host);
|
||||
}
|
||||
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
||||
_roles.RemoveAll(item => item.Name == RoleNames.Host); // remove host role
|
||||
|
||||
// get permission names
|
||||
if (string.IsNullOrEmpty(PermissionNames))
|
||||
{
|
||||
_permissionnames = new List<string>();
|
||||
_permissionnames.Add(Shared.PermissionNames.View);
|
||||
_permissionnames.Add(Shared.PermissionNames.Edit);
|
||||
}
|
||||
else
|
||||
{
|
||||
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
}
|
||||
// get permission names
|
||||
if (string.IsNullOrEmpty(PermissionNames))
|
||||
{
|
||||
_permissionnames = new List<string>();
|
||||
_permissionnames.Add(Shared.PermissionNames.View);
|
||||
_permissionnames.Add(Shared.PermissionNames.Edit);
|
||||
}
|
||||
else
|
||||
{
|
||||
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
}
|
||||
|
||||
// initialize permissions
|
||||
_permissions = new List<Permission>();
|
||||
if (PermissionList != null && PermissionList.Any())
|
||||
{
|
||||
foreach (var permission in PermissionList)
|
||||
{
|
||||
_permissions.Add(permission);
|
||||
if (permission.UserId != null)
|
||||
{
|
||||
if (!_users.Any(item => item.UserId == permission.UserId.Value))
|
||||
{
|
||||
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string permissionname in _permissionnames)
|
||||
{
|
||||
// permission names can be in the form of "EntityName:PermissionName:Roles"
|
||||
if (permissionname.Contains(":"))
|
||||
{
|
||||
var segments = permissionname.Split(':');
|
||||
if (segments.Length == 3)
|
||||
{
|
||||
foreach (var role in segments[2].Split(';'))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
|
||||
}
|
||||
// ensure admin access
|
||||
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// initialize permissions
|
||||
_permissions = new List<Permission>();
|
||||
if (PermissionList != null && PermissionList.Any())
|
||||
{
|
||||
foreach (var permission in PermissionList)
|
||||
{
|
||||
_permissions.Add(permission);
|
||||
if (permission.UserId != null)
|
||||
{
|
||||
if (!_users.Any(item => item.UserId == permission.UserId.Value))
|
||||
{
|
||||
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string permissionname in _permissionnames)
|
||||
{
|
||||
// permission names can be in the form of "EntityName:PermissionName:Roles"
|
||||
if (permissionname.Contains(":"))
|
||||
{
|
||||
var segments = permissionname.Split(':');
|
||||
if (segments.Length == 3)
|
||||
{
|
||||
foreach (var role in segments[2].Split(';'))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
|
||||
}
|
||||
// ensure admin access
|
||||
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPermissionName(string permissionName)
|
||||
{
|
||||
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
|
||||
}
|
||||
private string GetPermissionName(string permissionName)
|
||||
{
|
||||
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
|
||||
}
|
||||
|
||||
private string GetEntityName(string permissionName)
|
||||
{
|
||||
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
|
||||
}
|
||||
private string GetEntityName(string permissionName)
|
||||
{
|
||||
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
|
||||
}
|
||||
|
||||
private string DisplayPermissionName(string permissionName)
|
||||
{
|
||||
var name = Localizer[GetPermissionName(permissionName)].ToString();
|
||||
name += " " + Localizer[GetEntityName(permissionName)].ToString();
|
||||
return name;
|
||||
}
|
||||
private string DisplayPermissionName(string permissionName)
|
||||
{
|
||||
var name = Localizer[GetPermissionName(permissionName)].ToString();
|
||||
name += " " + Localizer[GetEntityName(permissionName)].ToString();
|
||||
return name;
|
||||
}
|
||||
|
||||
private bool? GetPermissionValue(string permissionName, string roleName, int userId)
|
||||
{
|
||||
bool? isauthorized = null;
|
||||
if (roleName != "")
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||
if (permission != null)
|
||||
{
|
||||
isauthorized = permission.IsAuthorized;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||
if (permission != null)
|
||||
{
|
||||
isauthorized = permission.IsAuthorized;
|
||||
}
|
||||
}
|
||||
return isauthorized;
|
||||
}
|
||||
private bool? GetPermissionValue(string permissionName, string roleName, int userId)
|
||||
{
|
||||
bool? isauthorized = null;
|
||||
if (roleName != "")
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||
if (permission != null)
|
||||
{
|
||||
isauthorized = permission.IsAuthorized;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||
if (permission != null)
|
||||
{
|
||||
isauthorized = permission.IsAuthorized;
|
||||
}
|
||||
}
|
||||
return isauthorized;
|
||||
}
|
||||
|
||||
private bool GetPermissionDisabled(string permissionName, string roleName)
|
||||
{
|
||||
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool GetPermissionDisabled(string permissionName, string roleName)
|
||||
{
|
||||
var disabled = false;
|
||||
|
||||
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
||||
{
|
||||
if (roleName != "")
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||
if (permission != null)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||
if (permission != null)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
// administrator role permissions can only be changed by a host
|
||||
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
// API permissions can only be changed by an administrator
|
||||
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
return disabled;
|
||||
}
|
||||
|
||||
private bool? PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
||||
{
|
||||
if (roleName != "")
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||
if (permission != null)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
|
||||
// system roles cannot be denied - only custom roles can be denied
|
||||
var role = _roles.FirstOrDefault(item => item.Name == roleName);
|
||||
if (value != null && !value.Value && role.IsSystem)
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||
if (permission != null)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||
{
|
||||
@ -305,29 +311,20 @@
|
||||
|
||||
private void ValidatePermissions()
|
||||
{
|
||||
// remove deny all users, unauthenticated, and registered users
|
||||
var permissions = _permissions.Where(item => !item.IsAuthorized &&
|
||||
(item.RoleName == RoleNames.Everyone || item.RoleName == RoleNames.Unauthenticated || item.RoleName == RoleNames.Registered)).ToList();
|
||||
foreach (var permission in permissions)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
// remove deny administrators and host users
|
||||
permissions = _permissions.Where(item => !item.IsAuthorized &&
|
||||
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList();
|
||||
foreach (var permission in permissions)
|
||||
// remove host role permissions
|
||||
var permissions = _permissions.Where(item => item.RoleName == RoleNames.Host).ToList();
|
||||
foreach (var permission in permissions)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
// add host role permissions if administrator role is not assigned (to prevent lockout)
|
||||
foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
_permissions.Remove(permission);
|
||||
}
|
||||
foreach (var permissionname in _permissionnames)
|
||||
{
|
||||
// add administrators role if neither host or administrator is assigned
|
||||
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) &&
|
||||
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)))
|
||||
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) && item.RoleName == RoleNames.Admin))
|
||||
{
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true));
|
||||
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Host, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action<bool?> OnChange { get; set; }
|
||||
public Func<bool?, bool?> OnChange { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
@ -41,27 +41,35 @@
|
||||
break;
|
||||
}
|
||||
|
||||
_value = OnChange(_value);
|
||||
SetImage();
|
||||
OnChange(_value);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetImage()
|
||||
{
|
||||
switch (_value)
|
||||
if (!Disabled)
|
||||
{
|
||||
case true:
|
||||
_src = "images/checked.png";
|
||||
_title = Localizer["PermissionGranted"];
|
||||
break;
|
||||
case false:
|
||||
_src = "images/unchecked.png";
|
||||
_title = Localizer["PermissionDenied"];
|
||||
break;
|
||||
case null:
|
||||
_src = "images/null.png";
|
||||
_title = string.Empty;
|
||||
break;
|
||||
switch (_value)
|
||||
{
|
||||
case true:
|
||||
_src = "images/checked.png";
|
||||
_title = Localizer["PermissionGranted"];
|
||||
break;
|
||||
case false:
|
||||
_src = "images/unchecked.png";
|
||||
_title = Localizer["PermissionDenied"];
|
||||
break;
|
||||
case null:
|
||||
_src = "images/null.png";
|
||||
_title = string.Empty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_src = "images/disabled.png";
|
||||
_title = Localizer["PermissionDisabled"];
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
|
@ -500,33 +500,45 @@ namespace Oqtane.Modules
|
||||
};
|
||||
}
|
||||
|
||||
// date methods
|
||||
// date conversion methods
|
||||
public DateTime? UtcToLocal(DateTime? datetime)
|
||||
{
|
||||
TimeZoneInfo timezone = null;
|
||||
// Early return if input is null
|
||||
if (datetime == null)
|
||||
return null;
|
||||
|
||||
string timezoneId = null;
|
||||
|
||||
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
|
||||
{
|
||||
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId);
|
||||
timezoneId = PageState.User.TimeZoneId;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
|
||||
{
|
||||
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId);
|
||||
timezoneId = PageState.Site.TimeZoneId;
|
||||
}
|
||||
return Utilities.UtcAsLocalDateTime(datetime, timezone);
|
||||
|
||||
return Utilities.UtcAsLocalDateTime(datetime, timezoneId);
|
||||
}
|
||||
|
||||
public DateTime? LocalToUtc(DateTime? datetime)
|
||||
{
|
||||
TimeZoneInfo timezone = null;
|
||||
// Early return if input is null
|
||||
if (datetime == null)
|
||||
return null;
|
||||
|
||||
string timezoneId = null;
|
||||
|
||||
if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId))
|
||||
{
|
||||
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId);
|
||||
timezoneId = PageState.User.TimeZoneId;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId))
|
||||
{
|
||||
timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId);
|
||||
timezoneId = PageState.Site.TimeZoneId;
|
||||
}
|
||||
return Utilities.LocalDateAndTimeAsUtc(datetime, timezone);
|
||||
|
||||
return Utilities.LocalDateAndTimeAsUtc(datetime, timezoneId);
|
||||
}
|
||||
|
||||
// logging methods
|
||||
|
@ -4,7 +4,7 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.4</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -12,7 +12,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -22,10 +22,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -133,7 +133,7 @@
|
||||
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Error.Login.Fail" xml:space="preserve">
|
||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification.</value>
|
||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That New User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification Containing Further Instructions.</value>
|
||||
</data>
|
||||
<data name="Message.Required.UserInfo" xml:space="preserve">
|
||||
<value>Please Provide All Required Fields</value>
|
||||
|
@ -192,7 +192,7 @@
|
||||
<data name="Port.HelpText" xml:space="preserve">
|
||||
<value>Enter the port number for the SMTP server. Please note this field is required if you provide a host name.</value>
|
||||
</data>
|
||||
<data name="UseSsl.HelpText" xml:space="preserve">
|
||||
<data name="SmtpSSL.HelpText" xml:space="preserve">
|
||||
<value>Specify if SSL is required for your SMTP server</value>
|
||||
</data>
|
||||
<data name="SmtpUsername.HelpText" xml:space="preserve">
|
||||
@ -202,7 +202,7 @@
|
||||
<value>Enter the password for your SMTP account</value>
|
||||
</data>
|
||||
<data name="SmtpSender.HelpText" xml:space="preserve">
|
||||
<value>Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server.</value>
|
||||
<value>Enter the email address which emails will be sent from. Please note that this email address usually needs to be authorized with the SMTP server.</value>
|
||||
</data>
|
||||
<data name="EnablePWA.HelpText" xml:space="preserve">
|
||||
<value>Select whether you would like this site to be available as a Progressive Web Application (PWA)</value>
|
||||
@ -240,8 +240,8 @@
|
||||
<data name="Port.Text" xml:space="preserve">
|
||||
<value>Port: </value>
|
||||
</data>
|
||||
<data name="UseSsl.Text" xml:space="preserve">
|
||||
<value>SSL Enabled: </value>
|
||||
<data name="SmtpSSL.Text" xml:space="preserve">
|
||||
<value>SSL Required: </value>
|
||||
</data>
|
||||
<data name="SmtpUsername.Text" xml:space="preserve">
|
||||
<value>Username: </value>
|
||||
@ -372,10 +372,10 @@
|
||||
<data name="PageContent.Heading" xml:space="preserve">
|
||||
<value>Page Content</value>
|
||||
</data>
|
||||
<data name="SMTPEnabled.HelpText" xml:space="preserve">
|
||||
<data name="SmtpEnabled.HelpText" xml:space="preserve">
|
||||
<value>Specify if SMTP is enabled for this site</value>
|
||||
</data>
|
||||
<data name="SMTPEnabled.Text" xml:space="preserve">
|
||||
<data name="SmtpEnabled.Text" xml:space="preserve">
|
||||
<value>Enabled?</value>
|
||||
</data>
|
||||
<data name="Version.HelpText" xml:space="preserve">
|
||||
@ -453,4 +453,40 @@
|
||||
<data name="TimeZone.HelpText" xml:space="preserve">
|
||||
<value>The default time zone for the site</value>
|
||||
</data>
|
||||
<data name="Basic" xml:space="preserve">
|
||||
<value>Basic</value>
|
||||
</data>
|
||||
<data name="OAuth2" xml:space="preserve">
|
||||
<value>OAuth 2.0 (OAuth2)</value>
|
||||
</data>
|
||||
<data name="SmtpAuthentication.Text" xml:space="preserve">
|
||||
<value>Authentication:</value>
|
||||
</data>
|
||||
<data name="SmtpAuthentication.HelpText" xml:space="preserve">
|
||||
<value>Specify the SMTP authentication type</value>
|
||||
</data>
|
||||
<data name="SmtpClientID.Text" xml:space="preserve">
|
||||
<value>Client ID:</value>
|
||||
</data>
|
||||
<data name="SmtpClientID.HelpText" xml:space="preserve">
|
||||
<value>The Client ID for the SMTP provider</value>
|
||||
</data>
|
||||
<data name="SmtpClientSecret.Text" xml:space="preserve">
|
||||
<value>Client Secret:</value>
|
||||
</data>
|
||||
<data name="SmtpClientSecret.HelpText" xml:space="preserve">
|
||||
<value>The Client Secret for the SMTP provider</value>
|
||||
</data>
|
||||
<data name="SmtpScopes.Text" xml:space="preserve">
|
||||
<value>Scopes:</value>
|
||||
</data>
|
||||
<data name="SmtpScopes.HelpText" xml:space="preserve">
|
||||
<value>A list of Scopes for the SMTP provider (separated by commas)</value>
|
||||
</data>
|
||||
<data name="SmtpAuthority.Text" xml:space="preserve">
|
||||
<value>Authority Url:</value>
|
||||
</data>
|
||||
<data name="SmtpAuthority.HelpText" xml:space="preserve">
|
||||
<value>The Authority Url for the SMTP provider</value>
|
||||
</data>
|
||||
</root>
|
@ -147,9 +147,6 @@
|
||||
<data name="Message.User.NoLogIn" xml:space="preserve">
|
||||
<value>Current User Is Not Logged In</value>
|
||||
</data>
|
||||
<data name="Message.User.NoEmail" xml:space="preserve">
|
||||
<value>You Must Provide An Email Address For Your User Account</value>
|
||||
</data>
|
||||
<data name="Error.Profile.Load" xml:space="preserve">
|
||||
<value>Error Loading User Profile</value>
|
||||
</data>
|
||||
|
@ -162,4 +162,10 @@
|
||||
<data name="TimeZone.HelpText" xml:space="preserve">
|
||||
<value>The user's time zone</value>
|
||||
</data>
|
||||
<data name="Confirmed.Text" xml:space="preserve">
|
||||
<value>Verified?</value>
|
||||
</data>
|
||||
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the user's email is verified</value>
|
||||
</data>
|
||||
</root>
|
@ -217,7 +217,7 @@
|
||||
<value>The user's time zone</value>
|
||||
</data>
|
||||
<data name="Confirmed.Text" xml:space="preserve">
|
||||
<value>Confirmed?</value>
|
||||
<value>Verified?</value>
|
||||
</data>
|
||||
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the user's email is verified</value>
|
||||
|
@ -370,7 +370,13 @@
|
||||
<value>Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out.</value>
|
||||
</data>
|
||||
<data name="TwoFactor.Text" xml:space="preserve">
|
||||
<value>Two Factor?</value>
|
||||
<value>Two Factor Authentication?</value>
|
||||
</data>
|
||||
<data name="RequireConfirmedEmail.HelpText" xml:space="preserve">
|
||||
<value>Do you want to require registered users to verify their email address before they are allowed to log in?</value>
|
||||
</data>
|
||||
<data name="RequireConfirmedEmail.Text" xml:space="preserve">
|
||||
<value>Require Verified Email?</value>
|
||||
</data>
|
||||
<data name="Disabled" xml:space="preserve">
|
||||
<value>Disabled</value>
|
||||
@ -502,7 +508,7 @@
|
||||
<value>Info</value>
|
||||
</data>
|
||||
<data name="OAuth2" xml:space="preserve">
|
||||
<value>OAuth 2.0</value>
|
||||
<value>OAuth 2.0 (OAuth2)</value>
|
||||
</data>
|
||||
<data name="OIDC" xml:space="preserve">
|
||||
<value>OpenID Connect (OIDC)</value>
|
||||
|
@ -123,4 +123,7 @@
|
||||
<data name="PermissionDenied" xml:space="preserve">
|
||||
<value>Permission Denied</value>
|
||||
</data>
|
||||
<data name="PermissionDisabled" xml:space="preserve">
|
||||
<value>Permission Disabled</value>
|
||||
</data>
|
||||
</root>
|
120
Oqtane.Client/Resources/TimeZoneResources.resx
Normal file
120
Oqtane.Client/Resources/TimeZoneResources.resx
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
@ -256,7 +256,7 @@ namespace Oqtane.Services
|
||||
|
||||
Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPrivate);
|
||||
|
||||
Dictionary<string, string> MergeSettings(Dictionary<string, string> settings1, Dictionary<string, string> settings2);
|
||||
Dictionary<string, string> MergeSettings(Dictionary<string, string> baseSettings, Dictionary<string, string> overwriteSettings);
|
||||
|
||||
|
||||
[Obsolete("GetSettingAsync(int settingId) is deprecated. Use GetSettingAsync(string entityName, int settingId) instead.", false)]
|
||||
|
@ -1,11 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service to store and retrieve <see cref="TimeZone"/> entries
|
||||
/// Service to retrieve <see cref="TimeZone"/> entries
|
||||
/// </summary>
|
||||
public interface ITimeZoneService
|
||||
{
|
||||
@ -13,6 +12,6 @@ namespace Oqtane.Services
|
||||
/// Get the list of time zones
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<TimeZone>> GetTimeZonesAsync();
|
||||
List<TimeZone> GetTimeZones();
|
||||
}
|
||||
}
|
||||
|
@ -266,27 +266,25 @@ namespace Oqtane.Services
|
||||
return settings;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> MergeSettings(Dictionary<string, string> settings1, Dictionary<string, string> settings2)
|
||||
public Dictionary<string, string> MergeSettings(Dictionary<string, string> baseSettings, Dictionary<string, string> overwriteSettings)
|
||||
{
|
||||
if (settings1 == null)
|
||||
var settings = baseSettings != null ? new Dictionary<string, string>(baseSettings) : new Dictionary<string, string>();
|
||||
if (overwriteSettings != null)
|
||||
{
|
||||
settings1 = new Dictionary<string, string>();
|
||||
}
|
||||
if (settings2 != null)
|
||||
{
|
||||
foreach (var setting in settings2)
|
||||
foreach (var setting in overwriteSettings)
|
||||
{
|
||||
if (settings1.ContainsKey(setting.Key))
|
||||
if (settings.ContainsKey(setting.Key))
|
||||
{
|
||||
settings1[setting.Key] = setting.Value;
|
||||
settings[setting.Key] = setting.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
settings1.Add(setting.Key, setting.Value);
|
||||
settings.Add(setting.Key, setting.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return settings1;
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
[Obsolete("GetSettingAsync(int settingId) is deprecated. Use GetSettingAsync(string entityName, int settingId) instead.", false)]
|
||||
|
@ -1,22 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime.TimeZones;
|
||||
using NodaTime;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using NodaTime.Extensions;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class TimeZoneService : ServiceBase, ITimeZoneService
|
||||
public class TimeZoneService : ITimeZoneService
|
||||
{
|
||||
public TimeZoneService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
private readonly IStringLocalizer<TimeZoneResources> _TimeZoneLocalizer;
|
||||
|
||||
private string Apiurl => CreateApiUrl("TimeZone");
|
||||
|
||||
public async Task<List<TimeZone>> GetTimeZonesAsync()
|
||||
public TimeZoneService(IStringLocalizer<TimeZoneResources> TimeZoneLocalizer)
|
||||
{
|
||||
return await GetJsonAsync<List<TimeZone>>($"{Apiurl}");
|
||||
_TimeZoneLocalizer = TimeZoneLocalizer;
|
||||
}
|
||||
|
||||
public List<Models.TimeZone> GetTimeZones()
|
||||
{
|
||||
var timezones = new List<Models.TimeZone>();
|
||||
|
||||
foreach (var tz in DateTimeZoneProviders.Tzdb.GetAllZones()
|
||||
// only include timezones which have a country code defined or are US timezones
|
||||
.Where(item => !string.IsNullOrEmpty(TzdbDateTimeZoneSource.Default.ZoneLocations.FirstOrDefault(l => l.ZoneId == item.Id)?.CountryCode) || item.Id.ToLower().Contains("us/"))
|
||||
// order by UTC offset (ie. -11:00 to +14:00)
|
||||
.OrderBy(item => item.GetUtcOffset(Instant.FromDateTimeUtc(DateTime.UtcNow)).Ticks))
|
||||
{
|
||||
// get localized display name
|
||||
var displayname = _TimeZoneLocalizer[tz.Id].Value;
|
||||
if (displayname == tz.Id)
|
||||
{
|
||||
// use default "friendly" display format
|
||||
displayname = displayname.Replace("_", " ").Replace("/", " / ");
|
||||
}
|
||||
|
||||
// time zones can be excluded from the list by providing an empty translation in the localization file
|
||||
if (!string.IsNullOrEmpty(displayname))
|
||||
{
|
||||
// include offset prefix
|
||||
var offset = tz.GetUtcOffset(Instant.FromDateTimeUtc(DateTime.UtcNow)).Ticks;
|
||||
displayname = "(UTC" + (offset >= 0 ? "+" : "-") + new DateTime(Math.Abs(offset)).ToString("HH:mm") + ") " + displayname;
|
||||
|
||||
timezones.Add(new Models.TimeZone()
|
||||
{
|
||||
Id = tz.Id,
|
||||
DisplayName = displayname
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return timezones;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", "anonymous"),
|
||||
new Stylesheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
||||
new Stylesheet(ThemePath() + "Theme.css"),
|
||||
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||
};
|
||||
|
@ -20,6 +20,7 @@
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
// trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries
|
||||
// only include properties required by the ModuleActionsInteractive component
|
||||
_pageState = new PageState
|
||||
{
|
||||
Alias = PageState.Alias,
|
||||
|
@ -91,6 +91,7 @@
|
||||
}
|
||||
|
||||
// trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries
|
||||
// only include properties required by the ControlPanelInteractive component
|
||||
_pageState = new PageState
|
||||
{
|
||||
Alias = PageState.Alias,
|
||||
|
@ -353,7 +353,7 @@
|
||||
module.PageId = PageState.Page.PageId;
|
||||
module.ModuleDefinitionName = _moduleDefinitionName;
|
||||
module.AllPages = false;
|
||||
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
|
||||
module.PermissionList = GenerateDefaultPermissions(module.SiteId, module.ModuleDefinitionName);
|
||||
|
||||
module = await ModuleService.AddModuleAsync(module);
|
||||
newModuleId = module.ModuleId;
|
||||
@ -365,7 +365,7 @@
|
||||
module.SiteId = PageState.Page.SiteId;
|
||||
module.PageId = PageState.Page.PageId;
|
||||
module.AllPages = false;
|
||||
module.PermissionList = GenerateDefaultPermissions(module.SiteId);
|
||||
module.PermissionList = GenerateDefaultPermissions(module.SiteId, module.ModuleDefinitionName);
|
||||
|
||||
module = await ModuleService.AddModuleAsync(module);
|
||||
var moduleContent = await ModuleService.ExportModuleAsync(int.Parse(_moduleId), PageState.Page.PageId);
|
||||
@ -430,9 +430,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
private List<Permission> GenerateDefaultPermissions(int siteId)
|
||||
private List<Permission> GenerateDefaultPermissions(int siteId, string moduleDefinitionName)
|
||||
{
|
||||
var permissions = new List<Permission>();
|
||||
|
||||
// set module view permissions
|
||||
if (_visibility == "view")
|
||||
{
|
||||
// set module view permissions to page view permissions
|
||||
@ -443,8 +445,22 @@
|
||||
// set module view permissions to page edit permissions
|
||||
permissions = SetPermissions(permissions, siteId, PermissionNames.View, PermissionNames.Edit);
|
||||
}
|
||||
// set module edit permissions to page edit permissions
|
||||
permissions = SetPermissions(permissions, siteId, PermissionNames.Edit, PermissionNames.Edit);
|
||||
|
||||
// set remaining module permissions
|
||||
var permissionNames = PermissionNames.Edit;
|
||||
var moduleDefinition = _allModuleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == moduleDefinitionName);
|
||||
if (moduleDefinition != null && !string.IsNullOrEmpty(moduleDefinition.PermissionNames))
|
||||
{
|
||||
permissionNames = moduleDefinition.PermissionNames; // custom module permissions
|
||||
}
|
||||
foreach (var permission in permissionNames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (permission != PermissionNames.View)
|
||||
{
|
||||
// set module permissions to page edit permissions
|
||||
permissions = SetPermissions(permissions, siteId, permission, PermissionNames.Edit);
|
||||
}
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
@ -103,6 +103,9 @@
|
||||
{
|
||||
var cookieConsentSetting = SettingService.GetSetting(PageState.Site.Settings, "CookieConsent", string.Empty);
|
||||
_enabled = !string.IsNullOrEmpty(cookieConsentSetting);
|
||||
|
||||
if (!_enabled) return;
|
||||
|
||||
_optout = cookieConsentSetting == "optout";
|
||||
_actioned = await CookieConsentService.IsActionedAsync();
|
||||
|
||||
|
@ -16,8 +16,8 @@ namespace Oqtane.Themes.OqtaneTheme
|
||||
ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client",
|
||||
Resources = new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css", "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==", "anonymous"),
|
||||
// obtained from https://cdnjs.com/libraries/bootswatch
|
||||
new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.7/cyborg/bootstrap.min.css", "sha512-/LQFzDeXqysGQ/POl5YOEjgVZH1BmqDHvshhnFIChf50bMGQ470qhUrsecD9MRCUwzwqRoshwAbmA2oTW4I6Yg==", "anonymous"),
|
||||
new Stylesheet("~/Theme.css"),
|
||||
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||
}
|
||||
|
14
Oqtane.Client/TimeZoneResources.cs
Normal file
14
Oqtane.Client/TimeZoneResources.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Oqtane
|
||||
{
|
||||
/// <summary>
|
||||
/// Dummy class used to collect shared resource strings for this application
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is mostly used with IStringLocalizer and IHtmlLocalizer interfaces.
|
||||
/// The class must reside at the project root.
|
||||
/// </remarks>
|
||||
public class TimeZoneResources
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
@if (ComponentType != null && _visible)
|
||||
{
|
||||
<a id="@ModuleState.PageModuleId.ToString()"></a>
|
||||
<CascadingValue Value="@ModuleState">
|
||||
<CascadingValue Value="@ModuleState" IsFixed="true">
|
||||
@if (_useadminborder)
|
||||
{
|
||||
<div class="app-pane-admin-border">
|
||||
@ -31,6 +31,9 @@
|
||||
[Parameter]
|
||||
public Module ModuleState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string ContainerType { get; set; }
|
||||
|
||||
protected override bool ShouldRender()
|
||||
{
|
||||
return PageState?.RenderId == ModuleState?.RenderId;
|
||||
@ -44,6 +47,10 @@
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
string container = ModuleState.ContainerType;
|
||||
if (!string.IsNullOrEmpty(ContainerType))
|
||||
{
|
||||
container = ContainerType;
|
||||
}
|
||||
if (PageState.ModuleId != -1 && PageState.Route.Action != "" && ModuleState.UseAdminContainer)
|
||||
{
|
||||
container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer;
|
||||
|
@ -224,17 +224,17 @@ namespace Oqtane.UI
|
||||
|
||||
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt)
|
||||
{
|
||||
UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1);
|
||||
UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1, false);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask<bool> UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int chunksize, CancellationToken cancellationToken = default)
|
||||
public ValueTask<bool> UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int chunksize, bool anonymizeuploadfilenames, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _jsRuntime.InvokeAsync<bool>(
|
||||
"Oqtane.Interop.uploadFiles", cancellationToken,
|
||||
posturl, folder, id, antiforgerytoken, jwt, chunksize);
|
||||
posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -10,11 +10,11 @@
|
||||
@((MarkupString)_comment)
|
||||
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static)
|
||||
{
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" />
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@_pageState" SiteState="@SiteState" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@PageState" SiteState="@SiteState" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, _prerender)" />
|
||||
<RenderModeBoundary ModuleState="@ModuleState" PageState="@_pageState" SiteState="@SiteState" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(PageState.Site.Runtime, _prerender)" />
|
||||
}
|
||||
}
|
||||
@if (PageState.ModuleId == -1)
|
||||
@ -32,6 +32,7 @@
|
||||
|
||||
private bool _prerender;
|
||||
private string _comment;
|
||||
private PageState _pageState;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
@ -48,11 +49,12 @@
|
||||
}
|
||||
_comment += " -->";
|
||||
|
||||
_pageState = PageState.Clone();
|
||||
if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
// trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries
|
||||
// please note that this performance optimization results in the PageState.Pages property not being available for use in Interactive components
|
||||
PageState.Site.Pages = new List<Page>();
|
||||
// please note that this performance optimization results in the PageState.Pages property not being available for use in downstream Interactive components
|
||||
_pageState.Site.Pages = new List<Page>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,5 +37,34 @@ namespace Oqtane.UI
|
||||
{
|
||||
get { return Site?.Languages; }
|
||||
}
|
||||
|
||||
public PageState Clone()
|
||||
{
|
||||
return new PageState
|
||||
{
|
||||
Alias = Alias,
|
||||
Site = Site,
|
||||
Page = Page,
|
||||
Modules = Modules,
|
||||
User = User,
|
||||
Uri = Uri,
|
||||
Route = Route,
|
||||
QueryString = QueryString,
|
||||
UrlParameters = UrlParameters,
|
||||
ModuleId = ModuleId,
|
||||
Action = Action,
|
||||
EditMode = EditMode,
|
||||
LastSyncDate = LastSyncDate,
|
||||
RenderMode = RenderMode,
|
||||
Runtime = Runtime,
|
||||
VisitorId = VisitorId,
|
||||
RemoteIPAddress = RemoteIPAddress,
|
||||
ReturnUrl = ReturnUrl,
|
||||
IsInternalNavigation = IsInternalNavigation,
|
||||
RenderId = RenderId,
|
||||
Refresh = Refresh,
|
||||
AllowCookies = AllowCookies
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,9 @@ else
|
||||
[Parameter]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string ContainerType { get; set; }
|
||||
|
||||
RenderFragment DynamicComponent { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
@ -45,12 +48,6 @@ else
|
||||
{
|
||||
foreach (Module module in PageState.Modules)
|
||||
{
|
||||
// set renderid - this allows the framework to determine which components should be rendered when PageState changes
|
||||
if (module.RenderId != PageState.RenderId)
|
||||
{
|
||||
module.RenderId = PageState.RenderId;
|
||||
}
|
||||
|
||||
var pane = module.Pane;
|
||||
if (module.ModuleId == PageState.ModuleId && PageState.Action != Constants.DefaultAction)
|
||||
{
|
||||
@ -101,7 +98,7 @@ else
|
||||
|
||||
if (authorized)
|
||||
{
|
||||
CreateComponent(builder, module, module.PageModuleId);
|
||||
CreateComponent(builder, module);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,7 +109,7 @@ else
|
||||
// check if user is authorized to view module
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
|
||||
{
|
||||
CreateComponent(builder, module, -1);
|
||||
CreateComponent(builder, module);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,14 +118,12 @@ else
|
||||
};
|
||||
}
|
||||
|
||||
private void CreateComponent(RenderTreeBuilder builder, Module module, int key)
|
||||
private void CreateComponent(RenderTreeBuilder builder, Module module)
|
||||
{
|
||||
builder.OpenComponent(0, typeof(ContainerBuilder));
|
||||
builder.AddAttribute(1, "ModuleState", module);
|
||||
if (key != -1)
|
||||
{
|
||||
builder.SetKey(module.PageModuleId);
|
||||
}
|
||||
builder.AddAttribute(2, "ContainerType", ContainerType);
|
||||
builder.SetKey(module.PageModuleId);
|
||||
builder.CloseComponent();
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@
|
||||
@inject ILogService LoggingService
|
||||
@inherits ErrorBoundary
|
||||
|
||||
<CascadingValue Value="@PageState">
|
||||
<CascadingValue Value="@ModuleState">
|
||||
<CascadingValue Value="@PageState" IsFixed="true">
|
||||
<CascadingValue Value="@ModuleState" IsFixed="true">
|
||||
@if (CurrentException is null)
|
||||
{
|
||||
@if (ModuleType != null)
|
||||
|
@ -48,12 +48,18 @@
|
||||
|
||||
private bool _initialized = false;
|
||||
private bool _installed = false;
|
||||
private string _display = "display: none;";
|
||||
private string _display = "";
|
||||
|
||||
private PageState _pageState { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (PageState != null && PageState.RenderMode == RenderModes.Interactive && PageState.Site.Prerender)
|
||||
{
|
||||
// prevents flash on initial interactive page load when using prerendering
|
||||
_display = "display: none;";
|
||||
}
|
||||
|
||||
SiteState.AntiForgeryToken = AntiForgeryToken;
|
||||
SiteState.AuthorizationToken = AuthorizationToken;
|
||||
SiteState.Platform = Platform;
|
||||
@ -61,7 +67,7 @@
|
||||
|
||||
if (Runtime == Runtimes.Hybrid)
|
||||
{
|
||||
var installation = await InstallationService.IsInstalled();
|
||||
var installation = await InstallationService.IsInstalled();
|
||||
_installed = installation.Success;
|
||||
if (installation.Alias != null)
|
||||
{
|
||||
@ -73,8 +79,8 @@
|
||||
if (PageState != null)
|
||||
{
|
||||
_pageState = PageState;
|
||||
SiteState.Alias = PageState.Alias;
|
||||
SiteState.RemoteIPAddress = (PageState != null) ? PageState.RemoteIPAddress : "";
|
||||
SiteState.Alias = _pageState.Alias;
|
||||
SiteState.RemoteIPAddress = _pageState.RemoteIPAddress;
|
||||
_installed = true;
|
||||
}
|
||||
}
|
||||
@ -85,9 +91,7 @@
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// prevents flash on initial interactive page load
|
||||
_display = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@
|
||||
{
|
||||
if (PageState == null || PageState.Refresh)
|
||||
{
|
||||
await Refresh();
|
||||
await Refresh(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@
|
||||
{
|
||||
_absoluteUri = args.Location;
|
||||
_isInternalNavigation = true;
|
||||
await Refresh();
|
||||
await Refresh(true);
|
||||
}
|
||||
|
||||
Task IHandleAfterRender.OnAfterRenderAsync()
|
||||
@ -93,7 +93,7 @@
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
|
||||
private async Task Refresh()
|
||||
private async Task Refresh(bool locationChanged)
|
||||
{
|
||||
Site site = null;
|
||||
Page page = null;
|
||||
@ -103,6 +103,7 @@
|
||||
var refresh = false;
|
||||
var lastsyncdate = DateTime.MinValue;
|
||||
var visitorId = -1;
|
||||
var renderid = Guid.Empty;
|
||||
_error = "";
|
||||
|
||||
Route route = new Route(_absoluteUri, SiteState.Alias.Path);
|
||||
@ -288,11 +289,21 @@
|
||||
modules = PageState.Modules;
|
||||
}
|
||||
|
||||
// renderid allows the framework to determine which module components should be rendered on a page
|
||||
if (PageState == null || locationChanged)
|
||||
{
|
||||
renderid = Guid.NewGuid();
|
||||
}
|
||||
else
|
||||
{
|
||||
renderid = PageState.RenderId;
|
||||
}
|
||||
|
||||
// load additional metadata for current page
|
||||
page = ProcessPage(page, site, user, SiteState.Alias, action);
|
||||
|
||||
// load additional metadata for modules
|
||||
(page, modules) = ProcessModules(site, page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
|
||||
(page, modules) = ProcessModules(site, page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias, renderid);
|
||||
|
||||
//cookie consent
|
||||
var _allowCookies = PageState?.AllowCookies;
|
||||
@ -324,7 +335,7 @@
|
||||
RemoteIPAddress = SiteState.RemoteIPAddress,
|
||||
ReturnUrl = returnurl,
|
||||
IsInternalNavigation = _isInternalNavigation,
|
||||
RenderId = Guid.NewGuid(),
|
||||
RenderId = renderid,
|
||||
Refresh = false,
|
||||
AllowCookies = _allowCookies.GetValueOrDefault(true)
|
||||
};
|
||||
@ -447,7 +458,7 @@
|
||||
return page;
|
||||
}
|
||||
|
||||
private (Page Page, List<Module> Modules) ProcessModules(Site site, Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias)
|
||||
private (Page Page, List<Module> Modules) ProcessModules(Site site, Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias, Guid renderid)
|
||||
{
|
||||
var paneindex = new Dictionary<string, int>();
|
||||
|
||||
@ -592,6 +603,8 @@
|
||||
{
|
||||
module.ContainerType = defaultcontainertype;
|
||||
}
|
||||
|
||||
module.RenderId = renderid;
|
||||
}
|
||||
|
||||
foreach (Module module in modules.Where(item => item.PageId == page.PageId))
|
||||
|
@ -20,13 +20,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// force authenticated user to provide email address (email may be missing if using external login)
|
||||
if (PageState.User != null && PageState.User.IsAuthenticated && string.IsNullOrEmpty(PageState.User.Email) && PageState.Route.PagePath != "profile")
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "profile", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
|
||||
return;
|
||||
}
|
||||
|
||||
// set page title
|
||||
if (!string.IsNullOrEmpty(PageState.Page.Title))
|
||||
{
|
||||
|
@ -75,13 +75,9 @@ namespace Oqtane.Database.MySQL
|
||||
return dr;
|
||||
}
|
||||
|
||||
public override string RewriteName(string name, bool isQuery)
|
||||
public override string DelimitName(string name)
|
||||
{
|
||||
if (name.ToLower() == "rows" && isQuery)
|
||||
{
|
||||
name = $"`{name}`"; // escape reserved word in SQL query
|
||||
}
|
||||
return name;
|
||||
return $"`{name}`";
|
||||
}
|
||||
|
||||
public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.4</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MySql.Data" Version="9.3.0" />
|
||||
<PackageReference Include="MySql.Data" Version="9.4.0" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.3.efcore.9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.4</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.7" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -87,9 +87,9 @@ namespace Oqtane.Database.PostgreSQL
|
||||
return _rewriter.RewriteName(name);
|
||||
}
|
||||
|
||||
public override string RewriteName(string name, bool isQuery)
|
||||
public override string DelimitName(string name)
|
||||
{
|
||||
return _rewriter.RewriteName(name);
|
||||
return $"\"{name}\"";
|
||||
}
|
||||
|
||||
public override string RewriteValue(string value, string type)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.4</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -46,6 +46,11 @@ namespace Oqtane.Database.SqlServer
|
||||
}
|
||||
}
|
||||
|
||||
public override string DelimitName(string name)
|
||||
{
|
||||
return $"[{name}]";
|
||||
}
|
||||
|
||||
public override int ExecuteNonQuery(string connectionString, string query)
|
||||
{
|
||||
var conn = new SqlConnection(FormatConnectionString(connectionString));
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.4</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -84,6 +84,11 @@ namespace Oqtane.Database.Sqlite
|
||||
return dr;
|
||||
}
|
||||
|
||||
public override string DelimitName(string name)
|
||||
{
|
||||
return $"\"{name}\"";
|
||||
}
|
||||
|
||||
public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString)
|
||||
{
|
||||
return optionsBuilder.UseSqlite(connectionString)
|
||||
|
@ -6,7 +6,7 @@
|
||||
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.4</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -14,7 +14,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
@ -30,7 +30,7 @@
|
||||
<ApplicationId>com.oqtane.maui</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>6.1.3</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>6.1.4</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
@ -67,14 +67,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.61" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.61" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.61" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.7" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.90" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.90" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.90" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -75,6 +75,10 @@ app {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.app-moduleactions .dropdown-menu {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.app-moduleactions .dropdown-submenu {
|
||||
position: relative;
|
||||
}
|
||||
@ -235,18 +239,19 @@ app {
|
||||
.app-form-inline {
|
||||
display: inline;
|
||||
}
|
||||
.app-search{
|
||||
|
||||
.app-search {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.app-search input + button{
|
||||
.app-search input + button {
|
||||
background: none;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
.app-search input + button .oi{
|
||||
.app-search input + button .oi {
|
||||
top: 0;
|
||||
}
|
||||
.app-search-noinput {
|
||||
@ -271,3 +276,13 @@ app {
|
||||
.app-logo .navbar-brand {
|
||||
padding: 5px 20px 5px 20px;
|
||||
}
|
||||
|
||||
/* cookie consent */
|
||||
.gdpr-consent-bar .btn-show {
|
||||
bottom: -3px;
|
||||
left: 5px;
|
||||
}
|
||||
.gdpr-consent-bar .btn-hide {
|
||||
top: 0;
|
||||
right: 5px;
|
||||
}
|
||||
|
@ -311,7 +311,7 @@ Oqtane.Interop = {
|
||||
}
|
||||
return files;
|
||||
},
|
||||
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize) {
|
||||
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames) {
|
||||
var success = true;
|
||||
var fileinput = document.getElementById('FileInput_' + id);
|
||||
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
||||
@ -344,16 +344,22 @@ Oqtane.Interop = {
|
||||
const totalParts = Math.ceil(file.size / chunkSize);
|
||||
let partCount = 0;
|
||||
|
||||
let filename = file.name;
|
||||
if (anonymizeuploadfilenames) {
|
||||
filename = crypto.randomUUID() + '.' + filename.split('.').pop();
|
||||
}
|
||||
|
||||
const uploadPart = () => {
|
||||
const start = partCount * chunkSize;
|
||||
const end = Math.min(start + chunkSize, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let formdata = new FormData();
|
||||
formdata.append('__RequestVerificationToken', antiforgerytoken);
|
||||
formdata.append('folder', folder);
|
||||
formdata.append('formfile', chunk, file.name);
|
||||
formdata.append('formfile', chunk, filename);
|
||||
|
||||
var credentials = 'same-origin';
|
||||
var headers = new Headers();
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>6.1.3</version>
|
||||
<version>6.1.4</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>6.1.3</version>
|
||||
<version>6.1.4</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -11,8 +11,8 @@
|
||||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.1.3/Oqtane.Framework.6.1.3.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</releaseNotes>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.1.4/Oqtane.Framework.6.1.4.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Server</id>
|
||||
<version>6.1.3</version>
|
||||
<version>6.1.4</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Shared</id>
|
||||
<version>6.1.3</version>
|
||||
<version>6.1.4</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Updater</id>
|
||||
<version>6.1.3</version>
|
||||
<version>6.1.4</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,7 +12,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.3.Install.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.4.Install.zip" -Force
|
||||
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.3.Upgrade.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.4.Upgrade.zip" -Force
|
||||
|
@ -345,6 +345,7 @@
|
||||
DateTime expiry = DateTime.MinValue;
|
||||
if (visitorCookieValue != null && visitorCookieValue.Contains("|"))
|
||||
{
|
||||
// visitor cookies contain the visitor id and an expiry date separated by a pipe symbol
|
||||
var values = visitorCookieValue.Split('|');
|
||||
int.TryParse(values[0], out _visitorId);
|
||||
DateTime.TryParseExact(values[1], "M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out expiry);
|
||||
|
@ -444,9 +444,14 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
|
||||
// ensure filename is valid
|
||||
if (!formfile.FileName.IsPathOrFileValid() || !HasValidFileExtension(formfile.FileName))
|
||||
string fileName = formfile.FileName;
|
||||
if (Path.GetExtension(fileName).Contains(':'))
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
|
||||
fileName = fileName.Substring(0, fileName.LastIndexOf(':')); // remove invalid suffix from extension
|
||||
}
|
||||
if (!fileName.IsPathOrFileValid() || !HasValidFileExtension(fileName))
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", fileName);
|
||||
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||
}
|
||||
|
||||
@ -458,8 +463,8 @@ namespace Oqtane.Controllers
|
||||
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||
}
|
||||
|
||||
// create file name using header values
|
||||
string fileName = formfile.FileName + ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000");
|
||||
// create file name using header part values
|
||||
fileName += ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000");
|
||||
string folderPath = "";
|
||||
|
||||
try
|
||||
@ -532,13 +537,13 @@ namespace Oqtane.Controllers
|
||||
string parts = Path.GetExtension(filename)?.Replace(token, ""); // returns "001_999"
|
||||
int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1));
|
||||
|
||||
filename = Path.GetFileNameWithoutExtension(filename); // base filename
|
||||
filename = Path.GetFileNameWithoutExtension(filename); // base filename including original file extension
|
||||
string[] fileparts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts
|
||||
|
||||
// if all of the file parts exist (note that file parts can arrive out of order)
|
||||
if (fileparts.Length == totalparts && CanAccessFiles(fileparts))
|
||||
{
|
||||
// merge file parts into temp file (in case another user is trying to get the file)
|
||||
// merge file parts into temp file (in case another user is trying to read the file)
|
||||
bool success = true;
|
||||
using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
|
||||
{
|
||||
@ -559,25 +564,22 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
|
||||
// clean up file parts
|
||||
foreach (var file in Directory.GetFiles(folder, "*" + token + "*"))
|
||||
foreach (var file in fileparts)
|
||||
{
|
||||
if (fileparts.Contains(file))
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// unable to delete part - ignore
|
||||
}
|
||||
System.IO.File.Delete(file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// unable to delete part - ignore
|
||||
}
|
||||
}
|
||||
|
||||
// rename temp file
|
||||
if (success)
|
||||
{
|
||||
// remove file if it already exists (as well as any thumbnails which may exist)
|
||||
// remove existing file (as well as any thumbnails)
|
||||
foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*"))
|
||||
{
|
||||
if (Path.GetExtension(file) != ".tmp")
|
||||
|
@ -43,7 +43,6 @@ namespace Oqtane.Controllers
|
||||
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
private readonly string _visitorCookie;
|
||||
|
||||
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager,
|
||||
IOptions<CookieAuthenticationOptions> cookieOptions, IOptionsSnapshot<CookieAuthenticationOptions> cookieOptionsSnapshot, IOptionsMonitorCache<CookieAuthenticationOptions> cookieOptionsMonitorCache,
|
||||
@ -70,7 +69,6 @@ namespace Oqtane.Controllers
|
||||
_identityOptionsMonitorCache = identityOptionsMonitorCache;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
_visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
|
||||
}
|
||||
|
||||
// GET: api/<controller>
|
||||
@ -299,11 +297,8 @@ namespace Oqtane.Controllers
|
||||
authorized = User.IsInRole(RoleNames.Admin);
|
||||
if (!authorized)
|
||||
{
|
||||
// a visitor may have cookies disabled
|
||||
if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId))
|
||||
{
|
||||
authorized = (visitorId == entityId);
|
||||
}
|
||||
var visitorCookieName = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
|
||||
authorized = (entityId == GetVisitorCookieId(Request.Cookies[visitorCookieName]));
|
||||
}
|
||||
break;
|
||||
default: // custom entity
|
||||
@ -344,11 +339,8 @@ namespace Oqtane.Controllers
|
||||
case EntityNames.Visitor:
|
||||
if (!User.IsInRole(RoleNames.Admin))
|
||||
{
|
||||
filter = true;
|
||||
if (int.TryParse(Request.Cookies[_visitorCookie], out int visitorId))
|
||||
{
|
||||
filter = (visitorId != entityId);
|
||||
}
|
||||
var visitorCookieName = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
|
||||
filter = (entityId != GetVisitorCookieId(Request.Cookies[visitorCookieName]));
|
||||
}
|
||||
break;
|
||||
default: // custom entity
|
||||
@ -358,6 +350,13 @@ namespace Oqtane.Controllers
|
||||
return filter;
|
||||
}
|
||||
|
||||
private int GetVisitorCookieId(string visitorCookie)
|
||||
{
|
||||
// visitor cookies contain the visitor id and an expiry date separated by a pipe symbol
|
||||
visitorCookie = (visitorCookie.Contains("|")) ? visitorCookie.Split('|')[0] : visitorCookie;
|
||||
return (int.TryParse(visitorCookie, out int visitorId)) ? visitorId : -1;
|
||||
}
|
||||
|
||||
private void AddSyncEvent(string EntityName, int EntityId, int SettingId, string Action)
|
||||
{
|
||||
_syncManager.AddSyncEvent(_alias, EntityName + "Setting", SettingId, Action);
|
||||
|
@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class TimeZoneController : Controller
|
||||
{
|
||||
public TimeZoneController() {}
|
||||
|
||||
// GET: api/<controller>
|
||||
[HttpGet]
|
||||
public IEnumerable<Models.TimeZone> Get()
|
||||
{
|
||||
return TimeZoneInfo.GetSystemTimeZones()
|
||||
.Select(item => new Models.TimeZone
|
||||
{
|
||||
Id = item.Id,
|
||||
DisplayName = item.DisplayName
|
||||
})
|
||||
.OrderBy(item => item.DisplayName);
|
||||
}
|
||||
}
|
||||
}
|
@ -165,14 +165,13 @@ namespace Oqtane.Controllers
|
||||
bool allowregistration;
|
||||
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
|
||||
{
|
||||
user.EmailConfirmed = true;
|
||||
user.IsAuthenticated = true;
|
||||
user.IsAuthenticated = true; // admins can add any existing user to a site
|
||||
allowregistration = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.EmailConfirmed = false;
|
||||
user.IsAuthenticated = false;
|
||||
user.EmailConfirmed = false; // standard users cannot specify that their email is verified
|
||||
user.IsAuthenticated = false; // existing users can only be added to a site if they provide a valid username and password
|
||||
allowregistration = _sites.GetSite(user.SiteId).AllowRegistration;
|
||||
}
|
||||
|
||||
|
@ -51,11 +51,8 @@ namespace Oqtane.Controllers
|
||||
bool authorized = User.IsInRole(RoleNames.Admin);
|
||||
if (!authorized)
|
||||
{
|
||||
var visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
|
||||
if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId))
|
||||
{
|
||||
authorized = (visitorId == id);
|
||||
}
|
||||
var visitorCookieName = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
|
||||
authorized = (id == GetVisitorCookieId(Request.Cookies[visitorCookieName]));
|
||||
}
|
||||
|
||||
var visitor = _visitors.GetVisitor(id);
|
||||
@ -77,5 +74,12 @@ namespace Oqtane.Controllers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetVisitorCookieId(string visitorCookie)
|
||||
{
|
||||
// visitor cookies contain the visitor id and an expiry date separated by a pipe symbol
|
||||
visitorCookie = (visitorCookie.Contains("|")) ? visitorCookie.Split('|')[0] : visitorCookie;
|
||||
return (int.TryParse(visitorCookie, out int visitorId)) ? visitorId : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,12 +61,12 @@ namespace Oqtane.Databases
|
||||
|
||||
public abstract IDataReader ExecuteReader(string connectionString, string query);
|
||||
|
||||
public virtual string RewriteName(string name)
|
||||
public virtual string DelimitName(string name)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public virtual string RewriteName(string name, bool isQuery)
|
||||
public virtual string RewriteName(string name)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
@ -26,9 +26,9 @@ namespace Oqtane.Databases.Interfaces
|
||||
|
||||
public IDataReader ExecuteReader(string connectionString, string query);
|
||||
|
||||
public string RewriteName(string name);
|
||||
public string DelimitName(string name); // only used in conjunction with method using MigrationBuilder.Sql()
|
||||
|
||||
public string RewriteName(string name, bool isQuery);
|
||||
public string RewriteName(string name);
|
||||
|
||||
public string RewriteValue(string value, string type);
|
||||
|
||||
|
@ -228,11 +228,12 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
options.Lockout.AllowedForNewUsers = false;
|
||||
|
||||
// SignIn settings
|
||||
options.SignIn.RequireConfirmedEmail = true;
|
||||
options.SignIn.RequireConfirmedEmail = false;
|
||||
options.SignIn.RequireConfirmedAccount = false;
|
||||
options.SignIn.RequireConfirmedPhoneNumber = false;
|
||||
|
||||
// User settings
|
||||
options.User.RequireUniqueEmail = false; // changing to true will cause issues for legacy data
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
|
||||
});
|
||||
|
||||
|
@ -404,13 +404,13 @@ namespace Oqtane.Extensions
|
||||
else if (!string.IsNullOrEmpty(name)) // name claim provided
|
||||
{
|
||||
username = name.ToLower().Replace(" ", "") + DateTime.UtcNow.ToString("mmss");
|
||||
emailaddress = ""; // unknown - will need to be requested from user later
|
||||
emailaddress = username + "@unknown.com";
|
||||
displayname = name;
|
||||
}
|
||||
else // neither email nor name provided
|
||||
{
|
||||
username = Guid.NewGuid().ToString("N");
|
||||
emailaddress = ""; // unknown - will need to be requested from user later
|
||||
emailaddress = username + "@unknown.com";
|
||||
displayname = username;
|
||||
}
|
||||
|
||||
|
@ -738,7 +738,7 @@ namespace Oqtane.Infrastructure
|
||||
databases += "{ \"Name\": \"LocalDB\", \"ControlType\": \"Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer\" },";
|
||||
databases += "{ \"Name\": \"SQL Server\", \"ControlType\": \"Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer\" },";
|
||||
databases += "{ \"Name\": \"SQLite\", \"ControlType\": \"Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite\" },";
|
||||
databases += "{ \"Name\": \"MySQL\", \"ControlType\": \"Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.MySQL.SqlServerDatabase, Oqtane.Database.MySQL\" },";
|
||||
databases += "{ \"Name\": \"MySQL\", \"ControlType\": \"Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL\" },";
|
||||
databases += "{ \"Name\": \"PostgreSQL\", \"ControlType\": \"Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL\" }";
|
||||
databases += "]";
|
||||
_configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, databases, true);
|
||||
|
@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Threading.Tasks;
|
||||
using MailKit.Net.Smtp;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Identity.Client;
|
||||
using MimeKit;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Shared;
|
||||
using MailKit.Security;
|
||||
|
||||
namespace Oqtane.Infrastructure
|
||||
{
|
||||
@ -23,7 +26,7 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
|
||||
// job is executed for each tenant in installation
|
||||
public override string ExecuteJob(IServiceProvider provider)
|
||||
public async override Task<string> ExecuteJobAsync(IServiceProvider provider)
|
||||
{
|
||||
string log = "";
|
||||
|
||||
@ -44,124 +47,175 @@ namespace Oqtane.Infrastructure
|
||||
|
||||
if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True")
|
||||
{
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPHost", "") != "" &&
|
||||
settingRepository.GetSettingValue(settings, "SMTPPort", "") != "" &&
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "") != "")
|
||||
bool valid = true;
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
|
||||
{
|
||||
// construct SMTP Client
|
||||
var client = new SmtpClient()
|
||||
// basic
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "") == "")
|
||||
{
|
||||
DeliveryMethod = SmtpDeliveryMethod.Network,
|
||||
UseDefaultCredentials = false,
|
||||
Host = settingRepository.GetSettingValue(settings, "SMTPHost", ""),
|
||||
Port = int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")),
|
||||
EnableSsl = bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False"))
|
||||
};
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "")
|
||||
{
|
||||
client.Credentials = new NetworkCredential(settingRepository.GetSettingValue(settings, "SMTPUsername", ""), settingRepository.GetSettingValue(settings, "SMTPPassword", ""));
|
||||
log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "<br />";
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// iterate through undelivered notifications
|
||||
int sent = 0;
|
||||
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
|
||||
foreach (Notification notification in notifications)
|
||||
{
|
||||
// get sender and receiver information from user object if not provided
|
||||
if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
|
||||
{
|
||||
var user = userRepository.GetUser(notification.FromUserId.Value);
|
||||
if (user != null)
|
||||
{
|
||||
notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail;
|
||||
notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName;
|
||||
}
|
||||
}
|
||||
if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null)
|
||||
{
|
||||
var user = userRepository.GetUser(notification.ToUserId.Value);
|
||||
if (user != null)
|
||||
{
|
||||
notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail;
|
||||
notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
// validate recipient
|
||||
if (string.IsNullOrEmpty(notification.ToEmail) || !MailAddress.TryCreate(notification.ToEmail, out _))
|
||||
{
|
||||
log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}<br />";
|
||||
notification.IsDeleted = true;
|
||||
notificationRepository.UpdateNotification(notification);
|
||||
}
|
||||
else
|
||||
{
|
||||
MailMessage mailMessage = new MailMessage();
|
||||
|
||||
// sender
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(notification.FromDisplayName))
|
||||
{
|
||||
mailMessage.From = new MailAddress(notification.FromEmail, notification.FromDisplayName);
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.From = new MailAddress(notification.FromEmail);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.From = new MailAddress(settingRepository.GetSettingValue(settings, "SMTPSender", ""), (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name);
|
||||
}
|
||||
|
||||
// recipient
|
||||
if (!string.IsNullOrEmpty(notification.ToDisplayName))
|
||||
{
|
||||
mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName));
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.To.Add(new MailAddress(notification.ToEmail));
|
||||
}
|
||||
|
||||
// subject
|
||||
mailMessage.Subject = notification.Subject;
|
||||
|
||||
//body
|
||||
mailMessage.Body = notification.Body;
|
||||
if (!mailMessage.Body.Contains("<") || !mailMessage.Body.Contains(">"))
|
||||
{
|
||||
// plain text messages should convert line breaks to HTML tags to preserve formatting
|
||||
mailMessage.Body = mailMessage.Body.Replace("\n", "<br />");
|
||||
}
|
||||
|
||||
// encoding
|
||||
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
|
||||
mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
|
||||
mailMessage.IsBodyHtml = true;
|
||||
|
||||
// send mail
|
||||
try
|
||||
{
|
||||
client.Send(mailMessage);
|
||||
sent++;
|
||||
notification.IsDelivered = true;
|
||||
notification.DeliveredOn = DateTime.UtcNow;
|
||||
notificationRepository.UpdateNotification(notification);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// error
|
||||
log += $"NotificationId: {notification.NotificationId} - {ex.Message}<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
log += "Notifications Delivered: " + sent + "<br />";
|
||||
}
|
||||
else
|
||||
{
|
||||
log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "<br />";
|
||||
// oauth
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPAuthority", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPClientId", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPClientSecret", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPScopes", "") == "" ||
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "") == "")
|
||||
{
|
||||
log += "SMTP Not Configured Properly In Site Settings - Host, Port, Authority, Client ID, Client Secret, Scopes, And Sender Are All Required" + "<br />";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (valid)
|
||||
{
|
||||
// construct SMTP Client
|
||||
using var client = new SmtpClient();
|
||||
|
||||
await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""),
|
||||
int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")),
|
||||
bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) ? SecureSocketOptions.StartTls : SecureSocketOptions.None);
|
||||
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
|
||||
{
|
||||
// it is possible to use basic without any authentication (not recommended)
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "")
|
||||
{
|
||||
await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
|
||||
settingRepository.GetSettingValue(settings, "SMTPPassword", ""));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// oauth authentication
|
||||
var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(settingRepository.GetSettingValue(settings, "SMTPClientId", ""))
|
||||
.WithAuthority(settingRepository.GetSettingValue(settings, "SMTPAuthority", ""))
|
||||
.WithClientSecret(settingRepository.GetSettingValue(settings, "SMTPClientSecret", ""))
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
var result = await confidentialClientApplication.AcquireTokenForClient(settingRepository.GetSettingValue(settings, "SMTPScopes", "").Split(',')).ExecuteAsync();
|
||||
var oauth2 = new SaslMechanismOAuth2(settingRepository.GetSettingValue(settings, "SMTPSender", ""), result.AccessToken);
|
||||
await client.AuthenticateAsync(oauth2);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log += "SMTP Not Configured Properly In Site Settings - OAuth Token Could Not Be Retrieved From Authority - " + ex.Message + "<br />";
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid)
|
||||
{
|
||||
// iterate through undelivered notifications
|
||||
int sent = 0;
|
||||
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
|
||||
foreach (Notification notification in notifications)
|
||||
{
|
||||
// get sender and receiver information from user object if not provided
|
||||
if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
|
||||
{
|
||||
var user = userRepository.GetUser(notification.FromUserId.Value);
|
||||
if (user != null)
|
||||
{
|
||||
notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail;
|
||||
notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName;
|
||||
}
|
||||
}
|
||||
if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null)
|
||||
{
|
||||
var user = userRepository.GetUser(notification.ToUserId.Value);
|
||||
if (user != null)
|
||||
{
|
||||
notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail;
|
||||
notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
// validate recipient
|
||||
if (string.IsNullOrEmpty(notification.ToEmail) || !MailboxAddress.TryParse(notification.ToEmail, out _))
|
||||
{
|
||||
log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}<br />";
|
||||
notification.IsDeleted = true;
|
||||
notificationRepository.UpdateNotification(notification);
|
||||
}
|
||||
else
|
||||
{
|
||||
MimeMessage mailMessage = new MimeMessage();
|
||||
|
||||
// sender
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(notification.FromDisplayName))
|
||||
{
|
||||
mailMessage.From.Add(new MailboxAddress(notification.FromDisplayName, notification.FromEmail));
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.From.Add(new MailboxAddress("", notification.FromEmail));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.From.Add(new MailboxAddress((!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name,
|
||||
settingRepository.GetSettingValue(settings, "SMTPSender", "")));
|
||||
}
|
||||
|
||||
// recipient
|
||||
if (!string.IsNullOrEmpty(notification.ToDisplayName))
|
||||
{
|
||||
mailMessage.To.Add(new MailboxAddress(notification.ToDisplayName, notification.ToEmail));
|
||||
}
|
||||
else
|
||||
{
|
||||
mailMessage.To.Add(new MailboxAddress("", notification.ToEmail));
|
||||
}
|
||||
|
||||
// subject
|
||||
mailMessage.Subject = notification.Subject;
|
||||
|
||||
//body
|
||||
var bodyText = notification.Body;
|
||||
|
||||
if (!bodyText.Contains('<') || !bodyText.Contains('>'))
|
||||
{
|
||||
// plain text messages should convert line breaks to HTML tags to preserve formatting
|
||||
bodyText = bodyText.Replace("\n", "<br />");
|
||||
}
|
||||
|
||||
mailMessage.Body = new TextPart("html", System.Text.Encoding.UTF8)
|
||||
{
|
||||
Text = bodyText
|
||||
};
|
||||
|
||||
// send mail
|
||||
try
|
||||
{
|
||||
await client.SendAsync(mailMessage);
|
||||
sent++;
|
||||
notification.IsDelivered = true;
|
||||
notification.DeliveredOn = DateTime.UtcNow;
|
||||
notificationRepository.UpdateNotification(notification);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// error
|
||||
log += $"NotificationId: {notification.NotificationId} - {ex.Message}<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
await client.DisconnectAsync(true);
|
||||
log += "Notifications Delivered: " + sent + "<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -180,7 +180,7 @@ namespace Oqtane.Managers
|
||||
if (User != null)
|
||||
{
|
||||
string siteName = _sites.GetSite(user.SiteId).Name;
|
||||
if (!user.EmailConfirmed)
|
||||
if (!user.EmailConfirmed && bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireConfirmedEmail", "true")))
|
||||
{
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
@ -252,29 +252,32 @@ namespace Oqtane.Managers
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
}
|
||||
|
||||
if (user.EmailConfirmed)
|
||||
if (bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireConfirmedEmail", "true")))
|
||||
{
|
||||
if (!identityuser.EmailConfirmed)
|
||||
if (user.EmailConfirmed)
|
||||
{
|
||||
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
||||
if (!identityuser.EmailConfirmed)
|
||||
{
|
||||
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
||||
|
||||
string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password.";
|
||||
string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password.";
|
||||
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
identityuser.EmailConfirmed = false;
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
||||
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
identityuser.EmailConfirmed = false;
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
||||
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
|
||||
user = _users.UpdateUser(user);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||
@ -354,15 +357,14 @@ namespace Oqtane.Managers
|
||||
if (!user.IsDeleted)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false";
|
||||
var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired;
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
var twoFactorRequired = _settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired;
|
||||
if (twoFactorRequired)
|
||||
{
|
||||
var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email");
|
||||
user.TwoFactorCode = token;
|
||||
user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10);
|
||||
_users.UpdateUser(user);
|
||||
string siteName = _sites.GetSite(alias.SiteId).Name;
|
||||
string subject = _localizer["TwoFactorEmailSubject"];
|
||||
subject = subject.Replace("[SiteName]", siteName);
|
||||
string body = _localizer["TwoFactorEmailBody"].Value;
|
||||
@ -377,7 +379,7 @@ namespace Oqtane.Managers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await _identityUserManager.IsEmailConfirmedAsync(identityuser))
|
||||
if (!bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireConfirmedEmail", "true")) || await _identityUserManager.IsEmailConfirmedAsync(identityuser))
|
||||
{
|
||||
user = GetUser(identityuser.UserName, alias.SiteId);
|
||||
if (user != null)
|
||||
@ -400,13 +402,25 @@ namespace Oqtane.Managers
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User {Username} Is Not An Active Member Of Site {SiteId}", user.Username, alias.SiteId);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Denied - User {Username} Is Not An Active Member Of Site {SiteId}", user.Username, alias.SiteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Email Address Not Verified {Username}", user.Username);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Denied - User Email Address Not Verified For {Username}", user.Username);
|
||||
|
||||
// send verification email again
|
||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||
string subject = _localizer["VerificationEmailSubject"];
|
||||
subject = subject.Replace("[SiteName]", siteName);
|
||||
string body = _localizer["VerificationEmailBody"].Value;
|
||||
body = body.Replace("[UserDisplayName]", user.DisplayName);
|
||||
body = body.Replace("[URL]", url);
|
||||
body = body.Replace("[SiteName]", siteName);
|
||||
var notification = new Notification(alias.SiteId, user, subject, body);
|
||||
_notifications.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -538,8 +552,7 @@ namespace Oqtane.Managers
|
||||
if (user != null)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false";
|
||||
var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired;
|
||||
var twoFactorRequired = _settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired;
|
||||
if (twoFactorRequired && user.TwoFactorCode == token && DateTime.UtcNow < user.TwoFactorExpiry)
|
||||
{
|
||||
user.IsAuthenticated = true;
|
||||
|
@ -33,28 +33,28 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||
|
||||
protected string Schema { get; init; }
|
||||
|
||||
private string RewriteSqlEntityTableName(string name)
|
||||
private string AddSchema(string name)
|
||||
{
|
||||
if (Schema == null)
|
||||
if (string.IsNullOrEmpty(Schema))
|
||||
{
|
||||
return RewriteName(name);
|
||||
return name;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{Schema}.{RewriteName(name)}";
|
||||
return $"{Schema}.{name}";
|
||||
}
|
||||
}
|
||||
|
||||
private string DelimitName(string name)
|
||||
{
|
||||
return ActiveDatabase.DelimitName(name);
|
||||
}
|
||||
|
||||
private string RewriteName(string name)
|
||||
{
|
||||
return ActiveDatabase.RewriteName(name);
|
||||
}
|
||||
|
||||
private string RewriteName(string name, bool isQuery)
|
||||
{
|
||||
return ActiveDatabase.RewriteName(name, isQuery);
|
||||
}
|
||||
|
||||
private string RewriteValue(string value, string type)
|
||||
{
|
||||
return ActiveDatabase.RewriteValue(value, type);
|
||||
@ -468,9 +468,10 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||
|
||||
public void DeleteFromTable(string condition = "")
|
||||
{
|
||||
var deleteSql = $"DELETE FROM {RewriteSqlEntityTableName(EntityTableName)} ";
|
||||
var deleteSql = $"DELETE FROM {AddSchema(DelimitName(RewriteName(EntityTableName)))} ";
|
||||
if(!string.IsNullOrEmpty(condition))
|
||||
{
|
||||
// note that condition values must be created using RewriteName(), DelimitName(), RewriteValue() if targeting multiple database platforms
|
||||
deleteSql += $"WHERE {condition}";
|
||||
}
|
||||
_migrationBuilder.Sql(deleteSql);
|
||||
@ -488,9 +489,10 @@ namespace Oqtane.Migrations.EntityBuilders
|
||||
|
||||
public void UpdateColumn(string columnName, string value, string type, string condition)
|
||||
{
|
||||
var updateSql = $"UPDATE {RewriteSqlEntityTableName(EntityTableName)} SET {RewriteName(columnName, true)} = {RewriteValue(value, type)} ";
|
||||
var updateSql = $"UPDATE {AddSchema(DelimitName(RewriteName(EntityTableName)))} SET {DelimitName(RewriteName(columnName))} = {RewriteValue(value, type)} ";
|
||||
if (!string.IsNullOrEmpty(condition))
|
||||
{
|
||||
// note that condition values must be created using RewriteName(), DelimitName(), RewriteValue() if targeting multiple database platforms
|
||||
updateSql += $"WHERE {condition}";
|
||||
}
|
||||
_migrationBuilder.Sql(updateSql);
|
||||
|
@ -12,14 +12,19 @@ namespace Oqtane.Migrations
|
||||
|
||||
protected IDatabase ActiveDatabase { get; }
|
||||
|
||||
protected string RewriteName(string name)
|
||||
protected string DelimitName(string name)
|
||||
{
|
||||
return ActiveDatabase.RewriteName(name, false);
|
||||
return ActiveDatabase.DelimitName(name);
|
||||
}
|
||||
|
||||
protected string RewriteName(string name, bool isQuery)
|
||||
protected string RewriteName(string name)
|
||||
{
|
||||
return ActiveDatabase.RewriteName(name, isQuery);
|
||||
return ActiveDatabase.RewriteName(name);
|
||||
}
|
||||
|
||||
protected string RewriteValue(string value, string type)
|
||||
{
|
||||
return ActiveDatabase.RewriteValue(value, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace Oqtane.Migrations.Tenant
|
||||
notificationEntityBuilder.AddDateTimeColumn("SendOn", true);
|
||||
|
||||
//Update new Column
|
||||
notificationEntityBuilder.UpdateColumn("SendOn", $"{ActiveDatabase.RewriteName("CreatedOn")}", $"{ActiveDatabase.RewriteName("SendOn")} IS NULL");
|
||||
notificationEntityBuilder.UpdateColumn("SendOn", $"{RewriteName("CreatedOn")}", $"{DelimitName(RewriteName("SendOn"))} IS NULL");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
|
@ -18,8 +18,8 @@ namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
///Update Icon Field in Page
|
||||
var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
var updateSql = ActiveDatabase.ConcatenateSql("'oi oi-'", $"{ActiveDatabase.RewriteName("Icon")}");
|
||||
pageEntityBuilder.UpdateColumn("Icon", updateSql, $"{ActiveDatabase.RewriteName("Icon")} <> ''" );
|
||||
var updateSql = ActiveDatabase.ConcatenateSql("'oi oi-'", $"{DelimitName(RewriteName("Icon"))}");
|
||||
pageEntityBuilder.UpdateColumn("Icon", updateSql, $"{DelimitName(RewriteName("Icon"))} <> ''" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,18 +20,18 @@ namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
//Update DefaultContainerType In Site
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
siteEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'", "DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'");
|
||||
siteEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'", "DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client'");
|
||||
siteEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'", $"{DelimitName(RewriteName("DefaultContainerType"))} = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'");
|
||||
siteEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'", $"{DelimitName(RewriteName("DefaultContainerType"))} = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client'");
|
||||
|
||||
//Update DefaultContainerType in Page
|
||||
var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
pageEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'", "DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'");
|
||||
pageEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'", "DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client'");
|
||||
pageEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'", $"{DelimitName(RewriteName("DefaultContainerType"))} = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'");
|
||||
pageEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'", $"{DelimitName(RewriteName("DefaultContainerType"))} = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client'");
|
||||
|
||||
//Update ContainerType in PageModule
|
||||
var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
pageModuleEntityBuilder.UpdateColumn("ContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'", "ContainerType = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'");
|
||||
pageModuleEntityBuilder.UpdateColumn("ContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'", "ContainerType = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client'");
|
||||
pageModuleEntityBuilder.UpdateColumn("ContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'", $"{DelimitName(RewriteName("ContainerType"))} = 'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'");
|
||||
pageModuleEntityBuilder.UpdateColumn("ContainerType", "'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'", $"{DelimitName(RewriteName("ContainerType"))} = 'Oqtane.Themes.OqtaneTheme.NoTitle, Oqtane.Client'");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,22 +29,22 @@ namespace Oqtane.Migrations.Tenant
|
||||
siteEntityBuilder.DropColumn("DefaultLayoutType");
|
||||
|
||||
//Update DefaultContainerType In Site
|
||||
siteEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", "DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'");
|
||||
siteEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", "DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'");
|
||||
siteEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", $"{DelimitName(RewriteName("DefaultContainerType"))} = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'");
|
||||
siteEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", $"{DelimitName(RewriteName("DefaultContainerType"))} = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'");
|
||||
|
||||
//Drop Column from Page Table
|
||||
var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
pageEntityBuilder.DropColumn("LayoutType");
|
||||
|
||||
//Update DefaultContainerType in Page
|
||||
pageEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", "DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'");
|
||||
pageEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", "DefaultContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'");
|
||||
pageEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", $"{DelimitName(RewriteName("DefaultContainerType"))} = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'");
|
||||
pageEntityBuilder.UpdateColumn("DefaultContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", $"{DelimitName(RewriteName("DefaultContainerType"))} = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'");
|
||||
|
||||
|
||||
//Update ContainerType in PageModule
|
||||
var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
pageModuleEntityBuilder.UpdateColumn("ContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", "ContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'");
|
||||
pageModuleEntityBuilder.UpdateColumn("ContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", "ContainerType = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'");
|
||||
pageModuleEntityBuilder.UpdateColumn("ContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", $"{DelimitName(RewriteName("ContainerType"))} = 'Oqtane.Themes.OqtaneTheme.DefaultTitle, Oqtane.Client'");
|
||||
pageModuleEntityBuilder.UpdateColumn("ContainerType", "'Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client'", $"{DelimitName(RewriteName("ContainerType"))} = 'Oqtane.Themes.OqtaneTheme.DefaultNoTitle, Oqtane.Client'");
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace Oqtane.Migrations.Tenant
|
||||
var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
folderEntityBuilder.AddIntegerColumn("Capacity", true);
|
||||
folderEntityBuilder.UpdateColumn("Capacity", "0");
|
||||
folderEntityBuilder.UpdateColumn("Capacity", Constants.UserFolderCapacity.ToString(), $"{ActiveDatabase.RewriteName("Name")} = 'My Folder'");
|
||||
folderEntityBuilder.UpdateColumn("Capacity", Constants.UserFolderCapacity.ToString(), $"{DelimitName(RewriteName("Name"))} = 'My Folder'");
|
||||
folderEntityBuilder.AddStringColumn("ImageSizes", 512, true, true);
|
||||
|
||||
var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
|
@ -18,13 +18,13 @@ namespace Oqtane.Migrations.Tenant
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var settingEntityBuilder = new SettingEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
settingEntityBuilder.UpdateColumn("IsPublic", "1", "bool", $"{RewriteName("SettingName")} NOT LIKE 'SMTP%'");
|
||||
settingEntityBuilder.UpdateColumn("IsPublic", "1", "bool", $"{DelimitName(RewriteName("SettingName"))} NOT LIKE 'SMTP%'");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var settingEntityBuilder = new SettingEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
settingEntityBuilder.UpdateColumn("IsPublic", "0", "bool", $"{RewriteName("SettingName")} NOT LIKE 'SMTP%'");
|
||||
settingEntityBuilder.UpdateColumn("IsPublic", "0", "bool", $"{DelimitName(RewriteName("SettingName"))} NOT LIKE 'SMTP%'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace Oqtane.Migrations.Tenant
|
||||
var settingEntityBuilder = new SettingEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
settingEntityBuilder.AddBooleanColumn("IsPrivate", true);
|
||||
settingEntityBuilder.UpdateColumn("IsPrivate", "0", "bool", "");
|
||||
settingEntityBuilder.UpdateColumn("IsPrivate", "1", "bool", $"{RewriteName("EntityName")} = 'Site' AND { RewriteName("SettingName")} LIKE 'SMTP%'");
|
||||
settingEntityBuilder.UpdateColumn("IsPrivate", "1", "bool", $"{DelimitName(RewriteName("EntityName"))} = 'Site' AND { DelimitName(RewriteName("SettingName"))} LIKE 'SMTP%'");
|
||||
settingEntityBuilder.DropColumn("IsPublic");
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
[DbContext(typeof(TenantDBContext))]
|
||||
[Migration("Tenant.06.01.04.01")]
|
||||
public class RemoveUniqueEmailIndex : MultiDatabaseMigration
|
||||
{
|
||||
public RemoveUniqueEmailIndex(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// framework uses RequireUniqueEmail = False in .NET Identity configuration
|
||||
var aspNetUsersEntityBuilder = new AspNetUsersEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
aspNetUsersEntityBuilder.DropIndex("EmailIndex");
|
||||
aspNetUsersEntityBuilder.AddIndex("EmailIndex", "NormalizedEmail", false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
32
Oqtane.Server/Migrations/Tenant/06010402_ResetTimeZone.cs
Normal file
32
Oqtane.Server/Migrations/Tenant/06010402_ResetTimeZone.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
[DbContext(typeof(TenantDBContext))]
|
||||
[Migration("Tenant.06.01.04.02")]
|
||||
public class ResetTimeZone : MultiDatabaseMigration
|
||||
{
|
||||
public ResetTimeZone(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// resetting value as framework now uses IANA ID consistently for time zones
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
siteEntityBuilder.UpdateColumn("TimeZoneId", "''");
|
||||
|
||||
var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
userEntityBuilder.UpdateColumn("TimeZoneId", "''");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.4</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -11,7 +11,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -34,21 +34,22 @@
|
||||
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.7" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.11" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.8" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
|
||||
<PackageReference Include="MailKit" Version="4.13.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
|
||||
|
@ -16,5 +16,6 @@ namespace Oqtane.Repository
|
||||
void DeleteSetting(string entityName, int settingId);
|
||||
void DeleteSettings(string entityName, int entityId);
|
||||
string GetSettingValue(IEnumerable<Setting> settings, string settingName, string defaultValue);
|
||||
string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules.Admin.Users;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
@ -71,6 +73,19 @@ namespace Oqtane.Repository
|
||||
public void DeleteRole(int roleId)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
|
||||
// remove userroles for role
|
||||
foreach (var userrole in db.UserRole.Where(item => item.RoleId == roleId))
|
||||
{
|
||||
db.UserRole.Remove(userrole);
|
||||
}
|
||||
|
||||
// remove permissions for role
|
||||
foreach (var permission in db.Permission.Where(item => item.RoleId == roleId))
|
||||
{
|
||||
db.Permission.Remove(permission);
|
||||
}
|
||||
|
||||
Role role = db.Role.Find(roleId);
|
||||
db.Role.Remove(role);
|
||||
db.SaveChanges();
|
||||
|
@ -180,6 +180,19 @@ namespace Oqtane.Repository
|
||||
}
|
||||
}
|
||||
|
||||
public string GetSettingValue(string entityName, int entityId, string settingName, string defaultValue)
|
||||
{
|
||||
var setting = GetSetting(entityName, entityId, settingName);
|
||||
if (setting != null)
|
||||
{
|
||||
return setting.SettingValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMaster(string EntityName)
|
||||
{
|
||||
return (EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Host);
|
||||
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules.Admin.Users;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
@ -131,6 +132,13 @@ namespace Oqtane.Repository
|
||||
public void DeleteUser(int userId)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
|
||||
// remove permissions for user
|
||||
foreach (var permission in db.Permission.Where(item => item.UserId == userId))
|
||||
{
|
||||
db.Permission.Remove(permission);
|
||||
}
|
||||
|
||||
var user = db.User.Find(userId);
|
||||
db.User.Remove(user);
|
||||
db.SaveChanges();
|
||||
|
@ -13,11 +13,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.7" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -19,10 +19,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -17,9 +17,9 @@ namespace [Owner].Theme.[Theme]
|
||||
Resources = new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", CrossOrigin = "anonymous" },
|
||||
new StyleSheet(Constants.BootstrapStylesheetUrl, Constants.BootstrapStylesheetIntegrity, "anonymous"),
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
|
||||
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js", Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==", CrossOrigin = "anonymous" }
|
||||
new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
|
||||
|
||||
}
|
||||
};
|
||||
|
@ -13,9 +13,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -75,6 +75,10 @@ app {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.app-moduleactions .dropdown-menu {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.app-moduleactions .dropdown-submenu {
|
||||
position: relative;
|
||||
}
|
||||
@ -274,11 +278,11 @@ app {
|
||||
}
|
||||
|
||||
/* cookie consent */
|
||||
.gdpr-consent-bar .btn-show{
|
||||
.gdpr-consent-bar .btn-show {
|
||||
bottom: -3px;
|
||||
left: 5px;
|
||||
}
|
||||
.gdpr-consent-bar .btn-hide{
|
||||
.gdpr-consent-bar .btn-hide {
|
||||
top: 0;
|
||||
right: 5px;
|
||||
}
|
BIN
Oqtane.Server/wwwroot/images/disabled.png
Normal file
BIN
Oqtane.Server/wwwroot/images/disabled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 875 B |
@ -311,7 +311,7 @@ Oqtane.Interop = {
|
||||
}
|
||||
return files;
|
||||
},
|
||||
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize) {
|
||||
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames) {
|
||||
var success = true;
|
||||
var fileinput = document.getElementById('FileInput_' + id);
|
||||
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
||||
@ -344,16 +344,22 @@ Oqtane.Interop = {
|
||||
const totalParts = Math.ceil(file.size / chunkSize);
|
||||
let partCount = 0;
|
||||
|
||||
let filename = file.name;
|
||||
if (anonymizeuploadfilenames) {
|
||||
filename = crypto.randomUUID() + '.' + filename.split('.').pop();
|
||||
}
|
||||
|
||||
const uploadPart = () => {
|
||||
const start = partCount * chunkSize;
|
||||
const end = Math.min(start + chunkSize, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let formdata = new FormData();
|
||||
formdata.append('__RequestVerificationToken', antiforgerytoken);
|
||||
formdata.append('folder', folder);
|
||||
formdata.append('formfile', chunk, file.name);
|
||||
formdata.append('formfile', chunk, filename);
|
||||
|
||||
var credentials = 'same-origin';
|
||||
var headers = new Headers();
|
||||
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>6.1.3</Version>
|
||||
<Version>6.1.4</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -11,7 +11,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.4</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -19,11 +19,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.7" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.5" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user