Merge pull request #67 from ONLYOFFICE/feature/Wizard

Feature/wizard
This commit is contained in:
Alexey Safronov 2020-08-12 17:33:18 +03:00 committed by GitHub
commit acf5cff2f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 2686 additions and 12402 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@
/products/ASC.People/Data/
Data/
Logs/
**/.DS_Store

14
build/start/restart.bat Normal file
View File

@ -0,0 +1,14 @@
PUSHD %~dp0..
call runasadmin.bat "%~dpnx0"
if %errorlevel% == 0 (
for /R "run\" %%f in (*.bat) do (
call nssm stop Onlyoffice%%~nf
)
for /R "run\" %%f in (*.bat) do (
call nssm start Onlyoffice%%~nf
)
call iisreset
)

View File

@ -12,6 +12,7 @@ using ASC.Security.Cryptography;
using ASC.Web.Studio.Core;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -33,7 +34,8 @@ namespace ASC.Api.Core.Auth
TenantManager tenantManager,
UserManager userManager,
AuthManager authManager,
AuthContext authContext) :
AuthContext authContext,
IServiceProvider serviceProvider) :
base(options, logger, encoder, clock)
{
SecurityContext = securityContext;
@ -43,6 +45,7 @@ namespace ASC.Api.Core.Auth
UserManager = userManager;
AuthManager = authManager;
AuthContext = authContext;
ServiceProvider = serviceProvider;
}
public SecurityContext SecurityContext { get; }
@ -52,10 +55,12 @@ namespace ASC.Api.Core.Auth
public UserManager UserManager { get; }
public AuthManager AuthManager { get; }
public AuthContext AuthContext { get; }
public IServiceProvider ServiceProvider { get; }
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var emailValidationKeyModel = EmailValidationKeyModel.FromRequest(Context.Request);
using var scope = ServiceProvider.CreateScope();
var emailValidationKeyModel = scope.ServiceProvider.GetService<EmailValidationKeyModel>();
if (!emailValidationKeyModel.Type.HasValue)
{
@ -67,7 +72,7 @@ namespace ASC.Api.Core.Auth
EmailValidationKeyProvider.ValidationResult checkKeyResult;
try
{
checkKeyResult = emailValidationKeyModel.Validate(EmailValidationKeyProvider, AuthContext, TenantManager, UserManager, AuthManager);
checkKeyResult = emailValidationKeyModel.Validate();
}
catch (ArgumentNullException)
{

View File

@ -49,13 +49,13 @@ namespace ASC.Core.Billing
public DateTime DueDate { get; set; }
[JsonPropertyName("portal_count")]
public int PortalCount { get; set; }
public int PortalCount { get; set; }
public bool Trial { get; set; }
[JsonPropertyName("user_quota")]
public int ActiveUsers { get; set; }
[JsonPropertyName("user_quota")]
public int ActiveUsers { get; set; }
[JsonPropertyName("customer_id")]
public string CustomerId { get; set; }
@ -68,7 +68,16 @@ namespace ASC.Core.Billing
try
{
var license = JsonSerializer.Deserialize<License>(licenseString);
var options = new JsonSerializerOptions
{
AllowTrailingCommas = true,
PropertyNameCaseInsensitive = true
};
options.Converters.Add(new LicenseConverter());
var license = JsonSerializer.Deserialize<License>(licenseString, options);
if (license == null) throw new BillingNotFoundException("Can't parse license");
license.OriginalLicense = licenseString;
@ -80,5 +89,52 @@ namespace ASC.Core.Billing
throw new BillingNotFoundException("Can't parse license");
}
}
}
public class LicenseConverter : JsonConverter<object>
{
public override bool CanConvert(Type typeToConvert)
{
return
typeof(int) == typeToConvert ||
typeof(bool) == typeToConvert;
}
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (typeToConvert == typeof(int) && reader.TokenType == JsonTokenType.String)
{
var i = reader.GetString();
if (!int.TryParse(i, out var result))
{
return 0;
}
return result;
}
if (typeToConvert == typeof(bool))
{
if (reader.TokenType == JsonTokenType.String)
{
var i = reader.GetString();
if (!bool.TryParse(i, out var result))
{
return false;
}
return result;
}
return reader.GetBoolean();
}
return null;
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
return;
}
}
}

View File

@ -27,15 +27,12 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Core.Tenants;
using ASC.Core.Users;
using ASC.Core.Users;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
@ -162,14 +159,8 @@ namespace ASC.Core.Billing
licenseStream.Seek(0, SeekOrigin.Begin);
}
const int bufferSize = 4096;
using var fs = File.Open(path, FileMode.Create);
var buffer = new byte[bufferSize];
int readed;
while ((readed = licenseStream.Read(buffer, 0, bufferSize)) != 0)
{
fs.Write(buffer, 0, readed);
}
using var fs = File.Open(path, FileMode.Create);
licenseStream.CopyTo(fs);
}
private DateTime Validate(License license)
@ -281,52 +272,55 @@ namespace ASC.Core.Billing
public DateTime VersionReleaseDate
{
get
{
if (_date != DateTime.MinValue) return _date;
{
// release sign is not longer requered
return _date;
_date = DateTime.MaxValue;
try
{
var versionDate = Configuration["version:release:date"];
var sign = Configuration["version:release:sign"];
//if (_date != DateTime.MinValue) return _date;
if (!sign.StartsWith("ASC "))
{
throw new Exception("sign without ASC");
}
//_date = DateTime.MaxValue;
//try
//{
// var versionDate = Configuration["version:release:date"];
// var sign = Configuration["version:release:sign"];
var splitted = sign.Substring(4).Split(':');
var pkey = splitted[0];
if (pkey != versionDate)
{
throw new Exception("sign with different date");
}
// if (!sign.StartsWith("ASC "))
// {
// throw new Exception("sign without ASC");
// }
var date = splitted[1];
var orighash = splitted[2];
// var splitted = sign.Substring(4).Split(':');
// var pkey = splitted[0];
// if (pkey != versionDate)
// {
// throw new Exception("sign with different date");
// }
var skey = Configuration["core:machinekey"];
// var date = splitted[1];
// var orighash = splitted[2];
using (var hasher = new HMACSHA1(Encoding.UTF8.GetBytes(skey)))
{
var data = string.Join("\n", date, pkey);
var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(data));
if (WebEncoders.Base64UrlEncode(hash) != orighash && Convert.ToBase64String(hash) != orighash)
{
throw new Exception("incorrect hash");
}
}
// var skey = Configuration["core:machinekey"];
var year = int.Parse(versionDate.Substring(0, 4));
var month = int.Parse(versionDate.Substring(4, 2));
var day = int.Parse(versionDate.Substring(6, 2));
_date = new DateTime(year, month, day);
}
catch (Exception ex)
{
Log.Error("VersionReleaseDate", ex);
}
return _date;
// using (var hasher = new HMACSHA1(Encoding.UTF8.GetBytes(skey)))
// {
// var data = string.Join("\n", date, pkey);
// var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(data));
// if (WebEncoders.Base64UrlEncode(hash) != orighash && Convert.ToBase64String(hash) != orighash)
// {
// throw new Exception("incorrect hash");
// }
// }
// var year = int.Parse(versionDate.Substring(0, 4));
// var month = int.Parse(versionDate.Substring(4, 2));
// var day = int.Parse(versionDate.Substring(6, 2));
// _date = new DateTime(year, month, day);
//}
//catch (Exception ex)
//{
// Log.Error("VersionReleaseDate", ex);
//}
//return _date;
}
}

View File

@ -487,7 +487,8 @@ namespace ASC.Core.Billing
{
Tenant = tenant,
Tariff = bi.Item1,
Stamp = bi.Item2
Stamp = bi.Item2,
CreateOn = DateTime.UtcNow
};
CoreDbContext.Tariffs.Add(efTariff);

View File

@ -91,7 +91,7 @@ namespace ASC.Core.Data
PaymentId = r.PaymentId,
Spam = r.Spam,
Status = r.Status,
StatusChangeDate = r.StatusChanged,
StatusChangeDate = r.StatusChangedHack ?? DateTime.MinValue,
TenantAlias = r.Alias,
TenantId = r.Id,
MappedDomain = r.MappedDomain,
@ -382,9 +382,10 @@ namespace ASC.Core.Data
{
Id = key,
Tenant = tenant,
Value = data
Value = data,
LastModified = DateTime.UtcNow
};
TenantDbContext.CoreSettings.Add(settings);
TenantDbContext.AddOrUpdate(r => r.CoreSettings, settings);
}
TenantDbContext.SaveChanges();
tx.Commit();

View File

@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
namespace ASC.Core.Common.EF.Model
{
[Table("core_settings")]
public class DbCoreSettings
public class DbCoreSettings : BaseEntity
{
public int Tenant { get; set; }
public string Id { get; set; }
@ -13,6 +13,11 @@ namespace ASC.Core.Common.EF.Model
[Column("last_modified")]
public DateTime LastModified { get; set; }
public override object[] GetKeys()
{
return new object[] { Tenant, Id };
}
}
public static class CoreSettingsExtension

View File

@ -15,15 +15,23 @@ namespace ASC.Core.Common.EF.Model
public string Alias { get; set; }
public string MappedDomain { get; set; }
public int Version { get; set; }
public DateTime? Version_Changed { get; set; }
[NotMapped]
public DateTime VersionChanged { get => Version_Changed ?? DateTime.MinValue; set => Version_Changed = value; }
[Column("version_changed")]
public DateTime VersionChanged { get; set; }
public string Language { get; set; }
public string TimeZone { get; set; }
public string TrustedDomains { get; set; }
public TenantTrustedDomainsType TrustedDomainsEnabled { get; set; }
public TenantStatus Status { get; set; }
public DateTime StatusChanged { get; set; }
public DateTime? StatusChanged { get; set; }
//hack for DateTime?
[NotMapped]
public DateTime? StatusChangedHack { get { return StatusChanged; } set { StatusChanged = value; } }
public DateTime CreationDateTime { get; set; }
[Column("owner_id")]

View File

@ -156,64 +156,30 @@ namespace ASC.Security.Cryptography
public string Email { get; set; }
public Guid? UiD { get; set; }
public ConfirmType? Type { get; set; }
public int? P { get; set; }
public int? P { get; set; }
public EmailValidationKeyProvider Provider { get; }
public AuthContext AuthContext { get; }
public TenantManager TenantManager { get; }
public UserManager UserManager { get; }
public AuthManager Authentication { get; }
public EmailValidationKeyModel(
IHttpContextAccessor httpContextAccessor,
EmailValidationKeyProvider provider,
AuthContext authContext,
TenantManager tenantManager,
UserManager userManager,
AuthManager authentication)
{
Provider = provider;
AuthContext = authContext;
TenantManager = tenantManager;
UserManager = userManager;
Authentication = authentication;
var request = QueryHelpers.ParseQuery(httpContextAccessor.HttpContext.Request.Headers["confirm"]);
public ValidationResult Validate(EmailValidationKeyProvider provider, AuthContext authContext, TenantManager tenantManager, UserManager userManager, AuthManager authentication)
{
ValidationResult checkKeyResult;
switch (Type)
{
case ConfirmType.EmpInvite:
checkKeyResult = provider.ValidateEmailKey(Email + Type + (int)EmplType, Key, provider.ValidInterval);
break;
case ConfirmType.LinkInvite:
checkKeyResult = provider.ValidateEmailKey(Type.ToString() + (int)EmplType, Key, provider.ValidInterval);
break;
case ConfirmType.PortalOwnerChange:
checkKeyResult = provider.ValidateEmailKey(Email + Type + UiD.HasValue, Key, provider.ValidInterval);
break;
case ConfirmType.EmailChange:
checkKeyResult = provider.ValidateEmailKey(Email + Type + authContext.CurrentAccount.ID, Key, provider.ValidInterval);
break;
case ConfirmType.PasswordChange:
var hash = string.Empty;
if (P == 1)
{
var tenantId = tenantManager.GetCurrentTenant().TenantId;
hash = authentication.GetUserPasswordHash(tenantId, UiD.Value);
}
checkKeyResult = provider.ValidateEmailKey(Email + Type + (string.IsNullOrEmpty(hash) ? string.Empty : Hasher.Base64Hash(hash)) + UiD, Key, provider.ValidInterval);
break;
case ConfirmType.Activation:
checkKeyResult = provider.ValidateEmailKey(Email + Type + UiD, Key, provider.ValidInterval);
break;
case ConfirmType.ProfileRemove:
// validate UiD
if (P == 1)
{
var user = userManager.GetUsers(UiD.GetValueOrDefault());
if (user == null || user.Status == EmployeeStatus.Terminated || authContext.IsAuthenticated && authContext.CurrentAccount.ID != UiD)
return ValidationResult.Invalid;
}
checkKeyResult = provider.ValidateEmailKey(Email + Type + UiD, Key, provider.ValidInterval);
break;
default:
checkKeyResult = provider.ValidateEmailKey(Email + Type, Key, provider.ValidInterval);
break;
}
return checkKeyResult;
}
public static EmailValidationKeyModel FromRequest(HttpRequest httpRequest)
{
var Request = QueryHelpers.ParseQuery(httpRequest.Headers["confirm"]);
_ = Request.TryGetValue("type", out var type);
_ = request.TryGetValue("type", out var type);
ConfirmType? cType = null;
if (Enum.TryParse<ConfirmType>(type, out var confirmType))
@ -221,27 +187,78 @@ namespace ASC.Security.Cryptography
cType = confirmType;
}
_ = Request.TryGetValue("key", out var key);
_ = request.TryGetValue("key", out var key);
_ = Request.TryGetValue("p", out var pkey);
_ = request.TryGetValue("p", out var pkey);
_ = int.TryParse(pkey, out var p);
_ = Request.TryGetValue("emplType", out var emplType);
_ = request.TryGetValue("emplType", out var emplType);
_ = Enum.TryParse<EmployeeType>(emplType, out var employeeType);
_ = Request.TryGetValue("email", out var _email);
_ = Request.TryGetValue("uid", out var userIdKey);
_ = request.TryGetValue("email", out var _email);
_ = request.TryGetValue("uid", out var userIdKey);
_ = Guid.TryParse(userIdKey, out var userId);
return new EmailValidationKeyModel
Key = key;
Type = cType;
Email = _email;
EmplType = employeeType;
UiD = userId;
P = p;
}
public ValidationResult Validate()
{
ValidationResult checkKeyResult;
switch (Type)
{
Key = key,
Type = cType,
Email = _email,
EmplType = employeeType,
UiD = userId,
P = p
};
case ConfirmType.EmpInvite:
checkKeyResult = Provider.ValidateEmailKey(Email + Type + (int)EmplType, Key, Provider.ValidInterval);
break;
case ConfirmType.LinkInvite:
checkKeyResult = Provider.ValidateEmailKey(Type.ToString() + (int)EmplType, Key, Provider.ValidInterval);
break;
case ConfirmType.PortalOwnerChange:
checkKeyResult = Provider.ValidateEmailKey(Email + Type + UiD.HasValue, Key, Provider.ValidInterval);
break;
case ConfirmType.EmailChange:
checkKeyResult = Provider.ValidateEmailKey(Email + Type + AuthContext.CurrentAccount.ID, Key, Provider.ValidInterval);
break;
case ConfirmType.PasswordChange:
var hash = string.Empty;
if (P == 1)
{
var tenantId = TenantManager.GetCurrentTenant().TenantId;
hash = Authentication.GetUserPasswordHash(tenantId, UiD.Value);
}
checkKeyResult = Provider.ValidateEmailKey(Email + Type + (string.IsNullOrEmpty(hash) ? string.Empty : Hasher.Base64Hash(hash)) + UiD, Key, Provider.ValidInterval);
break;
case ConfirmType.Activation:
checkKeyResult = Provider.ValidateEmailKey(Email + Type + UiD, Key, Provider.ValidInterval);
break;
case ConfirmType.ProfileRemove:
// validate UiD
if (P == 1)
{
var user = UserManager.GetUsers(UiD.GetValueOrDefault());
if (user == null || user.Status == EmployeeStatus.Terminated || AuthContext.IsAuthenticated && AuthContext.CurrentAccount.ID != UiD)
return ValidationResult.Invalid;
}
checkKeyResult = Provider.ValidateEmailKey(Email + Type + UiD, Key, Provider.ValidInterval);
break;
case ConfirmType.Wizard:
checkKeyResult = Provider.ValidateEmailKey("" + Type, Key, Provider.ValidInterval);
break;
default:
checkKeyResult = Provider.ValidateEmailKey(Email + Type, Key, Provider.ValidInterval);
break;
}
return checkKeyResult;
}
public void Deconstruct(out string key, out string email, out EmployeeType? employeeType, out Guid? userId, out ConfirmType? confirmType, out int? p)
@ -252,13 +269,11 @@ namespace ASC.Security.Cryptography
{
public static DIHelper AddEmailValidationKeyProviderService(this DIHelper services)
{
if (services.TryAddScoped<EmailValidationKeyProvider>())
{
return services
.AddTenantManagerService();
}
return services;
services.TryAddTransient<EmailValidationKeyModel>();
services.TryAddScoped<EmailValidationKeyProvider>();
return services
.AddTenantManagerService();
}
}
}

View File

@ -26,6 +26,7 @@ namespace ASC.Web.Studio.Utility
PhoneAuth,
Auth,
TfaActivation,
TfaAuth
TfaAuth,
Wizard
}
}

View File

@ -23,12 +23,12 @@ namespace ASC.Resource.Manager
public void ConfigureServices(IServiceCollection services)
{
var diHelper = new DIHelper(services);
services.AddLogging();
diHelper.TryAddScoped<ResourceData>();
diHelper.AddDbContextManagerService<ResourceDbContext>();
diHelper.AddLoggerService();
diHelper.AddNLogManager();
diHelper.TryAddSingleton(Configuration);
}
diHelper.TryAddSingleton(Configuration); }
}
}

View File

@ -97,6 +97,9 @@
"url-shortener": {
"value" : "/sh/",
"internal": "http://localhost:9999/"
},
"controlpanel": {
"url": ""
}
},
"ConnectionStrings": {
@ -116,5 +119,5 @@
},
"bookmarking": {
"thumbnail-url": "http://localhost:9800/?url={0}"
}
}
}

View File

@ -1,5 +1,5 @@
{
"kafka": {
"kafka": {
"BootstrapServers": ""
}
}
}

View File

@ -36,4 +36,8 @@
<ProjectReference Include="..\ASC.Web.Core\ASC.Web.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Core\" />
</ItemGroup>
</Project>

View File

@ -51,7 +51,7 @@ namespace ASC.Web.Api.Controllers
}
[Create(false)]
public AuthenticationTokenData AuthenticateMe([FromBody]AuthModel auth)
public AuthenticationTokenData AuthenticateMe([FromBody] AuthModel auth)
{
var tenant = TenantManager.GetCurrentTenant();
var user = GetUser(tenant.TenantId, auth.UserName, auth.Password);
@ -83,9 +83,9 @@ namespace ASC.Web.Api.Controllers
[AllowAnonymous]
[Create("confirm", false)]
public ValidationResult CheckConfirm([FromBody]EmailValidationKeyModel model)
public ValidationResult CheckConfirm([FromBody] EmailValidationKeyModel model)
{
return model.Validate(EmailValidationKeyProvider, AuthContext, TenantManager, UserManager, AuthManager);
return model.Validate();
}
private UserInfo GetUser(int tenantId, string userName, string password)

View File

@ -31,6 +31,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Security;
using System.ServiceModel.Security;
using System.Text.RegularExpressions;
using System.Web;
@ -75,6 +76,7 @@ using ASC.Web.Studio.Core.SMS;
using ASC.Web.Studio.Core.Statistic;
using ASC.Web.Studio.Core.TFA;
using ASC.Web.Studio.UserControls.CustomNavigation;
using ASC.Web.Studio.UserControls.FirstTime;
using ASC.Web.Studio.UserControls.Statistics;
using ASC.Web.Studio.Utility;
@ -112,6 +114,7 @@ namespace ASC.Api.Settings
public ProviderManager ProviderManager { get; }
public MobileDetector MobileDetector { get; }
public IOptionsSnapshot<AccountLinker> AccountLinker { get; }
public FirstTimeTenantSettings FirstTimeTenantSettings { get; }
public UserManager UserManager { get; }
public TenantManager TenantManager { get; }
public TenantExtra TenantExtra { get; }
@ -196,7 +199,8 @@ namespace ASC.Api.Settings
IMemoryCache memoryCache,
ProviderManager providerManager,
MobileDetector mobileDetector,
IOptionsSnapshot<AccountLinker> accountLinker)
IOptionsSnapshot<AccountLinker> accountLinker,
FirstTimeTenantSettings firstTimeTenantSettings)
{
Log = option.Get("ASC.Api");
WebHostEnvironment = webHostEnvironment;
@ -211,6 +215,7 @@ namespace ASC.Api.Settings
ProviderManager = providerManager;
MobileDetector = mobileDetector;
AccountLinker = accountLinker;
FirstTimeTenantSettings = firstTimeTenantSettings;
MessageService = messageService;
StudioNotifyService = studioNotifyService;
ApiContext = apiContext;
@ -249,7 +254,7 @@ namespace ASC.Api.Settings
StorageSettingsHelper = storageSettingsHelper;
}
[Read("")]
[Read("", Check = false)]
[AllowAnonymous]
public SettingsWrapper GetSettings()
{
@ -272,6 +277,11 @@ namespace ASC.Api.Settings
}
else
{
if (!SettingsManager.Load<WizardSettings>().Completed)
{
settings.WizardToken = CommonLinkUtility.GetToken("", ConfirmType.Wizard, userId: Tenant.OwnerId);
}
settings.EnabledJoin =
(Tenant.TrustedDomainsType == TenantTrustedDomainsType.Custom &&
Tenant.TrustedDomains.Count > 0) ||
@ -525,15 +535,17 @@ namespace ASC.Api.Settings
}
[AllowAnonymous]
[Read("cultures")]
[Read("cultures", Check = false)]
public IEnumerable<object> GetSupportedCultures()
{
return SetupInfo.EnabledCultures.Select(r => r.Name).ToArray();
}
[Read("timezones")]
public List<object> GetTimeZones()
[Authorize(AuthenticationSchemes = "confirm", Roles = "Wizard,Administrators")]
[Read("timezones", Check = false)]
public List<TimezonesModel> GetTimeZones()
{
ApiContext.AuthByClaim();
var timeZones = TimeZoneInfo.GetSystemTimeZones().ToList();
if (timeZones.All(tz => tz.Id != "UTC"))
@ -541,7 +553,7 @@ namespace ASC.Api.Settings
timeZones.Add(TimeZoneInfo.Utc);
}
var listOfTimezones = new List<object>();
var listOfTimezones = new List<TimezonesModel>();
foreach (var tz in timeZones.OrderBy(z => z.BaseUtcOffset))
{
@ -565,6 +577,13 @@ namespace ASC.Api.Settings
return listOfTimezones;
}
[Authorize(AuthenticationSchemes = "confirm", Roles = "Wizard")]
[Read("machine", Check = false)]
public string GetMachineName()
{
return Dns.GetHostName().ToLowerInvariant();
}
/* [Read("greetingsettings")]
public string GetGreetingSettings()
{
@ -715,7 +734,7 @@ namespace ASC.Api.Settings
return EnabledModules;
}
[Read("security/password")]
[Read("security/password", Check = false)]
[Authorize(AuthenticationSchemes = "confirm", Roles = "Everyone")]
public object GetPasswordSettings()
{
@ -1044,22 +1063,18 @@ namespace ASC.Api.Settings
return StudioPeriodicNotify.ChangeSubscription(AuthContext.CurrentAccount.ID, StudioNotifyHelper);
}
[Update("wizard/complete")]
public WizardSettings CompleteWizard()
[Update("wizard/complete", Check = false)]
[Authorize(AuthenticationSchemes = "confirm", Roles = "Wizard")]
public WizardSettings CompleteWizard(WizardModel wizardModel)
{
ApiContext.AuthByClaim();
PermissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
var settings = SettingsManager.Load<WizardSettings>();
if (settings.Completed)
return settings;
settings.Completed = true;
SettingsManager.Save(settings);
return settings;
return FirstTimeTenantSettings.SaveData(wizardModel);
}
[Update("tfaapp")]
public bool TfaSettings(TfaModel model)
{
@ -1337,7 +1352,7 @@ namespace ASC.Api.Settings
return productId == Guid.Empty ? "All" : product != null ? product.Name : productId.ToString();
}
[Read("license/refresh")]
[Read("license/refresh", Check = false)]
public bool RefreshLicense()
{
if (!CoreBaseSettings.Standalone) return false;
@ -1345,6 +1360,57 @@ namespace ASC.Api.Settings
return true;
}
[AllowAnonymous]
[Read("license/required", Check = false)]
public bool RequestLicense()
{
return FirstTimeTenantSettings.RequestLicense;
}
[Create("license", Check = false)]
[Authorize(AuthenticationSchemes = "confirm", Roles = "Wizard")]
public object UploadLicense([FromForm]UploadLicenseModel model)
{
try
{
if (!AuthContext.IsAuthenticated && SettingsManager.Load<WizardSettings>().Completed) throw new SecurityException(Resource.PortalSecurity);
if (!model.Files.Any()) throw new Exception(Resource.ErrorEmptyUploadFileSelected);
ApiContext.AuthByClaim();
var licenseFile = model.Files.First();
var dueDate = LicenseReader.SaveLicenseTemp(licenseFile.OpenReadStream());
return dueDate >= DateTime.UtcNow.Date
? Resource.LicenseUploaded
: string.Format(Resource.LicenseUploadedOverdue,
"",
"",
dueDate.Date.ToLongDateString());
}
catch (LicenseExpiredException ex)
{
Log.Error("License upload", ex);
throw new Exception(Resource.LicenseErrorExpired);
}
catch (LicenseQuotaException ex)
{
Log.Error("License upload", ex);
throw new Exception(Resource.LicenseErrorQuota);
}
catch (LicensePortalException ex)
{
Log.Error("License upload", ex);
throw new Exception(Resource.LicenseErrorPortal);
}
catch (Exception ex)
{
Log.Error("License upload", ex);
throw new Exception(Resource.LicenseError);
}
}
[Read("customnavigation/getall")]
public List<CustomNavigationItem> GetCustomNavigationItems()
@ -1770,7 +1836,8 @@ namespace ASC.Api.Settings
.AddCustomNamingPeopleService()
.AddProviderManagerService()
.AddAccountLinker()
.AddMobileDetectorService();
.AddMobileDetectorService()
.AddFirstTimeTenantSettings();
}
}
}

View File

@ -0,0 +1,337 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Common.Utils;
using ASC.Core;
using ASC.Core.Billing;
using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
using ASC.Core.Users;
using ASC.MessagingSystem;
using ASC.Web.Api.Models;
using ASC.Web.Core;
using ASC.Web.Core.PublicResources;
using ASC.Web.Core.Users;
using ASC.Web.Core.Utility.Settings;
using ASC.Web.Studio.Core;
using ASC.Web.Studio.Core.Notify;
using ASC.Web.Studio.UserControls.Management;
using ASC.Web.Studio.Utility;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
namespace ASC.Web.Studio.UserControls.FirstTime
{
public class FirstTimeTenantSettings
{
public ILog Log { get; }
public IConfiguration Configuration { get; }
public TenantManager TenantManager { get; }
public CoreSettings CoreSettings { get; }
public TenantExtra TenantExtra { get; }
public SettingsManager SettingsManager { get; }
public UserManager UserManager { get; }
public SetupInfo SetupInfo { get; }
public SecurityContext SecurityContext { get; }
public CookiesManager CookiesManager { get; }
public UserManagerWrapper UserManagerWrapper { get; }
public PaymentManager PaymentManager { get; }
public MessageService MessageService { get; }
public LicenseReader LicenseReader { get; }
public StudioNotifyService StudioNotifyService { get; }
public TimeZoneConverter TimeZoneConverter { get; }
public FirstTimeTenantSettings(
IOptionsMonitor<ILog> options,
IConfiguration configuration,
TenantManager tenantManager,
CoreSettings coreSettings,
TenantExtra tenantExtra,
SettingsManager settingsManager,
UserManager userManager,
SetupInfo setupInfo,
SecurityContext securityContext,
CookiesManager cookiesManager,
UserManagerWrapper userManagerWrapper,
PaymentManager paymentManager,
MessageService messageService,
LicenseReader licenseReader,
StudioNotifyService studioNotifyService,
TimeZoneConverter timeZoneConverter)
{
Log = options.CurrentValue;
Configuration = configuration;
TenantManager = tenantManager;
CoreSettings = coreSettings;
TenantExtra = tenantExtra;
SettingsManager = settingsManager;
UserManager = userManager;
SetupInfo = setupInfo;
SecurityContext = securityContext;
CookiesManager = cookiesManager;
UserManagerWrapper = userManagerWrapper;
PaymentManager = paymentManager;
MessageService = messageService;
LicenseReader = licenseReader;
StudioNotifyService = studioNotifyService;
TimeZoneConverter = timeZoneConverter;
}
public WizardSettings SaveData(WizardModel wizardModel)
{
try
{
var (email, pwd, lng, timeZone, promocode, amiid, analytics) = wizardModel;
var tenant = TenantManager.GetCurrentTenant();
var settings = SettingsManager.Load<WizardSettings>();
if (settings.Completed)
{
throw new Exception("Wizard passed.");
}
if (!string.IsNullOrEmpty(SetupInfo.AmiMetaUrl) && IncorrectAmiId(amiid))
{
//throw new Exception(Resource.EmailAndPasswordIncorrectAmiId); TODO
}
if (tenant.OwnerId == Guid.Empty)
{
Thread.Sleep(TimeSpan.FromSeconds(6)); // wait cache interval
tenant = TenantManager.GetTenant(tenant.TenantId);
if (tenant.OwnerId == Guid.Empty)
{
Log.Error(tenant.TenantId + ": owner id is empty.");
}
}
var currentUser = UserManager.GetUsers(TenantManager.GetCurrentTenant().OwnerId);
if (!UserManagerWrapper.ValidateEmail(email))
{
throw new Exception(Resource.EmailAndPasswordIncorrectEmail);
}
UserManagerWrapper.CheckPasswordPolicy(pwd);
SecurityContext.SetUserPassword(currentUser.ID, pwd);
email = email.Trim();
if (currentUser.Email != email)
{
currentUser.Email = email;
currentUser.ActivationStatus = EmployeeActivationStatus.NotActivated;
}
UserManager.SaveUserInfo(currentUser);
if (!string.IsNullOrWhiteSpace(promocode))
{
try
{
PaymentManager.ActivateKey(promocode);
}
catch (Exception err)
{
Log.Error("Incorrect Promo: " + promocode, err);
throw new Exception(Resource.EmailAndPasswordIncorrectPromocode);
}
}
if (RequestLicense)
{
TariffSettings.SetLicenseAccept(SettingsManager);
MessageService.Send(MessageAction.LicenseKeyUploaded);
LicenseReader.RefreshLicense();
}
if (TenantExtra.Opensource)
{
settings.Analytics = analytics;
}
settings.Completed = true;
SettingsManager.Save(settings);
TrySetLanguage(tenant, lng);
tenant.TimeZone = TimeZoneConverter.GetTimeZone(timeZone).Id;
TenantManager.SaveTenant(tenant);
StudioNotifyService.SendCongratulations(currentUser);
SendInstallInfo(currentUser);
return settings;
}
catch (BillingNotFoundException)
{
throw new Exception(UserControlsCommonResource.LicenseKeyNotFound);
}
catch (BillingNotConfiguredException)
{
throw new Exception(UserControlsCommonResource.LicenseKeyNotCorrect);
}
catch (BillingException)
{
throw new Exception(UserControlsCommonResource.LicenseException);
}
catch (Exception ex)
{
Log.Error(ex);
throw;
}
}
public bool RequestLicense
{
get
{
return TenantExtra.EnableTarrifSettings && TenantExtra.Enterprise && !TenantExtra.EnterprisePaid;
}
}
private void TrySetLanguage(Tenant tenant, string lng)
{
if (string.IsNullOrEmpty(lng)) return;
try
{
var culture = CultureInfo.GetCultureInfo(lng);
tenant.Language = culture.Name;
}
catch (Exception err)
{
Log.Error(err);
}
}
private static string _amiId;
private bool IncorrectAmiId(string customAmiId)
{
customAmiId = (customAmiId ?? "").Trim();
if (string.IsNullOrEmpty(customAmiId)) return true;
if (string.IsNullOrEmpty(_amiId))
{
var getAmiIdUrl = SetupInfo.AmiMetaUrl + "instance-id";
var request = (HttpWebRequest)WebRequest.Create(getAmiIdUrl);
try
{
using (var response = request.GetResponse())
using (var responseStream = response.GetResponseStream())
using (var reader = new StreamReader(responseStream))
{
_amiId = reader.ReadToEnd();
}
Log.Debug("Instance id: " + _amiId);
}
catch (Exception e)
{
Log.Error("Request AMI id", e);
}
}
return string.IsNullOrEmpty(_amiId) || _amiId != customAmiId;
}
public void SendInstallInfo(UserInfo user)
{
try
{
var url = Configuration["web:install-url"];
if (string.IsNullOrEmpty(url)) return;
var tenant = TenantManager.GetCurrentTenant();
var q = new MailQuery
{
Email = user.Email,
Id = CoreSettings.GetKey(tenant.TenantId),
Alias = tenant.GetTenantDomain(CoreSettings),
};
var index = url.IndexOf("?v=", StringComparison.InvariantCultureIgnoreCase);
if (0 < index)
{
q.Version = url.Substring(index + 3) + Environment.OSVersion;
url = url.Substring(0, index);
}
using var webClient = new WebClient();
var values = new NameValueCollection
{
{"query", Signature.Create(q, "4be71393-0c90-41bf-b641-a8d9523fba5c")}
};
webClient.UploadValues(url, values);
}
catch (Exception error)
{
Log.Error(error);
}
}
private class MailQuery
{
public string Email { get; set; }
public string Version { get; set; }
public string Id { get; set; }
public string Alias { get; set; }
}
}
public static class FirstTimeTenantSettingsExtension
{
public static DIHelper AddFirstTimeTenantSettings(this DIHelper services)
{
services.TryAddTransient<FirstTimeTenantSettings>();
return services
.AddTenantManagerService()
.AddCoreConfigurationService()
.AddCoreSettingsService()
.AddTenantExtraService()
.AddSettingsManagerService()
.AddSetupInfo()
.AddSecurityContextService()
.AddCookiesManagerService()
.AddUserManagerWrapperService()
.AddPaymentManagerService()
.AddMessageServiceService()
.AddLicenseReaderService()
.AddStudioNotifyServiceService();
}
}
}

View File

@ -57,6 +57,8 @@ namespace ASC.Api.Settings
public bool? ThirdpartyEnable { get; set; }
public string WizardToken { get; set; }
public static SettingsWrapper GetSample()
{
return new SettingsWrapper

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
namespace ASC.Web.Api.Models
{
public class UploadLicenseModel
{
public IEnumerable<IFormFile> Files { get; set; }
}
}

View File

@ -0,0 +1,16 @@
namespace ASC.Web.Api.Models
{
public class WizardModel
{
public string Email { get; set; }
public string Pwd { get; set; }
public string Lng { get; set; }
public string TimeZone { get; set; }
public string Promocode { get; set; }
public string AmiId { get; set; }
public bool Analytics { get; set; }
public void Deconstruct(out string email, out string pwd, out string lng, out string timeZone, out string promocode, out string amiid, out bool analytics)
=> (email, pwd, lng, timeZone, promocode, amiid, analytics) = (Email, Pwd, Lng, TimeZone, Promocode, AmiId, Analytics);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

View File

@ -1,35 +1,37 @@
import React, { Suspense, lazy } from "react";
import { Router, Route, Switch } from "react-router-dom";
import { Loader } from "asc-web-components";
import { history, PrivateRoute, PublicRoute, Login, Error404, StudioLayout, Offline, ComingSoon} from "asc-web-common";
import About from "./components/pages/About";
const Home = lazy(() => import("./components/pages/Home"));
const Confirm = lazy(() => import("./components/pages/Confirm"));
const Settings = lazy(() => import("./components/pages/Settings"));
const App = () => {
return (
navigator.onLine ?
<Router history={history}>
<StudioLayout>
<Suspense
fallback={<Loader className="pageLoader" type="rombs" size='40px' />}
>
<Switch>
<PublicRoute exact path={["/login","/login/error=:error", "/login/confirmed-email=:confirmedEmail"]} component={Login} />
<Route path="/confirm" component={Confirm} />
<PrivateRoute exact path={["/","/error=:error"]} component={Home} />
<PrivateRoute exact path="/about" component={About} />
<PrivateRoute restricted path="/settings" component={Settings} />
<PrivateRoute exact path={["/coming-soon"]} component={ComingSoon} />
<PrivateRoute component={Error404} />
</Switch>
</Suspense>
</StudioLayout>
</Router> :
<Offline/>
);
};
export default App;
import React, { Suspense, lazy } from "react";
import { Router, Route, Switch } from "react-router-dom";
import { Loader } from "asc-web-components";
import { history, PrivateRoute, PublicRoute, Login, Error404, StudioLayout, Offline, ComingSoon} from "asc-web-common";
import About from "./components/pages/About";
const Home = lazy(() => import("./components/pages/Home"));
const Confirm = lazy(() => import("./components/pages/Confirm"));
const Settings = lazy(() => import("./components/pages/Settings"));
const Wizard = lazy(() => import("./components/pages/Wizard"));
const App = () => {
return (
navigator.onLine ?
<Router history={history}>
<StudioLayout>
<Suspense
fallback={<Loader className="pageLoader" type="rombs" size='40px' />}
>
<Switch>
<Route exact path="/wizard" component={Wizard} />
<PublicRoute exact path={["/login","/login/error=:error", "/login/confirmed-email=:confirmedEmail"]} component={Login} />
<Route path="/confirm" component={Confirm} />
<PrivateRoute exact path={["/","/error=:error"]} component={Home} />
<PrivateRoute exact path="/about" component={About} />
<PrivateRoute restricted path="/settings" component={Settings} />
<PrivateRoute exact path={["/coming-soon"]} component={ComingSoon} />
<PrivateRoute component={Error404} />
</Switch>
</Suspense>
</StudioLayout>
</Router> :
<Offline/>
);
};
export default App;

View File

@ -0,0 +1,53 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import { constants } from 'asc-web-common';
const { LANGUAGE } = constants;
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: false
},
backend: {
loadPath: `/locales/Wizard/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
}
};
newInstance.init({
resources: resources,
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: false
}
});
}
export default newInstance;

View File

@ -0,0 +1,517 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import styled from "styled-components";
import i18n from './i18n';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
PageLayout,
ErrorContainer,
history,
constants,
utils as commonUtils
} from "asc-web-common";
import {
Loader,
utils
} from 'asc-web-components';
import HeaderContainer from './sub-components/header-container';
import ButtonContainer from './sub-components/button-container';
import SettingsContainer from './sub-components/settings-container';
import InputContainer from './sub-components/input-container';
import ModalContainer from './sub-components/modal-dialog-container';
import {
getPortalPasswordSettings,
getPortalTimezones,
getPortalCultures,
setIsWizardLoaded,
getMachineName,
getIsRequiredLicense,
setPortalOwner,
setLicense,
resetLicenseUploaded
} from '../../../store/wizard/actions';
const { tablet } = utils.device;
const { changeLanguage } = commonUtils;
const { EmailSettings } = utils.email;
const emailSettings = new EmailSettings();
emailSettings.allowDomainPunycode = true;
const WizardContainer = styled.div`
width: 960px;
margin: 0 auto;
margin-top: 120px;
.wizard-form {
margin-top: 32px;
display: grid;
grid-template-columns: 1fr;
grid-row-gap: 32px;
}
@media ${tablet} {
width: 100%;
max-width: 480px;
}
@media(max-width: 520px) {
width: calc(100% - 32px);
margin-top: 72px
}
`;
class Body extends Component {
constructor(props) {
super(props);
const { t } = props;
this.state = {
password: '',
isValidPass: false,
errorLoading: false,
errorMessage: null,
errorInitWizard: null,
sending: false,
visibleModal: false,
emailValid: false,
email: '',
changeEmail: '',
license: false,
languages: null,
timezones: null,
selectLanguage: null,
selectTimezone: null,
emailNeeded: true,
emailOwner: 'fake@mail.com',
hasErrorEmail: false,
hasErrorPass: false,
hasErrorLicense: false,
checkingMessages: []
}
document.title = t('wizardTitle');
}
async componentDidMount() {
const {
t, wizardToken,
getPortalPasswordSettings, getPortalCultures,
getPortalTimezones, setIsWizardLoaded,
getMachineName, getIsRequiredLicense, history
} = this.props;
window.addEventListener("keyup", this.onKeyPressHandler);
localStorage.setItem(constants.WIZARD_KEY, true);
if(!wizardToken) {
history.push('/');
} else {
await Promise.all([
getPortalPasswordSettings(wizardToken),
getMachineName(wizardToken),
getIsRequiredLicense(),
getPortalTimezones(wizardToken)
.then(() => {
const { timezones, timezone } = this.props;
const zones = this.mapTimezonesToArray(timezones);
const select = zones.filter(zone => zone.key === timezone);
this.setState({
timezones: zones,
selectTimezone: {
key: select[0].key,
label: select[0].label
}
});
}),
getPortalCultures()
.then(() => {
const { cultures, culture } = this.props;
const languages = this.mapCulturesToArray(cultures, t);
let select = languages.filter(lang => lang.key === culture );
if (!select.length) select = languages.filter(lang => lang.key === 'en-US' )
this.setState({
languages: languages,
selectLanguage: {
key: select[0].key,
label: select[0].label
}
})
})
])
.then(() => setIsWizardLoaded(true))
.catch((e) => {
this.setState({
errorInitWizard: e
})});
}
}
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.isWizardLoaded === true || nextState.errorInitWizard !== null) {
return true;
} else {
return false;
}
}
componentWillUnmount() {
window.removeEventListener("keyup", this.onKeyPressHandler);
localStorage.removeItem(constants.WIZARD_KEY);
}
mapTimezonesToArray = (timezones) => {
return timezones.map((timezone) => {
return { key: timezone.id, label: timezone.displayName };
});
};
mapCulturesToArray = (cultures, t) => {
return cultures.map((culture) => {
return { key: culture, label: t(`Culture_${culture}`) };
});
};
onKeyPressHandler = e => {
if (e.key === "Enter") this.onContinueHandler();
}
isValidPassHandler = val => this.setState({ isValidPass: val });
onChangePassword = e => this.setState({ password: e.target.value, hasErrorPass: false });
onClickChangeEmail = () => this.setState({ visibleModal: true });
onEmailChangeHandler = result => {
const { emailNeeded } = this.state;
emailNeeded
? this.setState({
emailValid: result.isValid,
email: result.value,
hasErrorEmail: false
})
: this.setState({
emailValid: result.isValid,
changeEmail: result.value
})
}
onChangeLicense = () => this.setState({ license: !this.state.license });
onContinueHandler = () => {
const valid = this.checkingValid();
if (valid) {
const { setPortalOwner, wizardToken } = this.props;
const { password, email,
selectLanguage, selectTimezone,
emailOwner
} = this.state;
this.setState({ sending: true });
const emailTrim = email ? email.trim() : emailOwner.trim();
const analytics = true;
// console.log(emailTrim, password, selectLanguage.key, selectTimezone.key, analytics, wizardToken);
setPortalOwner(emailTrim, password, selectLanguage.key, selectTimezone.key, wizardToken, analytics)
.then(() => history.push('/login'))
.catch( e => this.setState({
errorLoading: true,
sending: false,
errorMessage: e
}))
} else {
this.setState({ visibleModal: true })
}
}
checkingValid = () => {
const { t, isLicenseRequired, licenseUpload } = this.props;
const { isValidPass, emailValid, license, emailNeeded } = this.state;
let checkingMessages = [];
if(!isValidPass) {
checkingMessages.push(t('errorPassword'));
this.setState({hasErrorPass: true, checkingMessages: checkingMessages });
}
if(!license) {
checkingMessages.push(t('errorLicenseRead'));
this.setState({ checkingMessages: checkingMessages });
}
if ( emailNeeded && !isLicenseRequired) {
if(!emailValid) {
checkingMessages.push(t('errorEmail'));
this.setState({ hasErrorEmail: true, checkingMessages: checkingMessages });
}
if( isValidPass && emailValid && license ) {
return true;
}
}
if (emailNeeded && isLicenseRequired) {
if(!emailValid) {
checkingMessages.push(t('errorEmail'));
this.setState({ hasErrorEmail: true, checkingMessages: checkingMessages });
}
if(!licenseUpload) {
checkingMessages.push(t('errorUploadLicenseFile'));
this.setState({ hasErrorLicense: true, checkingMessages: checkingMessages });
}
if( isValidPass && emailValid && license && licenseUpload) {
return true;
}
}
if (!emailNeeded && isLicenseRequired) {
if(!licenseUpload) {
checkingMessages.push(t('errorUploadLicenseFile'));
this.setState({ hasErrorLicense: true, checkingMessages: checkingMessages });
}
if( isValidPass && license && licenseUpload) {
return true;
}
}
return false;
}
onSaveEmailHandler = () => {
const { changeEmail, emailValid } = this.state;
if( emailValid && changeEmail ) {
this.setState({ email: changeEmail})
}
this.setState({ visibleModal: false })
}
onCloseModal = () => {
this.setState({
visibleModal: false,
errorLoading: false,
errorMessage: null
});
}
onSelectTimezoneHandler = el => this.setState({ selectTimezone: el });
onSelectLanguageHandler = lang => this.setState({
selectLanguage: {
key: lang.key,
label: lang.label
}});
onInputFileHandler = file => {
const { setLicense, wizardToken, licenseUpload, resetLicenseUploaded } = this.props;
if (licenseUpload) resetLicenseUploaded();
this.setState({ hasErrorLicense: false });
let fd = new FormData();
fd.append("files", file );
setLicense(wizardToken, fd)
.catch( e => this.setState({
errorLoading: true,
errorMessage: e,
hasErrorLicense: true
}))
};
render() {
const {
t,
isWizardLoaded,
machineName,
passwordSettings,
culture,
isLicenseRequired,
urlLicense
} = this.props;
const {
sending,
selectLanguage,
license,
selectTimezone,
languages,
timezones,
emailNeeded,
email,
emailOwner,
password,
errorLoading,
visibleModal,
errorMessage,
errorInitWizard,
changeEmail,
hasErrorEmail,
hasErrorPass,
hasErrorLicense,
checkingMessages
} = this.state;
console.log('wizard render');
if (errorInitWizard) {
return <ErrorContainer
headerText={t('errorInitWizardHeader')}
bodyText={t('errorInitWizard')}
buttonText={t('errorInitWizardButton')}
buttonUrl="/" />
} else if (isWizardLoaded) {
return <WizardContainer>
<ModalContainer t={t}
errorLoading={errorLoading}
visibleModal={visibleModal}
errorMessage={errorMessage}
emailOwner={changeEmail ? changeEmail : emailOwner}
settings={emailSettings}
checkingMessages={checkingMessages}
onEmailChangeHandler={this.onEmailChangeHandler}
onSaveEmailHandler={this.onSaveEmailHandler}
onCloseModal={this.onCloseModal}
/>
<HeaderContainer t={t} />
<form className='wizard-form'>
<InputContainer t={t}s
settingsPassword={passwordSettings}
emailNeeded={emailNeeded}
password={password}
license={license}
settings={emailSettings}
isLicenseRequired={isLicenseRequired}
hasErrorEmail={hasErrorEmail}
hasErrorPass={hasErrorPass}
hasErrorLicense={hasErrorLicense}
urlLicense={urlLicense}
onChangeLicense={this.onChangeLicense}
isValidPassHandler={this.isValidPassHandler}
onChangePassword={this.onChangePassword}
onInputFileHandler={this.onInputFileHandler}
onEmailChangeHandler={this.onEmailChangeHandler}
/>
<SettingsContainer t={t}
selectLanguage={selectLanguage}
selectTimezone={selectTimezone}
languages={languages}
timezones={timezones}
emailNeeded={emailNeeded}
emailOwner={emailOwner}
email={email}
machineName={machineName}
portalCulture={culture}
onClickChangeEmail={this.onClickChangeEmail}
onSelectLanguageHandler={this.onSelectLanguageHandler}
onSelectTimezoneHandler={this.onSelectTimezoneHandler} />
<ButtonContainer t={t}
sending={sending}
onContinueHandler={this.onContinueHandler} />
</form>
</WizardContainer>
}
return <Loader className="pageLoader" type="rombs" size='40px' />;
}
}
Body.propTypes = {
culture: PropTypes.string,
i18n: PropTypes.object,
isWizardLoaded: PropTypes.bool.isRequired,
machineName: PropTypes.string.isRequired,
wizardToken: PropTypes.string,
passwordSettings: PropTypes.object,
cultures: PropTypes.array.isRequired,
timezones: PropTypes.array.isRequired,
timezone: PropTypes.string.isRequired,
licenseUpload: PropTypes.string
}
const WizardWrapper = withTranslation()(Body);
const WizardPage = props => {
const { isLoaded } = props;
changeLanguage(i18n);
return (
<>
{ isLoaded && <PageLayout
sectionBodyContent={<WizardWrapper i18n={i18n} {...props} />}
/>
}
</>
);
}
WizardPage.propTypes = {
culture: PropTypes.string.isRequired,
isLoaded: PropTypes.bool
}
function mapStateToProps({ wizard, auth }) {
const {
isWizardLoaded,
machineName,
isLicenseRequired,
licenseUpload
} = wizard;
const {
culture,
wizardToken,
passwordSettings,
cultures,
timezones,
timezone,
urlLicense
} = auth.settings;
return {
isLoaded: auth.isLoaded,
isWizardLoaded,
machineName,
culture,
wizardToken,
passwordSettings,
cultures,
timezones,
timezone,
urlLicense,
isLicenseRequired,
licenseUpload
};
}
export default connect(mapStateToProps, {
getPortalPasswordSettings,
getPortalCultures,
getPortalTimezones,
setIsWizardLoaded,
getMachineName,
getIsRequiredLicense,
setPortalOwner,
setLicense,
resetLicenseUploaded
})(withRouter(WizardPage));

View File

@ -0,0 +1,45 @@
{
"wizardTitle": " Portal Setup - Go through these easy steps to start your web office easily",
"welcomeTitle": "Welcome to your portal!",
"desc": "Please setup the portal registration data.",
"placeholderEmail": "E-mail",
"placeholderPass": "Password",
"placeholderLicense": "Your license file",
"license": "Accept the terms of the ",
"licenseLink": "License agreements",
"domain": "Domain:",
"email": "E-mail:",
"language": "Language:",
"timezone": "Time zone:",
"buttonContinue": "Continue",
"errorLicenseTitle": "Loading error",
"errorLicenseBody": "The license is not valid. Make sure you select the correct file",
"changeEmailTitle": "Change e-mail",
"changeEmailBtn": "Save",
"closeModalButton": "Close",
"tooltipPasswordTitle": "Password must contain:",
"tooltipPasswordLength": "-30 characters",
"tooltipPasswordDigits": "digits",
"tooltipPasswordCapital": "capital letters",
"tooltipPasswordSpecial": "special characters (!@#$%^&*)",
"Culture_en": "English (United Kingdom)",
"Culture_en-US": "English (United States)",
"Culture_ru-RU": "Russian (Russia)",
"errorPassword": "Password does not meet the requirements",
"errorEmail": "Invalid e-mail Address",
"errorLicenseRead": "You must accept the terms of the license agreement",
"errorUploadLicenseFile": "You must select a license file",
"errorInitWizardHeader": "Something went wrong.",
"errorInitWizard": "The service is currently unavailable, please try again later.",
"errorInitWizardButton": "Try again",
"generatePassword": "Generate password",
"errorParamsTitle": "Registration error",
"errorParamsBody": "Incorrect data entered:",
"errorParamsFooter": "Close"
}

View File

@ -0,0 +1,46 @@
{
"wizardTitle": "Благодарим Вас за выбор ONLYOFFICE! В целях безопасности необходимо выполнить процедуру установки пароля",
"welcomeTitle": "Добро пожаловать на Ваш портал!",
"desc": "Пожалуйста, введите регистрационные данные.",
"placeholderEmail": "E-mail",
"placeholderPass": "Пароль",
"placeholderLicense": "Ваш файл лицензии",
"license": "Принять условия ",
"licenseLink": "лицензионного соглашения",
"domain": "Домен:",
"email": "E-mail:",
"language": "Язык:",
"timezone": "Часовой пояс:",
"buttonContinue": "Продолжить",
"errorLicenseTitle": "Ошибка загрузки",
"errorLicenseBody": "Лицензия не действительна. Убедитесь что вы выбрали верный файл",
"changeEmailTitle": "Измененить адрес электронной почты",
"changeEmailBtn": "Сохранить",
"closeModalButton": "Закрыть",
"tooltipPasswordTitle": "Пароль должен содержать:",
"tooltipPasswordLength": "-30 символов",
"tooltipPasswordDigits": "цыфры",
"tooltipPasswordCapital": "Заглавные буквы",
"tooltipPasswordSpecial": "Специальные символы (!@#$%^&*)",
"Culture_en": "Английский (Великобритания)",
"Culture_en-US": "Английский (США)",
"Culture_ru-RU": "Русский (Россия)",
"errorPassword": "Пароль не соответсвует требованиям",
"errorEmail": "Некорректный адрес электронной почты",
"errorLicenseRead": "Необходимо приянть условия лицензионного соглашения",
"errorUploadLicenseFile": "Небходим файл лицензии",
"errorInitWizardHeader": "Что-то пошло не так.",
"errorInitWizard": "В данный момент сервис недоступен, попробуйте позже.",
"errorInitWizardButton": "Попробовать снова",
"generatePassword": "Сгенерировать пароль",
"errorParamsTitle": "Ошибка регистрации",
"errorParamsBody": "Введены некорректные данные:",
"errorParamsFooter": "Закрыть"
}

View File

@ -0,0 +1,48 @@
import React from 'react';
import PropTypes from "prop-types";
import styled from 'styled-components';
import {
Box,
Button,
utils
} from 'asc-web-components';
const { tablet } = utils.device;
const StyledContainer = styled(Box)`
width: 311px;
margin: 0 auto;
@media ${tablet} {
width: 100%;
}
`;
const ButtonContainer = ({
t,
sending,
onContinueHandler
}) => {
return (
<StyledContainer>
<Button
size="large"
scale={true}
primary
isDisabled={sending}
isLoading={sending ? true : false}
label={t('buttonContinue')}
onClick={onContinueHandler}
/>
</StyledContainer>
);
}
ButtonContainer.propTypes = {
t: PropTypes.func.isRequired,
sending: PropTypes.bool.isRequired,
onContinueHandler: PropTypes.func.isRequired
}
export default ButtonContainer;

View File

@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from "prop-types";
import styled from 'styled-components';
import {
Box,
Heading,
Text,
utils
} from 'asc-web-components';
const { tablet } = utils.device;
const StyledHeaderContainer = styled(Box)`
width: 100%;
.wizard-title {
text-align: center;
font-weight: 600;
font-size: 32px;
line-height: 36px;
margin: 0;
}
.wizard-desc {
text-align: center;
margin-top: 8px;
}
@media ${tablet} {
.wizard-title, .wizard-desc {
text-align: left;
}
}
@media(max-width: 520px) {
.wizard-title {
font-size: 23px;
line-height: 28px;
}
}
`;
const HeaderContainer = ({ t }) => {
return (
<StyledHeaderContainer>
<Heading level={1} title="Wizard" className="wizard-title">
{t('welcomeTitle')}
</Heading>
<Text className="wizard-desc" fontSize="13px">
{t('desc')}
</Text>
</StyledHeaderContainer>
)
};
HeaderContainer.propTypes = {
t: PropTypes.func.isRequired
}
export default HeaderContainer;

View File

@ -0,0 +1,174 @@
import React, { useRef } from 'react';
import PropTypes from "prop-types";
import styled from 'styled-components';
import {
Box,
EmailInput,
FileInput,
PasswordInput,
Link,
Checkbox,
utils
} from 'asc-web-components';
const { tablet } = utils.device;
const StyledContainer = styled(Box)`
width: 311px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr;
grid-row-gap: 16px;
.generate-pass-link {
margin-bottom: 16px;
}
.wizard-checkbox {
display: inline-block;
}
.wizard-checkbox span {
margin-right: 0.3em;
vertical-align: middle;
}
.link {
vertical-align: middle;
}
@media ${tablet} {
width: 100%;
}
`;
const InputContainer = ({
t,
settingsPassword,
emailNeeded,
onEmailChangeHandler,
onInputFileHandler,
password,
onChangePassword,
isValidPassHandler,
license,
onChangeLicense,
settings,
isLicenseRequired,
hasErrorEmail,
hasErrorPass,
hasErrorLicense,
urlLicense
}) => {
const refPassInput = useRef(null);
const tooltipPassTitle = t('tooltipPasswordTitle');
const tooltipPassLength = `${settingsPassword.minLength}${t('tooltipPasswordLength')}`;
const tooltipPassDigits = settingsPassword.digits ? `${t('tooltipPasswordDigits')}` : null;
const tooltipPassCapital = settingsPassword.upperCase ? `${t('tooltipPasswordCapital')}` : null;
const tooltipPassSpecial = settingsPassword.specSymbols ? `${t('tooltipPasswordSpecial')}` : null;
const inputEmail = emailNeeded
? <EmailInput
name="wizard-email"
tabIndex={1}
size="large"
scale={true}
placeholder={t('email')}
emailSettings={settings}
hasError={hasErrorEmail}
onValidateInput={onEmailChangeHandler}
/>
: null;
const inputLicenseFile = isLicenseRequired
? <Box>
<FileInput
tabIndex={3}
placeholder={t('placeholderLicense')}
size="large"
scale={true}
accept=".lic"
hasError={hasErrorLicense}
onInput={onInputFileHandler}
/>
</Box>
: null;
return (
<StyledContainer>
{inputEmail}
<PasswordInput
ref={refPassInput}
tabIndex={2}
size="large"
scale={true}
inputValue={password}
passwordSettings={settingsPassword}
isDisabled={false}
placeholder={t('placeholderPass')}
hideNewPasswordButton={true}
isDisableTooltip={true}
isTextTooltipVisible={true}
hasError={hasErrorPass}
tooltipPasswordTitle={tooltipPassTitle}
tooltipPasswordLength={tooltipPassLength}
tooltipPasswordDigits={tooltipPassDigits}
tooltipPasswordCapital={tooltipPassCapital}
tooltipPasswordSpecial={tooltipPassSpecial}
onChange={onChangePassword}
onValidateInput={isValidPassHandler}
/>
{ inputLicenseFile }
{!isLicenseRequired
? <Link
className='generate-pass-link'
type="action"
fontWeight="600"
isHovered={true}
onClick={() => refPassInput.current.onGeneratePassword()}>
{t('generatePassword')}
</Link>
: null
}
<Box>
<Checkbox
className="wizard-checkbox"
id="license"
name="confirm"
label={t('license')}
isChecked={license}
isDisabled={false}
onChange={onChangeLicense}
/>
<Link
className="link"
type="page"
color="#116d9d"
fontSize="13px"
target="_blank"
href={urlLicense ? urlLicense : "https://gnu.org/licenses/gpl-3.0.html"}
isBold={false}
>{t('licenseLink')}</Link>
</Box>
</StyledContainer>
);
}
InputContainer.propTypes = {
t: PropTypes.func.isRequired,
settingsPassword: PropTypes.object.isRequired,
emailNeeded: PropTypes.bool.isRequired,
onEmailChangeHandler: PropTypes.func.isRequired,
onInputFileHandler: PropTypes.func.isRequired,
password: PropTypes.string.isRequired,
onChangePassword: PropTypes.func.isRequired,
isValidPassHandler: PropTypes.func.isRequired,
license: PropTypes.bool.isRequired,
onChangeLicense: PropTypes.func.isRequired,
urlLicense: PropTypes.string
};
export default InputContainer;

View File

@ -0,0 +1,124 @@
import React from 'react';
import PropTypes from "prop-types";
import styled from 'styled-components';
import {
ModalDialog,
EmailInput,
Button,
Box,
Text,
utils
} from 'asc-web-components';
const { tablet } = utils.device;
const BtnContainer = styled(Box)`
width: 100px;
@media ${tablet} {
width: 293px;
}
`;
const BodyContainer = styled(Box)`
font: 13px 'Open Sans', normal;
line-height: 20px;
`;
const ModalContainer = ({
t,
errorLoading,
visibleModal,
errorMessage,
emailOwner,
settings,
onEmailChangeHandler,
onSaveEmailHandler,
onCloseModal,
checkingMessages
}) => {
let header, content, footer;
const visible = errorLoading ? errorLoading : visibleModal;
if(errorLoading) {
header = t('errorLicenseTitle');
content = <BodyContainer>
{ errorMessage
? errorMessage
: t('errorLicenseBody')}
</BodyContainer>;
} else if( visibleModal && checkingMessages.length < 1) {
header = t('changeEmailTitle');
content = <EmailInput
tabIndex={1}
scale={true}
size='base'
id="change-email"
name="email-wizard"
placeholder={t('placeholderEmail')}
emailSettings={settings}
value={emailOwner}
onValidateInput={onEmailChangeHandler}
/>;
footer = <BtnContainer>
<Button
key="saveBtn"
label={t('changeEmailBtn')}
primary={true}
scale={true}
size="big"
onClick={onSaveEmailHandler}
/>
</BtnContainer>;
} else if ( visibleModal && checkingMessages.length > 0) {
header = t('errorParamsTitle');
content = <>
<Text as="p">{ t('errorParamsBody') }</Text>
{
checkingMessages.map((el, index) => <Text key={index} as="p">- {el};</Text>)
}
</>;
footer = <BtnContainer>
<Button
key="saveBtn"
label={t('errorParamsFooter')}
primary={true}
scale={true}
size="big"
onClick={onCloseModal} />
</BtnContainer>;
}
return (
<ModalDialog
visible={visible}
displayType="auto"
zIndex={310}
headerContent={header}
bodyContent={content}
footerContent={footer}
onClose={onCloseModal}
/>
);
}
ModalContainer.propTypes = {
t: PropTypes.func.isRequired,
errorLoading: PropTypes.bool.isRequired,
visibleModal: PropTypes.bool.isRequired,
emailOwner: PropTypes.string,
settings: PropTypes.object.isRequired,
onEmailChangeHandler: PropTypes.func.isRequired,
onSaveEmailHandler: PropTypes.func.isRequired,
onCloseModal: PropTypes.func.isRequired
};
export default ModalContainer;

View File

@ -0,0 +1,130 @@
import React from 'react';
import PropTypes from "prop-types";
import styled from 'styled-components';
import {
Box,
ComboBox,
Text,
Link,
utils
} from 'asc-web-components';
const { tablet } = utils.device;
const StyledContainer = styled(Box)`
width: 311px;
margin: 0 auto;
display: grid;
grid-template-columns: min-content auto;
grid-auto-columns: min-content;
grid-row-gap: 12px;
.title {
white-space: nowrap;
}
.machine-name-value,
.email-value {
margin-left: 16px;
}
.drop-down {
margin-left: 8px;
}
@media ${tablet} {
width: 100%;
}
`;
const SettingsContainer = ({
selectLanguage,
selectTimezone,
languages,
timezones,
emailNeeded,
email,
emailOwner,
t,
machineName,
onClickChangeEmail,
onSelectLanguageHandler,
onSelectTimezoneHandler
}) => {
const titleEmail = !emailNeeded
? <Text>{t('email')}</Text>
: null
const contentEmail = !emailNeeded
? <Link
className="email-value"
type="action"
fontSize="13px"
fontWeight="600"
isHovered={true}
onClick={onClickChangeEmail}
>
{email ? email : emailOwner}
</Link>
: null
return (
<StyledContainer>
<Text fontSize="13px">{t('domain')}</Text>
<Text className="machine-name-value" fontSize="13px" fontWeight="600">{machineName}</Text>
{titleEmail}
{contentEmail}
<Text fontSize="13px">{t('language')}</Text>
<ComboBox
className="drop-down"
options={languages}
selectedOption={{
key: selectLanguage.key,
label: selectLanguage.label
}}
noBorder={true}
scaled={false}
size='content'
dropDownMaxHeight={300}
onSelect={onSelectLanguageHandler}
/>
<Text className="title" fontSize="13px">{t('timezone')}</Text>
<ComboBox
className="drop-down"
options={timezones}
selectedOption={{
key: selectTimezone.key,
label: selectTimezone.label
}}
noBorder={true}
dropDownMaxHeight={300}
scaled={false}
size='content'
onSelect={onSelectTimezoneHandler}
/>
</StyledContainer>
);
}
SettingsContainer.propTypes = {
selectLanguage: PropTypes.object.isRequired,
selectTimezone: PropTypes.object.isRequired,
languages: PropTypes.array.isRequired,
timezones: PropTypes.array.isRequired,
emailNeeded: PropTypes.bool.isRequired,
emailOwner: PropTypes.string,
t: PropTypes.func.isRequired,
machineName: PropTypes.string.isRequired,
email: PropTypes.string,
onClickChangeEmail: PropTypes.func.isRequired,
onSelectLanguageHandler: PropTypes.func.isRequired,
onSelectTimezoneHandler: PropTypes.func.isRequired
}
export default SettingsContainer;

View File

@ -6,11 +6,13 @@ import "./custom.scss";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { store as commonStore, constants, history, ErrorBoundary} from "asc-web-common";
const {
getUserInfo,
getPortalSettings,
setIsLoaded
} = commonStore.auth.actions;
const { AUTH_KEY } = constants;
const token = localStorage.getItem(AUTH_KEY);
@ -19,6 +21,7 @@ if (!token) {
getPortalSettings(store.dispatch)
.then(() => store.dispatch(setIsLoaded(true)))
.catch(e => history.push(`/login/error=${e}`));
} else if (!window.location.pathname.includes("confirm/EmailActivation")) {
getUserInfo(store.dispatch)
.then(() => store.dispatch(setIsLoaded(true)))

View File

@ -2,12 +2,15 @@ import { combineReducers } from 'redux';
import settingsReducer from './settings/reducer';
import confirmReducer from './confirm/reducer';
import { store } from 'asc-web-common';
import wizardReducer from './wizard/reducer';
const { reducer: authReducer } = store.auth;
const rootReducer = combineReducers({
auth: authReducer,
settings: settingsReducer,
confirm: confirmReducer
confirm: confirmReducer,
wizard: wizardReducer
});
export default rootReducer;

View File

@ -0,0 +1,103 @@
import { store, api } from "asc-web-common";
const {
setPasswordSettings,
setTimezones,
setPortalCultures,
getPortalSettings,
setWizardComplete
} = store.auth.actions;
export const SET_IS_WIZARD_LOADED = 'SET_IS_WIZARD_LOADED';
export const SET_IS_MACHINE_NAME = 'SET_IS_MACHINE_NAME';
export const SET_IS_LICENSE_REQUIRED = "SET_IS_LICENSE_REQUIRED";
export const SET_LICENSE_UPLOAD = "SET_LICENSE_UPLOAD";
export const RESET_LICENSE_UPLOADED = "RESET_LICENSE_UPLOADED";
export function setIsWizardLoaded(isWizardLoaded) {
return {
type: SET_IS_WIZARD_LOADED,
isWizardLoaded
};
};
export function setMachineName(machineName) {
return {
type: SET_IS_MACHINE_NAME,
machineName
};
}
export function setIsRequiredLicense(isRequired) {
return {
type: SET_IS_LICENSE_REQUIRED,
isRequired
}
}
export function setLicenseUpload(message) {
return {
type: SET_LICENSE_UPLOAD,
message
}
}
export function resetLicenseUploaded() {
return {
type: RESET_LICENSE_UPLOADED
}
}
export function getPortalPasswordSettings(token) {
return dispatch => {
return api.settings.getPortalPasswordSettings(token).then(settings => {
dispatch(setPasswordSettings(settings));
});
};
}
export function getPortalTimezones(token) {
return dispatch => {
return api.settings.getPortalTimezones(token).then(timezones => {
dispatch(setTimezones(timezones));
});
};
}
export function getPortalCultures() {
return dispatch => {
return api.settings.getPortalCultures().then(cultures => {
dispatch(setPortalCultures(cultures));
});
};
}
export function getMachineName(token) {
return dispatch => {
return api.settings.getMachineName(token).then(machineName => {
dispatch(setMachineName(machineName));
});
};
}
export function setPortalOwner(email, pwd, lng, timeZone, confirmKey, analytics) {
return dispatch => {
return api.settings.setPortalOwner(email, pwd, lng, timeZone, confirmKey, analytics)
.then(() => dispatch(setWizardComplete()))
.then(() => getPortalSettings(dispatch))
}
}
export function getIsRequiredLicense() {
return dispatch => {
return api.settings.getIsLicenseRequired()
.then(isRequired => dispatch(setIsRequiredLicense(isRequired)))
}
}
export function setLicense(confirmKey, data) {
return dispatch => {
return api.settings.setLicense(confirmKey, data)
.then(res => dispatch(setLicenseUpload(res)))
}
}

View File

@ -0,0 +1,49 @@
import {
SET_IS_WIZARD_LOADED,
SET_IS_MACHINE_NAME,
SET_IS_LICENSE_REQUIRED,
SET_LICENSE_UPLOAD,
RESET_LICENSE_UPLOADED
} from "./actions";
const initState = {
isWizardLoaded: false,
isLicenseRequired: false,
machineName: 'unknown',
licenseUpload: null
};
const ownerReducer = ( state = initState, action) => {
switch(action.type) {
case SET_IS_WIZARD_LOADED:
return Object.assign({}, state, {
isWizardLoaded: action.isWizardLoaded
});
case SET_IS_MACHINE_NAME:
return Object.assign({}, state, {
machineName: action.machineName
});
case SET_IS_LICENSE_REQUIRED:
return Object.assign({}, state, {
isLicenseRequired: action.isRequired
});
case SET_LICENSE_UPLOAD:
return Object.assign({}, state, {
licenseUpload: action.message
})
case RESET_LICENSE_UPLOADED:
return Object.assign({}, state, {
licenseUpload: null
})
default:
return state;
}
}
export default ownerReducer;

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-common",
"version": "1.0.95",
"version": "1.0.96",
"description": "Ascensio System SIA common components and solutions library",
"license": "AGPL-3.0",
"files": [

View File

@ -1,5 +1,5 @@
import axios from "axios";
import { AUTH_KEY } from "../constants";
import { AUTH_KEY, WIZARD_KEY } from "../constants";
const PREFIX = "api";
const VERSION = "2.0";
@ -22,6 +22,9 @@ client.interceptors.response.use(
return response;
},
error => {
if(localStorage.getItem(WIZARD_KEY))
return;
if (error.response.status === 401) {
setAuthorizationToken();
window.location.href = "/login/error=unauthorized";

View File

@ -26,11 +26,16 @@ export function getSettings() {
return request(options);
}
export function getPortalTimezones() {
return request({
export function getPortalTimezones(confirmKey = null) {
const options = {
method: "get",
url: "/settings/timezones.json"
});
};
if(confirmKey)
options.headers = { confirm: confirmKey };
return request(options);
}
export function setLanguageAndTime(lng, timeZoneID) {
@ -99,4 +104,56 @@ export function getSettings() {
url: `/settings/sendjoininvite`,
data
});
}
export function getMachineName(confirmKey = null) {
const options = {
method: "get",
url: "/settings/machine.json"
};
if ( confirmKey )
options.headers = { confirm: confirmKey };
return request(options);
}
export function setPortalOwner( email, pwd, lng, timeZone, confirmKey = null, analytics ) {
const options = {
method: "put",
url: "/settings/wizard/complete.json",
data: {
email: email,
pwd: pwd,
lng: lng,
timeZone: timeZone,
analytics: analytics
}
}
if ( confirmKey ) {
options.headers = { confirm: confirmKey};
}
return request(options);
}
export function getIsLicenseRequired() {
return request({
method: 'get',
url: '/settings/license/required.json'
})
}
export function setLicense(confirmKey = null, data) {
const options = {
method: "post",
url: `/settings/license`,
data
}
if ( confirmKey ) {
options.headers = { confirm: confirmKey }
}
return request(options);
}

View File

@ -49,7 +49,7 @@ const Header = styled.header`
}
`;
const HeaderUnauth = ({ t, enableAdmMess }) => {
const HeaderUnauth = ({ t, enableAdmMess, wizardToken }) => {
//console.log("Header render");
@ -68,7 +68,7 @@ const HeaderUnauth = ({ t, enableAdmMess }) => {
</div>
<div>
{enableAdmMess && <RecoverAccess t={t} />}
{(enableAdmMess && !wizardToken) && <RecoverAccess t={t} />}
</div>
</Box>
</Header>
@ -79,12 +79,14 @@ HeaderUnauth.displayName = "Header";
HeaderUnauth.propTypes = {
t: PropTypes.func.isRequired,
enableAdmMess: PropTypes.bool.isRequired
enableAdmMess: PropTypes.bool,
wizardToken: PropTypes.string
};
function mapStateToProps(state) {
return {
enableAdmMess: state.auth.settings.enableAdmMess
enableAdmMess: state.auth.settings.enableAdmMess,
wizardToken: state.auth.settings.wizardToken
};
}

View File

@ -16,24 +16,15 @@ const PrivateRoute = ({ component: Component, ...rest }) => {
restricted,
allowForMe,
currentUser,
computedMatch
computedMatch,
wizardToken
} = rest;
const { userId } = computedMatch.params;
const token = localStorage.getItem(AUTH_KEY);
const renderComponent = useCallback(
props => {
if (!token || (isLoaded && !isAuthenticated)) {
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}
if (!isLoaded) {
return (
<PageLayout
@ -44,6 +35,17 @@ const PrivateRoute = ({ component: Component, ...rest }) => {
);
}
if (!token || (isLoaded && !isAuthenticated)) {
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}
if (
!restricted ||
isAdmin ||
@ -66,11 +68,12 @@ const PrivateRoute = ({ component: Component, ...rest }) => {
allowForMe,
currentUser,
userId,
wizardToken,
Component
]
);
// console.log("PrivateRoute render", rest);
console.log("PrivateRoute render", rest);
return <Route {...rest} render={renderComponent} />;
};
@ -79,7 +82,8 @@ function mapStateToProps(state) {
isAuthenticated: state.auth.isAuthenticated,
isLoaded: state.auth.isLoaded,
isAdmin: isAdmin(state.auth.user),
currentUser: state.auth.user
currentUser: state.auth.user,
wizardToken: state.auth.settings.wizardToken
};
}

View File

@ -2,12 +2,15 @@
import React, { useCallback } from 'react';
import { Redirect, Route } from 'react-router-dom';
import { AUTH_KEY } from "../../constants";
import { connect } from "react-redux";
export const PublicRoute = ({ component: Component, ...rest }) => {
const token = localStorage.getItem(AUTH_KEY);
const {wizardToken, wizardCompleted} = rest;
const renderComponent = useCallback(
props => {
props => {
if(token) {
return (
<Redirect
@ -19,9 +22,19 @@ export const PublicRoute = ({ component: Component, ...rest }) => {
);
}
return <Component {...props} />;
}, [token, Component]);
if(wizardToken && !wizardCompleted) {
return (
<Redirect
to={{
pathname: "/wizard",
state: { from: props.location }
}}
/>
);
}
return <Component {...props} />;
}, [token, wizardToken, Component]);
return (
<Route
{...rest}
@ -29,4 +42,12 @@ export const PublicRoute = ({ component: Component, ...rest }) => {
/>
)
};
export default PublicRoute;
function mapStateToProps(state) {
return {
wizardToken: state.auth.settings.wizardToken,
wizardCompleted: state.auth.settings.wizardCompleted
};
}
export default connect(mapStateToProps)(PublicRoute);

View File

@ -1,6 +1,7 @@
export const AUTH_KEY = 'asc_auth_key';
export const LANGUAGE = 'language';
export const ARTICLE_PINNED_KEY = 'asc_article_pinned_key';
export const WIZARD_KEY = 'asc_wizard_key';
/**
* Enum for employee activation status.

View File

@ -15,6 +15,7 @@ export const SET_CURRENT_PRODUCT_ID = "SET_CURRENT_PRODUCT_ID";
export const SET_CURRENT_PRODUCT_HOME_PAGE = "SET_CURRENT_PRODUCT_HOME_PAGE";
export const SET_GREETING_SETTINGS = "SET_GREETING_SETTINGS";
export const SET_CUSTOM_NAMES = "SET_CUSTOM_NAMES";
export const SET_WIZARD_COMPLETED ="SET_WIZARD_COMPLETED";
export function setCurrentUser(user) {
return {
@ -114,6 +115,12 @@ export function setCustomNames(customNames) {
};
}
export function setWizardComplete() {
return {
type: SET_WIZARD_COMPLETED
}
}
export function getUser(dispatch) {
return api.people.getUser()
.then(user => dispatch(setCurrentUser(user)))
@ -181,4 +188,4 @@ export function getPortalPasswordSettings(dispatch, confirmKey = null) {
return api.settings.getPortalPasswordSettings(confirmKey).then(settings => {
dispatch(setPasswordSettings(settings));
});
}
}

View File

@ -1,7 +1,7 @@
import {
SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT, SET_PASSWORD_SETTINGS, SET_NEW_EMAIL,
SET_PORTAL_CULTURES, SET_PORTAL_LANGUAGE_AND_TIME, SET_TIMEZONES, SET_CURRENT_PRODUCT_ID, SET_CURRENT_PRODUCT_HOME_PAGE, SET_GREETING_SETTINGS,
SET_CUSTOM_NAMES } from './actions';
SET_CUSTOM_NAMES, SET_WIZARD_COMPLETED } from './actions';
import isEmpty from "lodash/isEmpty";
import { LANGUAGE, AUTH_KEY } from '../../constants';
@ -30,7 +30,8 @@ const initialState = {
timePattern: "h:mm tt"
},
greetingSettings: 'Web Office Applications',
enableAdmMess: false
enableAdmMess: false,
urlLicense: "https://gnu.org/licenses/gpl-3.0.html"
}
}
@ -102,6 +103,12 @@ const authReducer = (state = initialState, action) => {
return Object.assign({}, initialState, {
settings: state.settings
});
case SET_WIZARD_COMPLETED:
return Object.assign({}, state, {
settings: { ...state.settings, wizardCompleted: true}
})
default:
return state;
}

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-components",
"version": "1.0.374",
"version": "1.0.375",
"description": "Ascensio System SIA component library",
"license": "AGPL-3.0",
"main": "dist/asc-web-components.js",

View File

@ -30,7 +30,7 @@ const hoverCss = css`
`;
// eslint-disable-next-line no-unused-vars
const ButtonWrapper = ({primary, scale, size, isHovered, isClicked, isDisabled, isLoading, label, innerRef, ...props}) => <button ref={innerRef} type="button" {...props}></button>;
const ButtonWrapper = ({primary, scale, size, isHovered, isClicked, isDisabled, isLoading, label, innerRef, minWidth, ...props}) => <button ref={innerRef} type="button" {...props}></button>;
ButtonWrapper.propTypes = {
label: PropTypes.string,

View File

@ -120,7 +120,10 @@ class FileInput extends Component {
fileName: this.inputRef.current.files[0].name,
file: this.inputRef.current.files[0]
}, () => {
if(onInput) onInput(this.state.file);
if(onInput) {
this.inputRef.current.value = '';
onInput(this.state.file);
};
});
}

View File

@ -26,7 +26,6 @@ const StyledInput = styled(SimpleInput)`
line-height: 32px;
flex-direction: row;
flex-wrap: wrap;
@media ${tablet} {
flex-wrap: wrap;
}
@ -48,16 +47,13 @@ const StyledInput = styled(SimpleInput)`
const PasswordProgress = styled.div`
${props => (props.inputWidth ? `width: ${props.inputWidth};` : `flex: auto;`)}
.input-relative {
position: relative;
svg {
overflow: hidden;
vertical-align: middle;
}
}
*,
*::before,
*::after {
@ -68,13 +64,11 @@ const PasswordProgress = styled.div`
const NewPasswordButton = styled.div`
margin: 0 16px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
svg {
overflow: hidden;
vertical-align: middle;
margin-bottom: 4px;
}
:hover {
cursor: pointer;
}
@ -82,7 +76,6 @@ const NewPasswordButton = styled.div`
const CopyLink = styled.div`
margin-top: -6px;
@media ${tablet} {
width: 100%;
margin-left: 0px;
@ -579,6 +572,7 @@ PasswordInput.propTypes = {
tooltipPasswordSpecial: PropTypes.string,
generatorSpecial: PropTypes.string,
NewPasswordButtonVisible: PropTypes.bool,
passwordSettings: PropTypes.object.isRequired,
onValidateInput: PropTypes.func,
@ -613,4 +607,4 @@ PasswordInput.defaultProps = {
simpleView: false
};
export default PasswordInput;
export default PasswordInput;

View File

@ -8,7 +8,6 @@ export { default as Button } from './components/button'
export { default as Calendar } from './components/calendar'
export { default as Checkbox } from './components/checkbox'
export { default as ComboBox } from './components/combobox'
export { default as ContextMenu } from './components/context-menu'
export { default as ContextMenuButton } from './components/context-menu-button'
export { default as CustomScrollbarsVirtualList } from './components/scrollbar/custom-scrollbars-virtual-list'
export { default as DatePicker } from './components/date-picker'
@ -55,4 +54,4 @@ export { default as Tooltip } from './components/tooltip'
export { default as TreeMenu } from './components/tree-menu'
export { default as TreeNode } from './components/tree-menu/sub-components/tree-node'
export { default as utils } from './utils'
export { Icons } from './components/icons'
export { Icons } from './components/icons'

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +1,5 @@
<?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">
@ -129,6 +70,9 @@
<data name="AdministratorNotifySenderTypeName" xml:space="preserve">
<value>Administratorbenachrichtigungen</value>
</data>
<data name="AdminMessageSent" xml:space="preserve">
<value>Ihre Nachricht wurde erfolgreich gesendet. Der Portaladministrator wird Sie kontaktieren.</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Der Link zum Bestätigen der Operation wurde an :email verschickt (die E-Mail-Adresse des Portalbesitzers).</value>
</data>
@ -141,6 +85,15 @@
<data name="CustomNamingPeopleSchema" xml:space="preserve">
<value>Einstellbar</value>
</data>
<data name="EmailAndPasswordIncorrectEmail" xml:space="preserve">
<value>Falsche E-Mail-Adresse</value>
</data>
<data name="EmailAndPasswordIncorrectPromocode" xml:space="preserve">
<value>Ungültiger Werbecode</value>
</data>
<data name="EmailAndPasswordSaved" xml:space="preserve">
<value>Diese Einstellungen wurden erfolgreich gespeichert</value>
</data>
<data name="ErrorAccessDenied" xml:space="preserve">
<value>Keine Berechtigung zum Durchführen dieser Aktion</value>
</data>
@ -153,12 +106,18 @@
<data name="ErrorEmailAlreadyExists" xml:space="preserve">
<value>Ein Benutzer mit dieser E-Mail-Adresse ist bereits registriert</value>
</data>
<data name="ErrorEmailDomainNotAllowed" xml:space="preserve">
<value>Die Registrierung der E-Mails von dieser Domäne ist verboten</value>
</data>
<data name="ErrorEmailEmpty" xml:space="preserve">
<value>Das Feld für die E-Mail-Adresse ist leer</value>
</data>
<data name="ErrorEmailsAreTheSame" xml:space="preserve">
<value>Die aktuelle E-Mail-Adresse und die neue sind gleich</value>
</data>
<data name="ErrorEmptyMessage" xml:space="preserve">
<value>Der Nachrichtentext ist leer</value>
</data>
<data name="ErrorEmptyUploadFileSelected" xml:space="preserve">
<value>Die hochgeladene Datei konnte nicht gefunden werden</value>
</data>
@ -174,6 +133,9 @@
<data name="ErrorNotCorrectEmail" xml:space="preserve">
<value>Ungültige E-Mail-Adresse</value>
</data>
<data name="ErrorNotCorrectTrustedDomain" xml:space="preserve">
<value>Ungültiger Domain-Name</value>
</data>
<data name="ErrorPasswordEmpty" xml:space="preserve">
<value>Das Feld "Kennwort" ist leer.</value>
</data>
@ -195,6 +157,9 @@
<data name="ErrorPasswordNoUpperCase" xml:space="preserve">
<value>Großbuchstaben</value>
</data>
<data name="ErrorRequestLimitExceeded" xml:space="preserve">
<value>Anforderungslimit wurde überschritten</value>
</data>
<data name="ErrorUserNotFound" xml:space="preserve">
<value>Der Benutzer konnte nicht gefunden werden</value>
</data>
@ -213,12 +178,33 @@
<data name="FileSizePostfix" xml:space="preserve">
<value>Byte,KB,MB,GB,TB</value>
</data>
<data name="FinishInviteJoinEmailMessage" xml:space="preserve">
<value>Der Link zum Bestätigen Ihres Kontos wurde an die angegebene E-Mail-Adresse geschickt</value>
</data>
<data name="LdapSettingsErrorCantSaveLdapSettings" xml:space="preserve">
<value>Fehler des Servers beim Speichern der Einstellungsdaten</value>
</data>
<data name="LdapSettingsTooManyOperations" xml:space="preserve">
<value>Zu viele LDAP-Operationen.</value>
</data>
<data name="LicenseError" xml:space="preserve">
<value>Lizenz ist nicht korrekt</value>
</data>
<data name="LicenseErrorExpired" xml:space="preserve">
<value>Lizenz ist abgelaufen </value>
</data>
<data name="LicenseErrorPortal" xml:space="preserve">
<value>Die Anzahl der Portale übersteigt die zugelassene Lizenz</value>
</data>
<data name="LicenseErrorQuota" xml:space="preserve">
<value>Die Anzahl der Benutzer übersteigt die zugelassene Lizenz</value>
</data>
<data name="LicenseUploaded" xml:space="preserve">
<value>Erfolgreich hochgeladen </value>
</data>
<data name="LicenseUploadedOverdue" xml:space="preserve">
<value>Erfolgreich hochgeladen. {0}Support und Updates sind ab dieser Lizenz nicht mehr verfügbar {2}.{1}</value>
</data>
<data name="MessageEmailChangeInstuctionsSentOnEmail" xml:space="preserve">
<value>Die Hinweise für die Änderung der E-Mail-Adresse wurden erfolgreich gesendet</value>
</data>
@ -234,6 +220,9 @@
<data name="PersonalFreeSpaceException" xml:space="preserve">
<value>Der verfügbare Speicherplatz ist überschritten</value>
</data>
<data name="PortalSecurity" xml:space="preserve">
<value>Zugang zum Portal</value>
</data>
<data name="ProfileRemoved" xml:space="preserve">
<value>Das Profil wurde gelöscht</value>
</data>

View File

@ -1,64 +1,5 @@
<?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">
@ -129,6 +70,9 @@
<data name="AdministratorNotifySenderTypeName" xml:space="preserve">
<value>Nofificaciones de administradores</value>
</data>
<data name="AdminMessageSent" xml:space="preserve">
<value>Su mensaje fue enviado con éxito. El administrador del portal se pondrá en contacto con usted.</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Un enlace para confirmar la operación ha sido enviada al :correo electrónico (la dirección del correo electrónico del propietario del portal).</value>
</data>
@ -141,6 +85,15 @@
<data name="CustomNamingPeopleSchema" xml:space="preserve">
<value>Personalizado</value>
</data>
<data name="EmailAndPasswordIncorrectEmail" xml:space="preserve">
<value>Dirección de correo electrónico incorrecta</value>
</data>
<data name="EmailAndPasswordIncorrectPromocode" xml:space="preserve">
<value>Código de promocional incorrecto</value>
</data>
<data name="EmailAndPasswordSaved" xml:space="preserve">
<value>Estos ajustes han sido guardados con éxito</value>
</data>
<data name="ErrorAccessDenied" xml:space="preserve">
<value>No hay permisos para realizar esta acción</value>
</data>
@ -153,12 +106,18 @@
<data name="ErrorEmailAlreadyExists" xml:space="preserve">
<value>{!User} con este correo electrónico ya está registrado</value>
</data>
<data name="ErrorEmailDomainNotAllowed" xml:space="preserve">
<value>No se permite el registro para los correos electrónicos de este dominio </value>
</data>
<data name="ErrorEmailEmpty" xml:space="preserve">
<value>Campo de correo electrónico está vacío</value>
</data>
<data name="ErrorEmailsAreTheSame" xml:space="preserve">
<value>El correo electrónico actual y el nuevo correo electrónico son iguales</value>
</data>
<data name="ErrorEmptyMessage" xml:space="preserve">
<value>Texto del mensaje está vacío</value>
</data>
<data name="ErrorEmptyUploadFileSelected" xml:space="preserve">
<value>Imposible encontrar el archivo subido</value>
</data>
@ -174,6 +133,9 @@
<data name="ErrorNotCorrectEmail" xml:space="preserve">
<value>E-mail inválido</value>
</data>
<data name="ErrorNotCorrectTrustedDomain" xml:space="preserve">
<value>Nombre de dominio inválido</value>
</data>
<data name="ErrorPasswordEmpty" xml:space="preserve">
<value>La contraseña está vacía.</value>
</data>
@ -195,6 +157,9 @@
<data name="ErrorPasswordNoUpperCase" xml:space="preserve">
<value>letras mayúsculas</value>
</data>
<data name="ErrorRequestLimitExceeded" xml:space="preserve">
<value>Límite de solicitudes es excedido</value>
</data>
<data name="ErrorUserNotFound" xml:space="preserve">
<value>Usuario no ha sido encontrado</value>
</data>
@ -213,12 +178,33 @@
<data name="FileSizePostfix" xml:space="preserve">
<value>bytes,KB,MB,GB,TB</value>
</data>
<data name="FinishInviteJoinEmailMessage" xml:space="preserve">
<value>El enlace para confirmar su cuenta ha sido enviado al e-mail especificado. </value>
</data>
<data name="LdapSettingsErrorCantSaveLdapSettings" xml:space="preserve">
<value>El servidor no pudo guardar ajustes.</value>
</data>
<data name="LdapSettingsTooManyOperations" xml:space="preserve">
<value>Demasiado muchas operaciones LDAP.</value>
</data>
<data name="LicenseError" xml:space="preserve">
<value>Licencia no correcta</value>
</data>
<data name="LicenseErrorExpired" xml:space="preserve">
<value>Licencia ha expirado</value>
</data>
<data name="LicenseErrorPortal" xml:space="preserve">
<value>Número de portales excede el número permitido por la licencia</value>
</data>
<data name="LicenseErrorQuota" xml:space="preserve">
<value>Número de usuarios excede el número permitido por la licencia</value>
</data>
<data name="LicenseUploaded" xml:space="preserve">
<value>Subido con éxito</value>
</data>
<data name="LicenseUploadedOverdue" xml:space="preserve">
<value>Se ha subido correctamente. {0}Soporte y atualizaciones no están disponibles para esta licencia desde {2}.{1}</value>
</data>
<data name="MessageEmailChangeInstuctionsSentOnEmail" xml:space="preserve">
<value>Las instrucciones para cambiar el correo electrónico se han enviado con éxito</value>
</data>
@ -234,6 +220,9 @@
<data name="PersonalFreeSpaceException" xml:space="preserve">
<value>Cuota de espacio en disco excedida </value>
</data>
<data name="PortalSecurity" xml:space="preserve">
<value>Acceso a portal</value>
</data>
<data name="ProfileRemoved" xml:space="preserve">
<value>Perfil ha sido eliminado</value>
</data>

View File

@ -1,64 +1,5 @@
<?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">
@ -129,6 +70,9 @@
<data name="AdministratorNotifySenderTypeName" xml:space="preserve">
<value>Notifications d'administrateurs</value>
</data>
<data name="AdminMessageSent" xml:space="preserve">
<value>Votre message a été envoyé avec succès. Vous serez contacté par l'administrateur du portail.</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Un lien pour confirmer l'opération a été envoyé à :e-mail (l'adresse e-mail du propriétaire du portail).</value>
</data>
@ -141,6 +85,15 @@
<data name="CustomNamingPeopleSchema" xml:space="preserve">
<value>Personnel</value>
</data>
<data name="EmailAndPasswordIncorrectEmail" xml:space="preserve">
<value>Adresse e-mail incorrecte</value>
</data>
<data name="EmailAndPasswordIncorrectPromocode" xml:space="preserve">
<value>Le code promo est incorrect </value>
</data>
<data name="EmailAndPasswordSaved" xml:space="preserve">
<value>Ces paramètres ont été bien enregistrés</value>
</data>
<data name="ErrorAccessDenied" xml:space="preserve">
<value>Pas d'autorisation pour effectuer cette action</value>
</data>
@ -153,12 +106,18 @@
<data name="ErrorEmailAlreadyExists" xml:space="preserve">
<value>{!User} avec la même adresse e-mail est déjà inscrit</value>
</data>
<data name="ErrorEmailDomainNotAllowed" xml:space="preserve">
<value>Adresses provenant de ce domaine ne sont pas acceptées pour l'enregistrement</value>
</data>
<data name="ErrorEmailEmpty" xml:space="preserve">
<value>Le champ Email est vide</value>
</data>
<data name="ErrorEmailsAreTheSame" xml:space="preserve">
<value>L'adresse email actuelle et la nouvelle adresse sont les mêmes</value>
</data>
<data name="ErrorEmptyMessage" xml:space="preserve">
<value>Texte du message est vide</value>
</data>
<data name="ErrorEmptyUploadFileSelected" xml:space="preserve">
<value>Le fichier chargé ne peut pas être trouvé</value>
</data>
@ -174,6 +133,9 @@
<data name="ErrorNotCorrectEmail" xml:space="preserve">
<value>Adresse email non valide</value>
</data>
<data name="ErrorNotCorrectTrustedDomain" xml:space="preserve">
<value>Nom de domaine non valide</value>
</data>
<data name="ErrorPasswordEmpty" xml:space="preserve">
<value>Le champ "Mot de passe" est vide.</value>
</data>
@ -195,6 +157,9 @@
<data name="ErrorPasswordNoUpperCase" xml:space="preserve">
<value>lettres majuscules</value>
</data>
<data name="ErrorRequestLimitExceeded" xml:space="preserve">
<value>Le délai de requête est dépassé</value>
</data>
<data name="ErrorUserNotFound" xml:space="preserve">
<value>Il est impossible de trouver l'utilisateur</value>
</data>
@ -215,12 +180,33 @@
</value>
</data>
<data name="FinishInviteJoinEmailMessage" xml:space="preserve">
<value>Le lien pour confirmer votre compte a été envoyé à l'adresse email indiquée</value>
</data>
<data name="LdapSettingsErrorCantSaveLdapSettings" xml:space="preserve">
<value>Le serveur n'a pas pu enregistrer des paramètres.</value>
</data>
<data name="LdapSettingsTooManyOperations" xml:space="preserve">
<value>Trop d'opérations LDAP.</value>
</data>
<data name="LicenseError" xml:space="preserve">
<value>La licence n'est pas correcte</value>
</data>
<data name="LicenseErrorExpired" xml:space="preserve">
<value>La licence a expiré</value>
</data>
<data name="LicenseErrorPortal" xml:space="preserve">
<value>Le nombre de portails a dépassé la limite autorisée par la licence</value>
</data>
<data name="LicenseErrorQuota" xml:space="preserve">
<value>Le nombre d'utilisateurs a dépassé la limite autorisée par la licence</value>
</data>
<data name="LicenseUploaded" xml:space="preserve">
<value>Téléchargé avec succès</value>
</data>
<data name="LicenseUploadedOverdue" xml:space="preserve">
<value>Téléchargé avec succès. {0}Support et mises à jour ne sont plus disponibles pour cette licence depuis {2}.{1}</value>
</data>
<data name="MessageEmailChangeInstuctionsSentOnEmail" xml:space="preserve">
<value>Les instructions de changement d'émail ont été envoyées avec succès</value>
</data>
@ -236,6 +222,9 @@
<data name="PersonalFreeSpaceException" xml:space="preserve">
<value>Quota d'espace disque est dépassé</value>
</data>
<data name="PortalSecurity" xml:space="preserve">
<value>Accès au portail</value>
</data>
<data name="ProfileRemoved" xml:space="preserve">
<value>Le profil a été supprimé</value>
</data>

View File

@ -1,64 +1,5 @@
<?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">
@ -129,6 +70,9 @@
<data name="AdministratorNotifySenderTypeName" xml:space="preserve">
<value>Notifiche di amministratore</value>
</data>
<data name="AdminMessageSent" xml:space="preserve">
<value>Il tuo messaggio è stato inviato con successo. Sarai contattato dall'amministratore del portale.</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Un collegamento per confermare l'operazione è stato inviato all'indirizzo :email (l'indirizzo email del proprietario del portale).</value>
</data>
@ -141,6 +85,15 @@
<data name="CustomNamingPeopleSchema" xml:space="preserve">
<value>Personalizzato</value>
</data>
<data name="EmailAndPasswordIncorrectEmail" xml:space="preserve">
<value>Indirizzo email non corretto</value>
</data>
<data name="EmailAndPasswordIncorrectPromocode" xml:space="preserve">
<value>Codice promozionale errato</value>
</data>
<data name="EmailAndPasswordSaved" xml:space="preserve">
<value>Queste impostazioni sono state salvate con successo</value>
</data>
<data name="ErrorAccessDenied" xml:space="preserve">
<value>Non si dispone di permessi sufficienti per effettuare questa azione</value>
</data>
@ -153,12 +106,18 @@
<data name="ErrorEmailAlreadyExists" xml:space="preserve">
<value>Un {!user} con questo indirizzo email è già registrato</value>
</data>
<data name="ErrorEmailDomainNotAllowed" xml:space="preserve">
<value>Impossibile usare gli indirizzi email da questo dominio per iscrizione</value>
</data>
<data name="ErrorEmailEmpty" xml:space="preserve">
<value>Il campo Email è vuoto</value>
</data>
<data name="ErrorEmailsAreTheSame" xml:space="preserve">
<value>L'indirizzo email corrente coincide con quello nuovo.</value>
</data>
<data name="ErrorEmptyMessage" xml:space="preserve">
<value>Il testo del messaggio è vuoto</value>
</data>
<data name="ErrorEmptyUploadFileSelected" xml:space="preserve">
<value>E' impossibile trovare il file caricato</value>
</data>
@ -174,6 +133,9 @@
<data name="ErrorNotCorrectEmail" xml:space="preserve">
<value>Indirizzo email non valido</value>
</data>
<data name="ErrorNotCorrectTrustedDomain" xml:space="preserve">
<value>Nome dominio non valido</value>
</data>
<data name="ErrorPasswordEmpty" xml:space="preserve">
<value>Il campo Password è vuoto.</value>
</data>
@ -195,6 +157,9 @@
<data name="ErrorPasswordNoUpperCase" xml:space="preserve">
<value>lettere maiuscole</value>
</data>
<data name="ErrorRequestLimitExceeded" xml:space="preserve">
<value>Il limite di richieste è stato superato</value>
</data>
<data name="ErrorUserNotFound" xml:space="preserve">
<value>L'utente non è stato trovato</value>
</data>
@ -213,12 +178,33 @@
<data name="FileSizePostfix" xml:space="preserve">
<value>byte,KB,MB,GB,TB</value>
</data>
<data name="FinishInviteJoinEmailMessage" xml:space="preserve">
<value>Il collegamento per confermare il tuo account è stato inviato all'indirizzo email specificato</value>
</data>
<data name="LdapSettingsErrorCantSaveLdapSettings" xml:space="preserve">
<value>Il server non è riuscito a salvare le impostazioni.</value>
</data>
<data name="LdapSettingsTooManyOperations" xml:space="preserve">
<value>Troppe operazioni LDAP</value>
</data>
<data name="LicenseError" xml:space="preserve">
<value>Licenza errata</value>
</data>
<data name="LicenseErrorExpired" xml:space="preserve">
<value>La licenza è scaduta</value>
</data>
<data name="LicenseErrorPortal" xml:space="preserve">
<value>Il numero di portali supera quello consentito dalla licenza</value>
</data>
<data name="LicenseErrorQuota" xml:space="preserve">
<value>Il numero di utenti supera quello consentito dalla licenza</value>
</data>
<data name="LicenseUploaded" xml:space="preserve">
<value>Caricato con successo</value>
</data>
<data name="LicenseUploadedOverdue" xml:space="preserve">
<value>Caricato con successo. {0}Il supporto e gli aggiornamenti non sono disponibili per questa licenza dal {2}.{1}</value>
</data>
<data name="MessageEmailChangeInstuctionsSentOnEmail" xml:space="preserve">
<value>Le istruzioni di modifica dell'email sono state inviate correttamente</value>
</data>
@ -234,6 +220,9 @@
<data name="PersonalFreeSpaceException" xml:space="preserve">
<value>Quota di spazio su disco superata</value>
</data>
<data name="PortalSecurity" xml:space="preserve">
<value>Accesso al portale</value>
</data>
<data name="ProfileRemoved" xml:space="preserve">
<value>Il profilo è stato eliminato</value>
</data>

View File

@ -1,64 +1,5 @@
<?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">
@ -129,6 +70,9 @@
<data name="AdministratorNotifySenderTypeName" xml:space="preserve">
<value>Administrator Notifications</value>
</data>
<data name="AdminMessageSent" xml:space="preserve">
<value>Your message was successfully sent. You will be contacted by the portal administrator.</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>A link to confirm the operation has been sent to :email (the email address of the portal owner).</value>
</data>
@ -141,6 +85,15 @@
<data name="CustomNamingPeopleSchema" xml:space="preserve">
<value>Custom</value>
</data>
<data name="EmailAndPasswordIncorrectEmail" xml:space="preserve">
<value>Incorrect email address</value>
</data>
<data name="EmailAndPasswordIncorrectPromocode" xml:space="preserve">
<value>Incorrect promo code</value>
</data>
<data name="EmailAndPasswordSaved" xml:space="preserve">
<value>These settings have been successfully saved</value>
</data>
<data name="ErrorAccessDenied" xml:space="preserve">
<value>No permissions to perform this action</value>
</data>
@ -153,12 +106,18 @@
<data name="ErrorEmailAlreadyExists" xml:space="preserve">
<value>{!User} with this email is already registered</value>
</data>
<data name="ErrorEmailDomainNotAllowed" xml:space="preserve">
<value>Emails from this domain are not allowed for registration</value>
</data>
<data name="ErrorEmailEmpty" xml:space="preserve">
<value>Email field is empty</value>
</data>
<data name="ErrorEmailsAreTheSame" xml:space="preserve">
<value>The current email and the new email are the same</value>
</data>
<data name="ErrorEmptyMessage" xml:space="preserve">
<value>Message text is empty</value>
</data>
<data name="ErrorEmptyUploadFileSelected" xml:space="preserve">
<value>The uploaded file could not be found</value>
</data>
@ -174,6 +133,9 @@
<data name="ErrorNotCorrectEmail" xml:space="preserve">
<value>Incorrect email</value>
</data>
<data name="ErrorNotCorrectTrustedDomain" xml:space="preserve">
<value>Invalid domain name</value>
</data>
<data name="ErrorPasswordEmpty" xml:space="preserve">
<value>The password is empty.</value>
</data>
@ -195,6 +157,9 @@
<data name="ErrorPasswordNoUpperCase" xml:space="preserve">
<value>capital letters</value>
</data>
<data name="ErrorRequestLimitExceeded" xml:space="preserve">
<value>Request limit is exceeded</value>
</data>
<data name="ErrorUserNotFound" xml:space="preserve">
<value>The user could not be found</value>
</data>
@ -213,12 +178,33 @@
<data name="FileSizePostfix" xml:space="preserve">
<value>bytes,KB,MB,GB,TB</value>
</data>
<data name="FinishInviteJoinEmailMessage" xml:space="preserve">
<value>The link to confirm your account has been sent to the specified email</value>
</data>
<data name="LdapSettingsErrorCantSaveLdapSettings" xml:space="preserve">
<value>The server could not save settings.</value>
</data>
<data name="LdapSettingsTooManyOperations" xml:space="preserve">
<value>Too many LDAP operations.</value>
</data>
<data name="LicenseError" xml:space="preserve">
<value>License is not correct</value>
</data>
<data name="LicenseErrorExpired" xml:space="preserve">
<value>License has expired</value>
</data>
<data name="LicenseErrorPortal" xml:space="preserve">
<value>The number of portals exceeds the one allowed by the license</value>
</data>
<data name="LicenseErrorQuota" xml:space="preserve">
<value>The number of users exceeds the one allowed by the license</value>
</data>
<data name="LicenseUploaded" xml:space="preserve">
<value>Uploaded successfully</value>
</data>
<data name="LicenseUploadedOverdue" xml:space="preserve">
<value>Uploaded successfully. {0}Support and updates are not available for this license since {2}.{1}</value>
</data>
<data name="MessageEmailChangeInstuctionsSentOnEmail" xml:space="preserve">
<value>The email change instructions have been successfuly sent</value>
</data>
@ -234,6 +220,9 @@
<data name="PersonalFreeSpaceException" xml:space="preserve">
<value>Disk space quota exceeded</value>
</data>
<data name="PortalSecurity" xml:space="preserve">
<value>Portal Access</value>
</data>
<data name="ProfileRemoved" xml:space="preserve">
<value>Profile has been removed</value>
</data>

View File

@ -1,64 +1,5 @@
<?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">
@ -129,6 +70,9 @@
<data name="AdministratorNotifySenderTypeName" xml:space="preserve">
<value>Извещения администраторов</value>
</data>
<data name="AdminMessageSent" xml:space="preserve">
<value>Ваше сообщение успешно отправлено. Администратор портала с Вами свяжется.</value>
</data>
<data name="ChangePortalOwnerMsg" xml:space="preserve">
<value>Ссылка для подтверждения операции была отправлена на :email (адрес электронной почты владельца портала).</value>
</data>
@ -141,6 +85,15 @@
<data name="CustomNamingPeopleSchema" xml:space="preserve">
<value>Пользовательский</value>
</data>
<data name="EmailAndPasswordIncorrectEmail" xml:space="preserve">
<value>Некорректный адрес электронной почты</value>
</data>
<data name="EmailAndPasswordIncorrectPromocode" xml:space="preserve">
<value>Неверный промо-код</value>
</data>
<data name="EmailAndPasswordSaved" xml:space="preserve">
<value>Эти параметры были успешно сохранены</value>
</data>
<data name="ErrorAccessDenied" xml:space="preserve">
<value>Недостаточно прав для выполнения данной операции</value>
</data>
@ -153,12 +106,18 @@
<data name="ErrorEmailAlreadyExists" xml:space="preserve">
<value>{!User} с таким адресом email уже зарегистрирован</value>
</data>
<data name="ErrorEmailDomainNotAllowed" xml:space="preserve">
<value>Адреса с этого домена не разрешены для регистрации</value>
</data>
<data name="ErrorEmailEmpty" xml:space="preserve">
<value>Не заполнено поле адреса электронной почты</value>
</data>
<data name="ErrorEmailsAreTheSame" xml:space="preserve">
<value>Текущий адрес электронной почты совпадает с новым адресом</value>
</data>
<data name="ErrorEmptyMessage" xml:space="preserve">
<value>Отсутствует текст сообщения</value>
</data>
<data name="ErrorEmptyUploadFileSelected" xml:space="preserve">
<value>Загружаемый файл не найден</value>
</data>
@ -174,6 +133,9 @@
<data name="ErrorNotCorrectEmail" xml:space="preserve">
<value>Неправильный email</value>
</data>
<data name="ErrorNotCorrectTrustedDomain" xml:space="preserve">
<value>Неправильное имя домена</value>
</data>
<data name="ErrorPasswordEmpty" xml:space="preserve">
<value>Поле Пароль не заполнено.</value>
</data>
@ -195,6 +157,9 @@
<data name="ErrorPasswordNoUpperCase" xml:space="preserve">
<value>заглавные буквы</value>
</data>
<data name="ErrorRequestLimitExceeded" xml:space="preserve">
<value>Превышен лимит запросов</value>
</data>
<data name="ErrorUserNotFound" xml:space="preserve">
<value>Пользователь не найден</value>
</data>
@ -213,12 +178,33 @@
<data name="FileSizePostfix" xml:space="preserve">
<value>байт,Кб,Мб,Гб,Тб</value>
</data>
<data name="FinishInviteJoinEmailMessage" xml:space="preserve">
<value>На указанный email была выслана ссылка для подтверждения новой учетной записи</value>
</data>
<data name="LdapSettingsErrorCantSaveLdapSettings" xml:space="preserve">
<value>Серверу не удалось сохранить настройки.</value>
</data>
<data name="LdapSettingsTooManyOperations" xml:space="preserve">
<value>Слишком много операций LDAP.</value>
</data>
<data name="LicenseError" xml:space="preserve">
<value>Некорректная лицензия</value>
</data>
<data name="LicenseErrorExpired" xml:space="preserve">
<value>Истек срок действия лицензии</value>
</data>
<data name="LicenseErrorPortal" xml:space="preserve">
<value>Превышено количество порталов, разрешенных по лицензии</value>
</data>
<data name="LicenseErrorQuota" xml:space="preserve">
<value>Превышено количество пользователей, разрешенных по лицензии</value>
</data>
<data name="LicenseUploaded" xml:space="preserve">
<value>Успешно загружено</value>
</data>
<data name="LicenseUploadedOverdue" xml:space="preserve">
<value>Успешно загружено. {0}Поддержка и обновления недоступны для этой лицензии с {2}.{1}</value>
</data>
<data name="MessageEmailChangeInstuctionsSentOnEmail" xml:space="preserve">
<value>Инструкции по изменению электронной почты были успешно отправлены</value>
</data>
@ -234,6 +220,9 @@
<data name="PersonalFreeSpaceException" xml:space="preserve">
<value>Превышена квота на размер дискового пространства</value>
</data>
<data name="PortalSecurity" xml:space="preserve">
<value>Доступ к порталу</value>
</data>
<data name="ProfileRemoved" xml:space="preserve">
<value>Профиль удален</value>
</data>

View File

@ -69,6 +69,33 @@ namespace ASC.Web.Core.PublicResources {
}
}
/// <summary>
/// Looks up a localized string similar to License expired or user quota does not match the license.
/// </summary>
public static string LicenseException {
get {
return ResourceManager.GetString("LicenseException", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to License key is not correct.
/// </summary>
public static string LicenseKeyNotCorrect {
get {
return ResourceManager.GetString("LicenseKeyNotCorrect", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You must enter a license key.
/// </summary>
public static string LicenseKeyNotFound {
get {
return ResourceManager.GetString("LicenseKeyNotFound", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to month.
/// </summary>

View File

@ -61,6 +61,15 @@
<data name="BackupSpaceExceed" xml:space="preserve">
<value>Die Backup-Funktion ist nicht verfügbar, weil der Speicherplatz überschreitet {0}. Gehen Sie zum {1}Abschnitt "Statistik"{2} um besetzten Speicherplatz zu überprüfen.</value>
</data>
<data name="LicenseException" xml:space="preserve">
<value>Lizenz ist abgelaufen oder Benutzer-Quote zusammenfällt die Lizenz nicht.</value>
</data>
<data name="LicenseKeyNotCorrect" xml:space="preserve">
<value>Lizenzschlüssel ist nicht korrekt</value>
</data>
<data name="LicenseKeyNotFound" xml:space="preserve">
<value>Sie müssen Lizenzschlüssel einsetzen</value>
</data>
<data name="TariffPerMonth" xml:space="preserve">
<value>Monat</value>
</data>

View File

@ -61,6 +61,15 @@
<data name="BackupSpaceExceed" xml:space="preserve">
<value>La función de Backup no está disponible porque el espacio de almacenamiento excede {0}. Pase a la sección {1}Estadística{2} para obtener información sobre espacio usado.</value>
</data>
<data name="LicenseException" xml:space="preserve">
<value>Licencia ha caducado o cuota de los usuarios no coincide con la licencia</value>
</data>
<data name="LicenseKeyNotCorrect" xml:space="preserve">
<value>La clave de la licencia no es correcta</value>
</data>
<data name="LicenseKeyNotFound" xml:space="preserve">
<value>Tiene que meter la clave de la licencia</value>
</data>
<data name="TariffPerMonth" xml:space="preserve">
<value>mes</value>
</data>

View File

@ -61,6 +61,15 @@
<data name="BackupSpaceExceed" xml:space="preserve">
<value>La fonction de sauvegarde n'est pas disponible car l'espace de stockage dépasse {0}. Passez à la section {1}Statistique{2} pour vérifier l'espace de stockage utilisé.</value>
</data>
<data name="LicenseException" xml:space="preserve">
<value>La licence expirée ou le quota de l'utilisateur ne correspond pas à la licence</value>
</data>
<data name="LicenseKeyNotCorrect" xml:space="preserve">
<value>Clé de licence est pas correcte</value>
</data>
<data name="LicenseKeyNotFound" xml:space="preserve">
<value>Vous devez entrer une clé de licence</value>
</data>
<data name="TariffPerMonth" xml:space="preserve">
<value>mois</value>
</data>

View File

@ -61,6 +61,15 @@
<data name="BackupSpaceExceed" xml:space="preserve">
<value>La funzione di backup non è disponibile, siccome lo spazio di archiviazione supera {0}. Vai alla {1}sezione Statistiche{2} per verificare lo spazio di archiviazione utilizzato.</value>
</data>
<data name="LicenseException" xml:space="preserve">
<value>Licenza è scaduta oppure il numero di utenti non corrisponde alla licenza</value>
</data>
<data name="LicenseKeyNotCorrect" xml:space="preserve">
<value>La chiave della licenza non è corretta</value>
</data>
<data name="LicenseKeyNotFound" xml:space="preserve">
<value>Devi inserire una chiave di licenza</value>
</data>
<data name="TariffPerMonth" xml:space="preserve">
<value>mese</value>
</data>

View File

@ -61,6 +61,15 @@
<data name="BackupSpaceExceed" xml:space="preserve">
<value>The Backup function is not available as the storage space exceeds {0}. Go to the {1}Statistics section{2} to check the storage space used.</value>
</data>
<data name="LicenseException" xml:space="preserve">
<value>License expired or user quota does not match the license</value>
</data>
<data name="LicenseKeyNotCorrect" xml:space="preserve">
<value>License key is not correct</value>
</data>
<data name="LicenseKeyNotFound" xml:space="preserve">
<value>You must enter a license key</value>
</data>
<data name="TariffPerMonth" xml:space="preserve">
<value>month</value>
</data>

View File

@ -61,6 +61,15 @@
<data name="BackupSpaceExceed" xml:space="preserve">
<value>Функция резервного копирования недоступна, так как дисковое пространство превышает {0}. Перейдите в {1}раздел "Статистика"{2}, чтобы проверить занятое дисковое пространство.</value>
</data>
<data name="LicenseException" xml:space="preserve">
<value>Истек срок действия лицензии или количество пользователей не соответствует лицензии</value>
</data>
<data name="LicenseKeyNotCorrect" xml:space="preserve">
<value>Некорректный лицензионный ключ</value>
</data>
<data name="LicenseKeyNotFound" xml:space="preserve">
<value>Необходимо ввести лицензионный ключ</value>
</data>
<data name="TariffPerMonth" xml:space="preserve">
<value>месяц</value>
</data>

View File

@ -178,7 +178,7 @@ namespace ASC.Web.Studio.Core
DisplayPersonalBanners = GetAppSettings("web.display.personal.banners", false);
ShareTwitterUrl = GetAppSettings("web.share.twitter", "https://twitter.com/intent/tweet?text={0}");
ShareFacebookUrl = GetAppSettings("web.share.facebook", "");
ControlPanelUrl = GetAppSettings("web.controlpanel.url", "");
ControlPanelUrl = GetAppSettings("web:controlpanel:url", "");
FontOpenSansUrl = GetAppSettings("web.font.opensans.url", "");
VoipEnabled = GetAppSettings("voip.enabled", "false");
StartProductList = GetAppSettings("web.start.product.list", "");

View File

@ -25,8 +25,9 @@
using System;
using System.Globalization;
using System.Globalization;
using System.Text.Json.Serialization;
using ASC.Core.Common.Settings;
namespace ASC.Web.Studio.UserControls.Management
@ -34,17 +35,17 @@ namespace ASC.Web.Studio.UserControls.Management
[Serializable]
public class TariffSettings : ISettings
{
private static readonly CultureInfo CultureInfo = CultureInfo.CreateSpecificCulture("en-US");
private static readonly CultureInfo CultureInfo = CultureInfo.CreateSpecificCulture("en-US");
[JsonPropertyName("HideRecommendation")]
public bool HideBuyRecommendationSetting { get; set; }
public bool HideBuyRecommendationSetting { get; set; }
[JsonPropertyName("HideNotify")]
public bool HideNotifySetting { get; set; }
public bool HideNotifySetting { get; set; }
[JsonPropertyName("HidePricingPage")]
public bool HidePricingPageForUsers { get; set; }
public bool HidePricingPageForUsers { get; set; }
[JsonPropertyName("LicenseAccept")]
public string LicenseAcceptSetting { get; set; }
@ -65,54 +66,55 @@ namespace ASC.Web.Studio.UserControls.Management
}
//TODO: Need to be returned when needed
//public bool HideRecommendation
//{
// get { return LoadForCurrentUser().HideBuyRecommendationSetting; }
// set
// {
// var tariffSettings = LoadForCurrentUser();
// tariffSettings.HideBuyRecommendationSetting = value;
// tariffSettings.SaveForCurrentUser();
// }
//}
public static bool GetHideRecommendation(SettingsManager settingsManager)
{
return settingsManager.LoadForCurrentUser<TariffSettings>().HideBuyRecommendationSetting;
}
public static void SetHideRecommendation(SettingsManager settingsManager, bool newVal)
{
var tariffSettings = settingsManager.LoadForCurrentUser<TariffSettings>();
tariffSettings.HideBuyRecommendationSetting = newVal;
settingsManager.SaveForCurrentUser(tariffSettings);
}
public static bool GetHideNotify(SettingsManager settingsManager)
{
return settingsManager.LoadForCurrentUser<TariffSettings>().HideNotifySetting;
}
public static void SetHideNotify(SettingsManager settingsManager, bool newVal)
{
var tariffSettings = settingsManager.LoadForCurrentUser<TariffSettings>();
tariffSettings.HideNotifySetting = newVal;
settingsManager.SaveForCurrentUser(tariffSettings);
}
//public bool HideNotify
//{
// get { return LoadForCurrentUser().HideNotifySetting; }
// set
// {
// var tariffSettings = LoadForCurrentUser();
// tariffSettings.HideNotifySetting = value;
// tariffSettings.SaveForCurrentUser();
// }
//}
public static bool GetHidePricingPage(SettingsManager settingsManager)
{
return settingsManager.Load<TariffSettings>().HidePricingPageForUsers;
}
//public bool HidePricingPage
//{
// get { return Load().HidePricingPageForUsers; }
// set
// {
// var tariffSettings = Load();
// tariffSettings.HidePricingPageForUsers = value;
// tariffSettings.Save();
// }
//}
public static void SetHidePricingPage(SettingsManager settingsManager, bool newVal)
{
var tariffSettings = settingsManager.Load<TariffSettings>();
tariffSettings.HidePricingPageForUsers = newVal;
settingsManager.Save<TariffSettings>(tariffSettings);
}
//public bool LicenseAccept
//{
// get
// {
// return !DateTime.MinValue.ToString(CultureInfo).Equals(LoadForDefaultTenant().LicenseAcceptSetting);
// }
// set
// {
// var tariffSettings = LoadForDefaultTenant();
// if (DateTime.MinValue.ToString(CultureInfo).Equals(tariffSettings.LicenseAcceptSetting))
// {
// tariffSettings.LicenseAcceptSetting = DateTime.UtcNow.ToString(CultureInfo);
// tariffSettings.SaveForDefaultTenant();
// }
// }
//}
public static bool GetLicenseAccept(SettingsManager settingsManager)
{
return !DateTime.MinValue.ToString(CultureInfo).Equals(settingsManager.LoadForDefaultTenant<TariffSettings>().LicenseAcceptSetting);
}
public static void SetLicenseAccept(SettingsManager settingsManager)
{
var tariffSettings = settingsManager.LoadForDefaultTenant<TariffSettings>();
if (DateTime.MinValue.ToString(CultureInfo).Equals(tariffSettings.LicenseAcceptSetting))
{
tariffSettings.LicenseAcceptSetting = DateTime.UtcNow.ToString(CultureInfo);
settingsManager.SaveForDefaultTenant(tariffSettings);
}
}
}
}

View File

@ -521,10 +521,15 @@ namespace ASC.Web.Studio.Utility
}
public string GetConfirmationUrlRelative(string email, ConfirmType confirmType, object postfix = null, Guid userId = default)
{
return $"confirm/{confirmType}?{GetToken(email, confirmType, postfix, userId)}";
}
public string GetToken(string email, ConfirmType confirmType, object postfix = null, Guid userId = default)
{
var validationKey = EmailValidationKeyProvider.GetEmailKey(email + confirmType + (postfix ?? ""));
var link = $"confirm/{confirmType}?key={validationKey}";
var link = $"type={confirmType}&key={validationKey}";
if (!string.IsNullOrEmpty(email))
{