Merge branch 'develop' into feature/integration-system

This commit is contained in:
Ilya Oleshko 2022-07-14 11:35:43 +03:00
commit 586b3e941c
108 changed files with 2534 additions and 425 deletions

22
.gitignore vendored
View File

@ -17,3 +17,25 @@ Logs/
build/deploy/
/public/debuginfo.md
TestsResults/
/Data.Test
/build/install/RadicalePlugins/app_auth_plugin/app_auth_plugin.egg-info/PKG-INFO
/build/install/RadicalePlugins/app_auth_plugin/app_auth_plugin.egg-info/SOURCES.txt
/build/install/RadicalePlugins/app_auth_plugin/app_auth_plugin.egg-info/dependency_links.txt
/build/install/RadicalePlugins/app_auth_plugin/app_auth_plugin.egg-info/top_level.txt
/build/install/RadicalePlugins/app_auth_plugin/build/lib/app_auth_plugin/__init__.py
/build/install/RadicalePlugins/app_rights_plugin/app_rights_plugin.egg-info/PKG-INFO
/build/install/RadicalePlugins/app_rights_plugin/app_rights_plugin.egg-info/SOURCES.txt
/build/install/RadicalePlugins/app_rights_plugin/app_rights_plugin.egg-info/dependency_links.txt
/build/install/RadicalePlugins/app_rights_plugin/app_rights_plugin.egg-info/top_level.txt
/build/install/RadicalePlugins/app_rights_plugin/build/lib/app_rights_plugin/__init__.py
/build/install/RadicalePlugins/app_store_plugin/app_store_plugin.egg-info/PKG-INFO
/build/install/RadicalePlugins/app_store_plugin/app_store_plugin.egg-info/SOURCES.txt
/build/install/RadicalePlugins/app_store_plugin/app_store_plugin.egg-info/dependency_links.txt
/build/install/RadicalePlugins/app_store_plugin/app_store_plugin.egg-info/top_level.txt
/build/install/RadicalePlugins/app_store_plugin/build/lib/app_store_plugin/__init__.py
/build/install/RadicalePlugins/app_store_plugin/build/lib/app_store_plugin/cache.py
/build/install/RadicalePlugins/app_store_plugin/build/lib/app_store_plugin/delete.py
/build/install/RadicalePlugins/app_store_plugin/build/lib/app_store_plugin/history.py
/build/install/RadicalePlugins/app_store_plugin/build/lib/app_store_plugin/log.py
/build/install/RadicalePlugins/app_store_plugin/build/lib/app_store_plugin/sync.py
/build/install/RadicalePlugins/app_store_plugin/build/lib/app_store_plugin/upload.py

View File

@ -26,6 +26,8 @@
using static ASC.Security.Cryptography.EmailValidationKeyProvider;
using Constants = ASC.Core.Users.Constants;
namespace ASC.Api.Core.Security;
[Transient]
@ -58,7 +60,7 @@ public class EmailValidationKeyModelHelper
{
var request = QueryHelpers.ParseQuery(_httpContextAccessor.HttpContext.Request.Headers["confirm"]);
request.TryGetValue("type", out var type);
var type = request.ContainsKey("type") ? request["type"].FirstOrDefault() : null;
ConfirmType? cType = null;
if (ConfirmTypeExtensions.TryParse(type, out var confirmType))
@ -141,7 +143,7 @@ public class EmailValidationKeyModelHelper
case ConfirmType.ProfileRemove:
// validate UiD
var user = _userManager.GetUsers(uiD.GetValueOrDefault());
if (user == null || user.Status == EmployeeStatus.Terminated || _authContext.IsAuthenticated && _authContext.CurrentAccount.ID != uiD)
if (user == null || user == Constants.LostUser || user.Status == EmployeeStatus.Terminated || _authContext.IsAuthenticated && _authContext.CurrentAccount.ID != uiD)
{
return ValidationResult.Invalid;
}

View File

@ -45,7 +45,6 @@ public class DbLoginEventsManager
(int)MessageAction.LoginSuccessViaApiTfa
};
private readonly ICache _cache;
private readonly TenantManager _tenantManager;
private readonly AuthContext _authContext;
private readonly IMapper _mapper;
@ -54,14 +53,11 @@ public class DbLoginEventsManager
private readonly Lazy<MessagesContext> _lazyLoginEventContext;
public DbLoginEventsManager(
ICache cache,
TenantManager tenantManager,
AuthContext authContext,
DbContextManager<MessagesContext> dbContextManager,
TenantUtil tenantUtil,
IMapper mapper)
{
_cache = cache;
_tenantManager = tenantManager;
_authContext = authContext;
_mapper = mapper;
@ -70,24 +66,12 @@ public class DbLoginEventsManager
public async Task<List<int>> GetLoginEventIds(int tenantId, Guid userId)
{
var commonKey = GetCacheKey(tenantId, userId);
var cacheKeys = _cache.Get<List<int>>(commonKey);
if (cacheKeys != null)
{
return cacheKeys;
}
var date = DateTime.UtcNow.AddYears(-1);
var resultList = await LoginEventContext.LoginEvents
.Where(r => r.TenantId == tenantId && r.UserId == userId && _loginActions.Contains(r.Action) && r.Date >= date && r.Active)
.Select(r => r.Id)
.ToListAsync();
if (resultList != null)
{
_cache.Insert(commonKey, resultList, _expirationTimeout);
}
return resultList;
}
@ -174,7 +158,6 @@ public class DbLoginEventsManager
public void ResetCache(int tenantId, Guid userId)
{
var key = GetCacheKey(tenantId, userId);
_cache.Remove(key);
}
private string GetCacheKey(int tenantId, Guid userId)

View File

@ -65,6 +65,7 @@ public class DbTenant : IMapFrom<Tenant>
public void Mapping(Profile profile)
{
profile.CreateMap<Tenant, DbTenant>()
.ForMember(dest => dest.TrustedDomainsEnabled, opt => opt.MapFrom(dest => dest.TrustedDomainsType))
.ForMember(dest => dest.TrustedDomainsRaw, opt => opt.MapFrom(dest => dest.GetTrustedDomains()))
.ForMember(dest => dest.Alias, opt => opt.MapFrom(dest => dest.Alias.ToLowerInvariant()))
.ForMember(dest => dest.LastModified, opt => opt.MapFrom(dest => DateTime.UtcNow))

View File

@ -160,3 +160,5 @@ global using ProtoBuf;
global using Telegram.Bot;
global using static ASC.Security.Cryptography.EmailValidationKeyProvider;
global using JsonIgnoreAttribute = System.Text.Json.Serialization.JsonIgnoreAttribute;

View File

@ -35,6 +35,7 @@ public class EventTypeConverter : ITypeConverter<EventMessage, LoginEvent>, ITyp
var loginEvent = context.Mapper.Map<MessageEvent, LoginEvent>(messageEvent);
loginEvent.Login = source.Initiator;
loginEvent.Active = source.Active;
if (source.Description != null && source.Description.Count > 0)
{

View File

@ -35,6 +35,7 @@ public class LoginEvent : MessageEvent, IMapFrom<EventMessage>
public void Mapping(Profile profile)
{
profile.CreateMap<MessageEvent, LoginEvent>();
profile.CreateMap<EventMessage, LoginEvent>()
.ConvertUsing<EventTypeConverter>();
}

View File

@ -149,7 +149,8 @@ public class Tenant : IMapFrom<DbTenant>
public void Mapping(Profile profile)
{
profile.CreateMap<DbTenant, Tenant>();
profile.CreateMap<DbTenant, Tenant>()
.ForMember(r => r.TrustedDomainsType, opt => opt.MapFrom(src => src.TrustedDomainsEnabled));
profile.CreateMap<TenantUserSecurity, Tenant>()
.IncludeMembers(src => src.DbTenant);

View File

@ -35,6 +35,8 @@ public class TenantAuditSettings : ISettings<TenantAuditSettings>
public int AuditTrailLifeTime { get; set; }
public static readonly Guid Guid = new Guid("{8337D0FB-AD67-4552-8297-802312E7F503}");
[JsonIgnore]
public Guid ID => Guid;
public TenantAuditSettings GetDefault()

View File

@ -33,6 +33,7 @@ public class TenantControlPanelSettings : ISettings<TenantControlPanelSettings>
[DataMember(Name = "LimitedAccess")]
public bool LimitedAccess { get; set; }
[JsonIgnore]
public Guid ID => new Guid("{880585C4-52CD-4AE2-8DA4-3B8E2772753B}");
public TenantControlPanelSettings GetDefault()

View File

@ -49,6 +49,7 @@ public class TenantCookieSettings : ISettings<TenantCookieSettings>
return new TenantCookieSettings();
}
[JsonIgnore]
public Guid ID => new Guid("{16FB8E67-E96D-4B22-B217-C80F25C5DE1B}");
}

View File

@ -29,7 +29,7 @@ namespace ASC.Web.Core.Users;
[Serializable]
public class DarkThemeSettings : ISettings<DarkThemeSettings>
{
[System.Text.Json.Serialization.JsonIgnore]
[JsonIgnore]
public Guid ID
{
get { return new Guid("{38362061-066D-4C57-A23E-8953CF34EFC3}"); }

View File

@ -29,6 +29,7 @@ namespace ASC.Web.Core.Users;
[Serializable]
public class DisplayUserSettings : ISettings<DisplayUserSettings>
{
[JsonIgnore]
public Guid ID => new Guid("2EF59652-E1A7-4814-BF71-FEB990149428");
public bool IsDisableGettingStarted { get; set; }

View File

@ -31,6 +31,7 @@ public class PersonalQuotaSettings : ISettings<PersonalQuotaSettings>
{
public long MaxSpace { get; set; }
[JsonIgnore]
public Guid ID => new Guid("{C634A747-C39B-4517-8698-B3B39BF2BD8E}");
public PersonalQuotaSettings GetDefault()

View File

@ -40,6 +40,7 @@ public class MailWhiteLabelSettings : ISettings<MailWhiteLabelSettings>
public string DemoUrl { get; set; }
public string SiteUrl { get; set; }
[JsonIgnore]
public Guid ID => new Guid("{C3602052-5BA2-452A-BD2A-ADD0FAF8EB88}");
public MailWhiteLabelSettings(IConfiguration configuration)

View File

@ -97,6 +97,7 @@ public abstract class BaseStorageSettings<T> : ISettings<BaseStorageSettings<T>>
[Serializable]
public class StorageSettings : BaseStorageSettings<StorageSettings>, ISettings<StorageSettings>
{
[JsonIgnore]
public override Guid ID => new Guid("F13EAF2D-FA53-44F1-A6D6-A5AEDA46FA2B");
StorageSettings ISettings<StorageSettings>.GetDefault()
@ -109,6 +110,7 @@ public class StorageSettings : BaseStorageSettings<StorageSettings>, ISettings<S
[Serializable]
public class CdnStorageSettings : BaseStorageSettings<CdnStorageSettings>, ISettings<CdnStorageSettings>
{
[JsonIgnore]
public override Guid ID => new Guid("0E9AE034-F398-42FE-B5EE-F86D954E9FB2");
public override Func<DataStoreConsumer, DataStoreConsumer> Switch => d => d.Cdn;

View File

@ -60,6 +60,7 @@ public class IPRestrictionsRepository
var restrictions = TenantDbContext.TenantIpRestrictions.Where(r => r.Tenant == tenant).ToList();
TenantDbContext.TenantIpRestrictions.RemoveRange(restrictions);
TenantDbContext.SaveChanges();
var ipsList = ips.Select(r => new TenantIpRestrictions
{
@ -68,6 +69,7 @@ public class IPRestrictionsRepository
});
TenantDbContext.TenantIpRestrictions.AddRange(ipsList);
TenantDbContext.SaveChanges();
tx.Commit();
});

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System.Text.Json.Serialization;
namespace ASC.IPSecurity;
[Serializable]
@ -31,6 +33,7 @@ public class IPRestrictionsSettings : ISettings<IPRestrictionsSettings>
{
public bool Enable { get; set; }
[JsonIgnore]
public Guid ID => new Guid("{2EDDDF64-F792-4498-A638-2E3E6EBB13C9}");
public IPRestrictionsSettings GetDefault()

View File

@ -30,6 +30,8 @@ namespace ASC.ElasticSearch.Core;
public class SearchSettings : ISettings<SearchSettings>
{
public string Data { get; set; }
[JsonIgnore]
public Guid ID => new Guid("{93784AB2-10B5-4C2F-9B36-F2662CCCF316}");
internal List<SearchSettingsItem> Items
{

View File

@ -55,18 +55,32 @@ export function setDNSSettings(dnsName, enable) {
});
}
export function getIpRestrictions() {
return request({
method: "get",
url: "/settings/iprestrictions",
});
}
export function setIpRestrictions(data) {
return request({
method: "put",
url: "/settings/iprestrictions.json",
url: "/settings/iprestrictions",
data,
});
}
export function getIpRestrictionsEnable() {
return request({
method: "get",
url: "/settings/iprestrictions/settings",
});
}
export function setIpRestrictionsEnable(data) {
return request({
method: "put",
url: "/settings/iprestrictions/settings.json",
url: "/settings/iprestrictions/settings",
data,
});
}
@ -87,6 +101,13 @@ export function setCookieSettings(lifeTime) {
});
}
export function getCookieSettings() {
return request({
method: "get",
url: "/settings/cookiesettings.json",
});
}
export function setLifetimeAuditSettings(data) {
return request({
method: "post",

View File

@ -36,6 +36,9 @@ class SettingsStore {
: Base;
trustedDomains = [];
trustedDomainsType = 0;
ipRestrictionEnable = false;
ipRestrictions = [];
sessionLifetime = "1440";
timezone = "UTC";
timezones = [];
tenantAlias = "";
@ -474,6 +477,47 @@ class SettingsStore {
this.tenantAlias = tenantAlias;
};
getIpRestrictions = async () => {
const res = await api.settings.getIpRestrictions();
this.ipRestrictions = res?.map((el) => el.ip);
};
setIpRestrictions = async (ips) => {
const data = {
ips: ips,
};
const res = await api.settings.setIpRestrictions(data);
this.ipRestrictions = res;
};
getIpRestrictionsEnable = async () => {
const res = await api.settings.getIpRestrictionsEnable();
this.ipRestrictionEnable = res.enable;
};
setIpRestrictionsEnable = async (enable) => {
const data = {
enable: enable,
};
const res = await api.settings.setIpRestrictionsEnable(data);
this.ipRestrictionEnable = res.enable;
};
setMessageSettings = async (turnOn) => {
await api.settings.setMessageSettings(turnOn);
this.enableAdmMess = turnOn;
};
getSessionLifetime = async () => {
const res = await api.settings.getCookieSettings();
this.sessionLifetime = res;
};
setSessionLifetimeSettings = async (lifeTime) => {
const res = await api.settings.setCookieSettings(lifeTime);
this.sessionLifetime = lifeTime;
};
setIsBurgerLoading = (isBurgerLoading) => {
this.isBurgerLoading = isBurgerLoading;
};

View File

@ -1926,7 +1926,7 @@ const Base = {
header: {
backgroundColor: "#0F4071",
recoveryColor: "#27537F",
linkColor: "#7a95b0",
productColor: white,
},

View File

@ -1926,7 +1926,7 @@ const Dark = {
header: {
backgroundColor: "#1f1f1f ",
recoveryColor: "#4C4C4C",
linkColor: "#606060",
productColor: "#eeeeee",
},

View File

@ -1,6 +1,6 @@
export const size = {
mobile: 375,
hugeMobile: 414,
hugeMobile: 428,
smallTablet: 600,
tablet: 1024,
desktop: 1025,

View File

@ -1,32 +0,0 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL 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 details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Files.Core.ApiModels.RequestDto;
public class InviteLinkDto
{
public FileShare Access { get; set; }
}

View File

@ -110,6 +110,7 @@ public class FilesSettings : ISettings<FilesSettings>
};
}
[JsonIgnore]
public Guid ID => new Guid("{03B382BD-3C20-4f03-8AB9-5A33F016316E}");
}

View File

@ -208,7 +208,10 @@ public partial class FilesDbContextMySql : Migration
create_on = table.Column<DateTime>(type: "datetime", nullable: false),
url = table.Column<string>(type: "text", nullable: true, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
tenant_id = table.Column<int>(type: "int", nullable: false)
tenant_id = table.Column<int>(type: "int", nullable: false),
folder_id = table.Column<string>(type: "text", nullable: true, collation: "utf8_general_ci")
.Annotation("MySql:CharSet", "utf8"),
room_type = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{

View File

@ -107,7 +107,7 @@ public class FileConverterQueue<T>
var fileId = JsonDocument.Parse(x.Source).RootElement.GetProperty("id").Deserialize<T>();
var fileVersion = JsonDocument.Parse(x.Source).RootElement.GetProperty("version").Deserialize<int>();
return file.Id.ToString() == fileId.ToString() && file.Version == fileVersion;
return file.Id.ToString() == fileId.ToString() && (file.Version == fileVersion || x.Progress == 100 && file.Version == fileVersion + 1);
});
}

View File

@ -330,13 +330,13 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
/// Invitation link
/// </returns>
[HttpGet("rooms/{id}/links")]
public async Task<object> GetInvitationLinkAsync(T id, InviteLinkDto inDto)
public async Task<object> GetInvitationLinkAsync(T id, FileShare access)
{
ErrorIfNotDocSpace();
await ErrorIfNotRights(id, inDto.Access);
await ErrorIfNotRights(id, access);
return _roomLinksService.GenerateLink(id, (int)inDto.Access, EmployeeType.User, _authContext.CurrentAccount.ID);
return _roomLinksService.GenerateLink(id, (int)access, EmployeeType.User, _authContext.CurrentAccount.ID);
}
/// <summary>

View File

@ -86,9 +86,6 @@ internal class FileConverterService<T> : BackgroundService
try
{
var scopeClass = scope.ServiceProvider.GetService<FileConverterQueueScope>();
(_, tenantManager, userManager, securityContext, daoFactory, fileSecurity, pathProvider, setupInfo, fileUtility, documentServiceHelper, documentServiceConnector, entryManager, fileConverter) = scopeClass;
fileConverterQueue = scope.ServiceProvider.GetService<FileConverterQueue<T>>();
var _conversionQueue = fileConverterQueue.GetAllTask().ToList();
@ -112,6 +109,9 @@ internal class FileConverterService<T> : BackgroundService
var commonLinkUtilitySettings = scope.ServiceProvider.GetService<CommonLinkUtilitySettings>();
commonLinkUtilitySettings.ServerUri = converter.ServerRootPath;
var scopeClass = scope.ServiceProvider.GetService<FileConverterQueueScope>();
(_, tenantManager, userManager, securityContext, daoFactory, fileSecurity, pathProvider, setupInfo, fileUtility, documentServiceHelper, documentServiceConnector, entryManager, fileConverter) = scopeClass;
tenantManager.SetCurrentTenant(converter.TenantId);
securityContext.AuthenticateMeWithoutCookie(converter.Account);
@ -131,7 +131,7 @@ internal class FileConverterService<T> : BackgroundService
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
if (!fileSecurity.CanReadAsync(file).Result && file.RootFolderType != FolderType.BUNCH)
if (!await fileSecurity.CanReadAsync(file) && file.RootFolderType != FolderType.BUNCH)
{
//No rights in CRM after upload before attach
throw new System.Security.SecurityException(FilesCommonResource.ErrorMassage_SecurityException_ReadFile);
@ -149,7 +149,7 @@ internal class FileConverterService<T> : BackgroundService
var docKey = documentServiceHelper.GetDocKey(file);
fileUri = documentServiceConnector.ReplaceCommunityAdress(fileUri);
(operationResultProgress, convertedFileUrl) = documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, password, CultureInfo.CurrentUICulture.Name, null, null, true).Result;
(operationResultProgress, convertedFileUrl) = await documentServiceConnector.GetConvertedUriAsync(fileUri, fileExtension, toExtension, docKey, password, CultureInfo.CurrentUICulture.Name, null, null, true);
}
catch (Exception exception)
{
@ -210,7 +210,7 @@ internal class FileConverterService<T> : BackgroundService
try
{
newFile = fileConverter.SaveConvertedFileAsync(file, convertedFileUrl).Result;
newFile = await fileConverter.SaveConvertedFileAsync(file, convertedFileUrl);
}
catch (Exception e)
{
@ -233,8 +233,8 @@ internal class FileConverterService<T> : BackgroundService
if (newFile != null)
{
var folderDao = daoFactory.GetFolderDao<T>();
var folder = folderDao.GetFolderAsync(newFile.ParentId).Result;
var folderTitle = fileSecurity.CanReadAsync(folder).Result ? folder.Title : null;
var folder = await folderDao.GetFolderAsync(newFile.ParentId);
var folderTitle = await fileSecurity.CanReadAsync(folder) ? folder.Title : null;
operationResult.Result = fileConverterQueue.FileJsonSerializerAsync(entryManager, newFile, folderTitle).Result;
}

View File

@ -67,7 +67,6 @@ class FilesApplication : WebApplicationFactory<Program>
public class MySetUpClass
{
protected IServiceScope Scope { get; set; }
private readonly string _pathToProducts = Path.Combine("..", "..", "..", "Data.Test");
[OneTimeSetUp]
public void CreateDb()
@ -102,7 +101,7 @@ public class MySetUpClass
try
{
Directory.Delete(Path.Combine("..", "..", "..", _pathToProducts), true);
Directory.Delete(Path.Combine(Path.Combine("..", "..", "..", "..", "..", "..", "Data.Test")), true);
}
catch { }
}
@ -127,9 +126,9 @@ public class MySetUpClass
public partial class BaseFilesTests
{
protected readonly JsonSerializerOptions _options;
private readonly JsonSerializerOptions _options;
protected UserManager _userManager;
private protected HttpClient _client;
private HttpClient _client;
private readonly string _baseAddress;
private string _cookie;
@ -208,89 +207,29 @@ public partial class BaseFilesTests
return batchModel;
}
protected async Task<T> GetAsync<T>(string url, JsonSerializerOptions options)
protected Task<T> GetAsync<T>(string url)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _cookie);
var request = await _client.GetAsync(url);
var result = await request.Content.ReadFromJsonAsync<SuccessApiResponse>();
if (result.Response is JsonElement jsonElement)
{
return jsonElement.Deserialize<T>(options);
}
throw new Exception("can't parsing result");
return SendAsync<T>(HttpMethod.Get, url);
}
protected async Task<T> GetAsync<T>(string url, HttpContent content, JsonSerializerOptions options)
protected Task<T> PostAsync<T>(string url, object data = null)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _cookie);
var request = new HttpRequestMessage
{
RequestUri = new Uri(_baseAddress + url),
Method = HttpMethod.Delete,
Content = content
};
var result = await request.Content.ReadFromJsonAsync<SuccessApiResponse>();
if (result.Response is JsonElement jsonElement)
{
return jsonElement.Deserialize<T>(options);
}
throw new Exception("can't parsing result");
return SendAsync<T>(HttpMethod.Post, url, data);
}
protected async Task<T> PostAsync<T>(string url, HttpContent content, JsonSerializerOptions options)
protected Task<T> PutAsync<T>(string url, object data = null)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _cookie);
var response = await _client.PostAsync(url, content);
var result = await response.Content.ReadFromJsonAsync<SuccessApiResponse>();
if (result.Response is JsonElement jsonElement)
{
return jsonElement.Deserialize<T>(options);
}
throw new Exception("can't parsing result");
return SendAsync<T>(HttpMethod.Put, url, data);
}
protected async Task<T> PutAsync<T>(string url, HttpContent content, JsonSerializerOptions options)
protected Task<T> DeleteAsync<T>(string url, object data = null)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _cookie);
var response = await _client.PutAsync(url, content);
var result = await response.Content.ReadFromJsonAsync<SuccessApiResponse>();
if (result.Response is JsonElement jsonElement)
{
return jsonElement.Deserialize<T>(options);
}
throw new Exception("can't parsing result");
return SendAsync<T>(HttpMethod.Delete, url, data);
}
private protected async Task<HttpResponseMessage> DeleteAsync(string url, JsonContent content)
private protected Task<SuccessApiResponse> DeleteAsync(string url, object data = null)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _cookie);
var request = new HttpRequestMessage
{
RequestUri = new Uri(_baseAddress + url),
Method = HttpMethod.Delete,
Content = content
};
return await _client.SendAsync(request);
}
protected async Task<T> DeleteAsync<T>(string url, JsonContent content, JsonSerializerOptions options)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _cookie);
var response = await DeleteAsync(url, content);
var result = await response.Content.ReadFromJsonAsync<SuccessApiResponse>();
if (result.Response is JsonElement jsonElement)
{
return jsonElement.Deserialize<T>(options);
}
throw new Exception("can't parsing result");
return SendAsync(HttpMethod.Delete, url, data);
}
protected async Task<List<FileOperationResult>> WaitLongOperation()
@ -299,7 +238,7 @@ public partial class BaseFilesTests
while (true)
{
statuses = await GetAsync<List<FileOperationResult>>("fileops", _options);
statuses = await GetAsync<List<FileOperationResult>>("fileops");
if (statuses.TrueForAll(r => r.Finished))
{
@ -310,4 +249,41 @@ public partial class BaseFilesTests
return statuses;
}
protected void CheckStatuses(List<FileOperationResult> statuses)
{
Assert.IsTrue(statuses.Count > 0);
Assert.IsTrue(statuses.TrueForAll(r => string.IsNullOrEmpty(r.Error)));
}
protected async Task<T> SendAsync<T>(HttpMethod method, string url, object data = null)
{
var result = await SendAsync(method, url, data);
if (result.Response is JsonElement jsonElement)
{
return jsonElement.Deserialize<T>(_options);
}
throw new Exception("can't parsing result");
}
protected async Task<SuccessApiResponse> SendAsync(HttpMethod method, string url, object data = null)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _cookie);
var request = new HttpRequestMessage
{
RequestUri = new Uri(_baseAddress + url),
Method = method,
};
if (data != null)
{
request.Content = JsonContent.Create(data);
}
var response = await _client.SendAsync(request);
return await response.Content.ReadFromJsonAsync<SuccessApiResponse>();
}
}

View File

@ -38,8 +38,7 @@ public partial class BaseFilesTests
[Description("post - files/folder/{folderId} - attempt to create a folder when it is forbidden")]
public async Task CreateFolderReturnsFolderWrapperAsync(int folderId)
{
var response = await _client.PostAsync("folder/" + folderId, JsonContent.Create(new { Title = "test" }));
var result = await response.Content.ReadFromJsonAsync<SuccessApiResponse>();
var result = await SendAsync(HttpMethod.Post, "folder/" + folderId, new { Title = "test" });
Assert.AreEqual(HttpStatusCode.Forbidden, result.StatusCode);
}
@ -52,7 +51,7 @@ public partial class BaseFilesTests
[Order(1)]
public async Task CreateFileReturnsFolderWrapperAsync(int folderId)
{
var file = await PostAsync<FileDto<int>>(folderId + "/file", JsonContent.Create(new { Title = "test" }), _options);
var file = await PostAsync<FileDto<int>>($"{folderId}/file", new { Title = "test" });
Assert.AreEqual(file.FolderId, 1);
}
}

View File

@ -35,7 +35,7 @@ public partial class BaseFilesTests
[Description("post - files/favorites - add file and folder to favorites")]
public async Task AddFavoriteFolderAndFileToFolderWrapper(int folderID, int fileId)
{
var favorite = await PostAsync<bool>("favorites", JsonContent.Create(new { FolderIds = new List<int> { folderID }, FileIds = new List<int> { fileId } }), _options);
var favorite = await PostAsync<bool>("favorites", new { FolderIds = new List<int> { folderID }, FileIds = new List<int> { fileId } });
Assert.IsTrue(favorite);
}
@ -46,7 +46,7 @@ public partial class BaseFilesTests
[Description("delete - files/favorites - delete file and folder from favorites")]
public async Task DeleteFavoriteFolderAndFileToFolderWrapper(int folderID, int fileId)
{
var favorite = await DeleteAsync<bool>("favorites", JsonContent.Create(new { FolderIds = new List<int> { folderID }, FileIds = new List<int> { fileId } }), _options);
var favorite = await DeleteAsync<bool>("favorites", new { FolderIds = new List<int> { folderID }, FileIds = new List<int> { fileId } });
Assert.IsTrue(favorite);
}

View File

@ -71,5 +71,22 @@ public static class DataTests
public const bool Immediately = true;
public const bool Notify = false;
public const string Message = "folder_test";
public const string Message = "test_message";
public const string RoomTitle = "Room_Title";
public const string NewRoomTitle = "New_Room_Title";
public const int CustomRoomId = 5;
public const int RoomId = 18;
public const int RoomIdForDelete = 19;
public const int RoomIdForArchive = 20;
public const int RoomIdForUnpin = 21;
public const int RoomIdForUnarchive = 22;
public const int RoomIdWithTags = 24;
public const string RoomLinkKey = "394892027511.N5UVJ8F6UJPGN0RZKWSOMEYVZVLTZLECGVHHSDSH7ZA";
public const string TagNames = "name1,name2";
public const string Email = "test1@gmail.com";
public const string Image = "appIcon-180.png";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -35,7 +35,7 @@ public partial class BaseFilesTests
[Description("post - files/folder/{folderId} - create new folder")]
public async Task CreateFolderReturnsFolderWrapper(int folderId, string title)
{
var folder = await PostAsync<FolderDto<int>>("folder/" + folderId, JsonContent.Create(new { Title = title }), _options);
var folder = await PostAsync<FolderDto<int>>($"folder/{folderId}", new { Title = title });
Assert.IsNotNull(folder);
Assert.AreEqual(title, folder.Title);
Assert.AreEqual(folderId, folder.ParentId);
@ -48,7 +48,7 @@ public partial class BaseFilesTests
[Description("get - files/{folderId} - get empty folder / get not empty folder")]
public async Task GetFolderEmptyReturnsFolderContentWrapper(int folderId, int expectedCount)
{
var folder = await GetAsync<FolderContentDto<int>>(folderId.ToString(), _options);
var folder = await GetAsync<FolderContentDto<int>>(folderId.ToString());
Assert.IsNotNull(folder);
Assert.AreEqual(expectedCount, folder.Files.Count);
@ -61,7 +61,7 @@ public partial class BaseFilesTests
[Description("get - files/folder/{folderId} - get folder info")]
public async Task GetFolderInfoReturnsFolderWrapper(int folderId, string folderName, int parentId)
{
var folder = await GetAsync<FolderDto<int>>("folder/" + folderId, _options);
var folder = await GetAsync<FolderDto<int>>($"folder/{folderId}");
Assert.IsNotNull(folder);
Assert.AreEqual(folderName, folder.Title);
@ -75,7 +75,7 @@ public partial class BaseFilesTests
[Description("put - files/folder/{folderId} - rename folder")]
public async Task RenameFolderReturnsFolderWrapper(int folderId, string newTitle)
{
var folder = await PutAsync<FolderDto<int>>("folder/" + folderId, JsonContent.Create(new { Title = newTitle }), _options);
var folder = await PutAsync<FolderDto<int>>($"folder/{folderId}", new { Title = newTitle });
Assert.IsNotNull(folder);
Assert.AreEqual(folderId, folder.Id);
@ -88,9 +88,9 @@ public partial class BaseFilesTests
[Description("delete - files/folder/{folderId} - delete folder")]
public async Task DeleteFolderReturnsFolderWrapper(int folderId, bool deleteAfter, bool immediately)
{
await DeleteAsync("folder/" + folderId, JsonContent.Create(new { DeleteAfter = deleteAfter, Immediately = immediately }));
await DeleteAsync($"folder/{folderId}", new { DeleteAfter = deleteAfter, Immediately = immediately });
var statuses = await WaitLongOperation();
Assert.IsTrue(statuses.TrueForAll(r => string.IsNullOrEmpty(r.Error)));
CheckStatuses(statuses);
}
[TestCase(DataTests.NewTitle)]
@ -99,7 +99,7 @@ public partial class BaseFilesTests
[Description("post - files/@my/file - create file in myFolder")]
public async Task CreateFileReturnsFileWrapper(string newTitle)
{
var file = await PostAsync<FileDto<int>>("@my/file", JsonContent.Create(new { Title = newTitle }), _options);
var file = await PostAsync<FileDto<int>>("@my/file", new { Title = newTitle });
Assert.IsNotNull(file);
Assert.AreEqual($"{newTitle}.docx", file.Title);
@ -112,7 +112,7 @@ public partial class BaseFilesTests
[Description("get - files/file/{fileId} - get file info")]
public async Task GetFileInfoReturnsFilesWrapper(int fileId, string fileName)
{
var file = await GetAsync<FileDto<int>>("file/" + fileId, _options);
var file = await GetAsync<FileDto<int>>($"file/{fileId}");
Assert.IsNotNull(file);
Assert.AreEqual(fileName, file.Title);
@ -124,7 +124,7 @@ public partial class BaseFilesTests
[Description("put - files/file/{fileId} - update file")]
public async Task UpdateFileReturnsFileWrapper(int fileId, string newTitle, int lastVersion)
{
var file = await PutAsync<FileDto<int>>("file/" + fileId, JsonContent.Create(new { Title = newTitle, LastVersion = lastVersion }), _options);
var file = await PutAsync<FileDto<int>>($"file/{fileId}", new { Title = newTitle, LastVersion = lastVersion });
Assert.IsNotNull(file);
Assert.AreEqual(newTitle + ".docx", file.Title);
@ -136,9 +136,9 @@ public partial class BaseFilesTests
[Description("delete - files/file/{fileId} - delete file")]
public async Task DeleteFileReturnsFileWrapper(int fileId, bool deleteAfter, bool immediately)
{
await DeleteAsync("file/" + fileId, JsonContent.Create(new { DeleteAfter = deleteAfter, Immediately = immediately }));
await DeleteAsync($"file/{fileId}", new { DeleteAfter = deleteAfter, Immediately = immediately });
var statuses = await WaitLongOperation();
Assert.IsTrue(statuses.TrueForAll(r => string.IsNullOrEmpty(r.Error)));
CheckStatuses(statuses);
}
[TestCase(DataTests.MoveBatchItems)]
@ -148,7 +148,7 @@ public partial class BaseFilesTests
{
var batchModel = GetBatchModel(json);
var statuses = await PutAsync<IEnumerable<FileOperationDto>>("fileops/move", JsonContent.Create(batchModel), _options);
var statuses = await PutAsync<IEnumerable<FileOperationDto>>("fileops/move", batchModel);
FileOperationDto status = null;
foreach (var item in statuses)
@ -171,7 +171,7 @@ public partial class BaseFilesTests
{
var batchModel = GetBatchModel(json);
var statuses = await PutAsync<IEnumerable<FileOperationDto>>("fileops/copy", JsonContent.Create(batchModel), _options);
var statuses = await PutAsync<IEnumerable<FileOperationDto>>("fileops/copy", batchModel);
FileOperationDto status = null;
foreach (var item in statuses)

View File

@ -35,7 +35,7 @@ public partial class BaseFilesTests
[Description("post - file/{fileId}/recent - add file to recent")]
public async Task RecentFileReturnsFolderWrapper(int fileId, string fileName)
{
var file = await PostAsync<FileDto<int>>("file/" + fileId + "/recent", null, _options);
var file = await PostAsync<FileDto<int>>($"file/{fileId}/recent");
Assert.IsNotNull(file);
Assert.AreEqual(fileName, file.Title);
}
@ -46,10 +46,12 @@ public partial class BaseFilesTests
[Description("delete - file/{fileId}/recent - delete file which added to recent")]
public async Task DeleteRecentFileReturnsFolderWrapper(int fileId, string fileTitleExpected)
{
await PostAsync<FileDto<int>>("file/" + fileId + "/recent", null, _options);
await DeleteAsync("file/" + fileId, JsonContent.Create(new { DeleteAfter = false, Immediately = true }));
_ = await WaitLongOperation();
var recents = await GetAsync<FolderContentDto<int>>("@recent", _options);
await PostAsync<FileDto<int>>($"file/{fileId}/recent");
await DeleteAsync($"file/{fileId}", new { DeleteAfter = false, Immediately = true });
await WaitLongOperation();
var recents = await GetAsync<FolderContentDto<int>>("@recent");
Assert.IsTrue(!recents.Files.Any(r => r.Title == fileTitleExpected + ".docx"));
}
@ -58,7 +60,7 @@ public partial class BaseFilesTests
[Order(3)]
public async Task ShareFileToAnotherUserAddToRecent(int fileId, string fileName)
{
var file = await PostAsync<FileDto<int>>("file/" + fileId + "/recent", null, _options);
var file = await PostAsync<FileDto<int>>($"file/{fileId}/recent");
Assert.IsNotNull(file);
Assert.AreEqual(fileName, file.Title);

View File

@ -0,0 +1,215 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL 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 details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ASC.Core.Users;
namespace ASC.Files.Tests;
[TestFixture]
public partial class BaseFilesTests
{
[TestCase(DataTests.RoomTitle, DataTests.CustomRoomId)]
[Category("Room")]
[Order(1)]
[Description("post - rooms - create room")]
public async Task CreateRoom(string title, int roomType)
{
var room = await PostAsync<FolderDto<int>>("rooms", new { Title = title, RoomType = roomType });
Assert.IsNotNull(room);
Assert.AreEqual(title, room.Title);
}
[TestCase(DataTests.RoomId, DataTests.NewRoomTitle)]
[Category("Room")]
[Order(2)]
[Description("put - rooms/{id} - rename room")]
public async Task RenameRoom(int id, string newTitle)
{
var room = await PutAsync<FolderDto<int>>($"rooms/{id}", new { Title = newTitle });
Assert.IsNotNull(room);
Assert.AreEqual(newTitle, room.Title);
}
[TestCase(DataTests.RoomIdForDelete, DataTests.DeleteAfter)]
[Category("Room")]
[Order(3)]
[Description("delete - rooms/{id} - delete room")]
public async Task DeleteRoom(int id, bool deleteAfter)
{
await DeleteAsync<FileOperationDto>($"rooms/{id}", new { DeleteAfter = deleteAfter });
var statuses = await WaitLongOperation();
CheckStatuses(statuses);
}
[TestCase(DataTests.RoomIdForArchive, DataTests.DeleteAfter)]
[Category("Room")]
[Order(4)]
[Description("put - rooms/{id}/archive - archive a room")]
public async Task ArchiveRoom(int id, bool deleteAfter)
{
await PutAsync<FileOperationDto>($"rooms/{id}/archive", new { DeleteAfter = deleteAfter });
var statuses = await WaitLongOperation();
CheckStatuses(statuses);
}
[TestCase(DataTests.RoomIdForUnarchive, DataTests.DeleteAfter)]
[Category("Room")]
[Order(5)]
[Description("put - rooms/{id}/archive - unarchive a room")]
public async Task UnarchiveRoom(int id, bool deleteAfter)
{
await PutAsync<FileOperationDto>($"rooms/{id}/unarchive", new { DeleteAfter = deleteAfter });
var statuses = await WaitLongOperation();
CheckStatuses(statuses);
}
[TestCase(DataTests.RoomId, DataTests.Notify, DataTests.Message)]
[Category("Room")]
[Order(6)]
[Description("put - rooms/{id}/share - share a room")]
public async Task ShareRoom(int id, bool notify, string message)
{
var newUser = _userManager.GetUsers(Guid.Parse("005bb3ff-7de3-47d2-9b3d-61b9ec8a76a5"));
var testRoomParamRead = new List<FileShareParams> { new FileShareParams { Access = Core.Security.FileShare.Read, ShareTo = newUser.Id } };
var share = await PutAsync<IEnumerable<FileShareDto>>($"rooms/{id}/share", new { Share = testRoomParamRead, Notify = notify, SharingMessage = message });
Assert.IsNotNull(share);
}
[TestCase]
[Category("Room")]
[Order(7)]
[Description("get - rooms - get all rooms")]
public async Task GetAllRooms()
{
var rooms = await GetAsync<FolderContentDto<int>>($"rooms");
Assert.IsNotNull(rooms);
}
[TestCase(DataTests.RoomId)]
[Category("Room")]
[Order(8)]
[Description("get - rooms/{id} - get room by id")]
public async Task GetRoomById(int id)
{
var room = await GetAsync<FolderContentDto<int>>($"rooms/{id}");
Assert.IsNotNull(room);
}
[TestCase(DataTests.RoomId, DataTests.TagNames)]
[Category("Room")]
[Order(9)]
[Description("put - rooms/{id}/tags - add tags by id")]
public async Task AddTagsById(int id, string tagNames)
{
var folder = await PutAsync<FolderDto<int>>($"rooms/{id}/tags", new { Names = tagNames.Split(',') });
Assert.IsTrue(folder.Tags.Count() == 2);
}
[TestCase(DataTests.RoomIdWithTags, DataTests.TagNames)]
[Category("Room")]
[Order(10)]
[Description("delete - rooms/{id}/tags - delete tags by id")]
public async Task DeleteTagsById(int id, string tagNames)
{
var folder = await DeleteAsync<FolderDto<int>>($"rooms/{id}/tags", new { Names = tagNames.Split(',') });
Assert.IsTrue(folder.Tags.Count() == 0);
}
[TestCase(DataTests.RoomId)]
[Category("Room")]
[Order(11)]
[Description("put - rooms/{id}/pin - pin a room")]
public async Task PinRoom(int id)
{
var folder = await PutAsync<FolderDto<int>>($"rooms/{id}/pin");
Assert.IsTrue(folder.Pinned);
}
[TestCase(DataTests.RoomIdForUnpin)]
[Category("Room")]
[Order(12)]
[Description("put - rooms/{id}/unpin - unpin a room")]
public async Task UnpinRoom(int id)
{
var folder = await PutAsync<FolderDto<int>>($"rooms/{id}/unpin");
Assert.IsFalse(folder.Pinned);
}
[TestCase(DataTests.RoomId, DataTests.Email)]
[Category("Room")]
[Order(13)]
[Description("put - rooms/{id}/links/send - send invitation links to email")]
public async Task SendLink(int id, string email)
{
var invites = await PutAsync<IEnumerable<InviteResultDto>>($"rooms/{id}/links/send", new { Emails = new List<string>() { email }, EmployeeType = EmployeeType.All, Access = Core.Security.FileShare.Read });
Assert.IsTrue(invites.First().Success);
}
[TestCase(DataTests.RoomId, DataTests.RoomLinkKey)]
[Category("Room")]
[Order(14)]
[Description("put - rooms/{id}/share - share a room by link")]
public async Task ShareRoomByLink(int id, string key)
{
var share = await PutAsync<IEnumerable<FileShareDto>>($"rooms/{id}/share", new { Access = Core.Security.FileShare.Read, Key = key });
Assert.IsNotNull(share);
}
[TestCase(DataTests.RoomId)]
[Category("Room")]
[Order(15)]
[Description("get - rooms/{id}/links - get invitation links")]
public async Task GetLink(int id)
{
var invites = await GetAsync<string>($"rooms/{id}/links?access=2");
Assert.IsNotNull(invites);
Assert.IsNotEmpty(invites);
}
//[TestCase(DataTests.RoomId, DataTests.Image)]
//[Category("Room")]
//[Order(16)]
//[Description("post - rooms/{id}/logo - add logo/ delete - rooms/{id}/logo - delete logo")]
//public async Task AddAndDeleteLogo(int id, string image)
//{
// CopyImage(image);
// var room = await PostAsync<FolderDto<int>>($"rooms/{id}/logo", JsonContent.Create(new { TmpFile = image, X = 0, Y = 0, Width = 180, Height = 180 }));
// Assert.IsNotEmpty(room.Logo.Original);
// room = await DeleteAsync<FolderDto<int>>($"rooms/{id}/logo", null);
// Assert.IsEmpty(room.Logo.Original);
//}
//private void CopyImage(string image)
//{
// var imgPath = Path.Combine("..", "..", "..", "Infrastructure", "images", image);
// var destPath = Path.Combine("..", "..", "..", "..", "..", "..", "Data.Test", "Products\\Files\\logos\\00/00/01\\temp");
// Directory.CreateDirectory(destPath);
// File.Copy(imgPath, Path.Combine(destPath, image), true);
//}
}

View File

@ -49,7 +49,7 @@ public partial class BaseFilesTests
[Description("put - files/folder/{folderId}/share - share folder to another user for read")]
public async Task ShareFolderToAnotherUserRead(int folderId, bool notify, string message)
{
var share = await PutAsync<IEnumerable<FileShareDto>>("folder/" + folderId + "/share", JsonContent.Create(new { Share = _testFolderParamRead, Notify = notify, SharingMessage = message }), _options);
var share = await PutAsync<IEnumerable<FileShareDto>>($"folder/{folderId}/share", new { Share = _testFolderParamRead, Notify = notify, SharingMessage = message });
Assert.IsNotNull(share);
}
@ -59,8 +59,7 @@ public partial class BaseFilesTests
[Description("put - files/folder/{folderId} - try to update folder which can only read")]
public async Task RenameSharedFolderReturnsFolderWrapperReadAsync(int folderId)
{
var request = await _client.PutAsync("folder/" + folderId, JsonContent.Create(new { Title = "newName" }));
var result = await request.Content.ReadFromJsonAsync<SuccessApiResponse>();
var result = await SendAsync(HttpMethod.Put, "folder/" + folderId, new { Title = "newName" });
Assert.AreEqual(HttpStatusCode.Forbidden, result.StatusCode);
}
@ -70,7 +69,7 @@ public partial class BaseFilesTests
[Description("put - file/{fileId}/share - share file to another user for read")]
public async Task ShareFileToAnotherUserRead(int fileId, bool notify, string message)
{
var share = await PutAsync<IEnumerable<FileShareDto>>("file/" + fileId + "/share", JsonContent.Create(new { Share = _testFolderParamRead, Notify = notify, SharingMessage = message }), _options);
var share = await PutAsync<IEnumerable<FileShareDto>>($"file/{fileId}/share", new { Share = _testFolderParamRead, Notify = notify, SharingMessage = message });
Assert.IsNotNull(share);
}
@ -80,11 +79,10 @@ public partial class BaseFilesTests
[Description("put - files/file/{fileId} - try to update file which can only read")]
public async Task UpdateSharedFileReturnsFolderWrapperReadAsync(int fileId)
{
var request = await _client.PutAsync("file/" + fileId, JsonContent.Create(new { Title = "newName", LastVersion = 0 }));
var result = await request.Content.ReadFromJsonAsync<SuccessApiResponse>();
var result = await SendAsync(HttpMethod.Put, "file/" + fileId, new { Title = "newName", LastVersion = 0 });
Assert.That(HttpStatusCode.Forbidden == result.StatusCode);
}
#endregion
#region Shared Folder and File (Read and Write)
@ -95,7 +93,7 @@ public partial class BaseFilesTests
[Description("put - files/folder/{folderId}/share - share folder to another user for read and write")]
public async Task ShareFolderToAnotherUserReadAndWrite(int folderId, bool notify, string message)
{
var share = await PutAsync<IEnumerable<FileShareDto>>("folder/" + folderId + "/share", JsonContent.Create(new { Share = _testFolderParamReadAndWrite, Notify = notify, SharingMessage = message }), _options);
var share = await PutAsync<IEnumerable<FileShareDto>>($"folder/{folderId}/share", new { Share = _testFolderParamReadAndWrite, Notify = notify, SharingMessage = message });
Assert.IsNotNull(share);
}
@ -105,7 +103,7 @@ public partial class BaseFilesTests
[Description("put - files/folder/{folderId} - rename shared for read and write folder")]
public async Task RenameSharedFolderReturnsFolderWrapperReadAndWrite(int folderId, string newTitle)
{
var sharedFolder = await PutAsync<FolderDto<int>>("folder/" + folderId, JsonContent.Create(new { Title = newTitle }), _options);
var sharedFolder = await PutAsync<FolderDto<int>>($"folder/{folderId}", new { Title = newTitle });
Assert.IsNotNull(sharedFolder);
Assert.AreEqual(newTitle, sharedFolder.Title);
@ -117,7 +115,7 @@ public partial class BaseFilesTests
[Description("put - files/file/{fileId}/share - share file to another user for read and write")]
public async Task ShareFileToAnotherUserReadAndWrite(int fileId, bool notify, string message)
{
var share = await PutAsync<IEnumerable<FileShareDto>>("file/" + fileId + "/share", JsonContent.Create(new { Share = _testFolderParamReadAndWrite, Notify = notify, SharingMessage = message }), _options);
var share = await PutAsync<IEnumerable<FileShareDto>>($"file/{fileId}/share", new { Share = _testFolderParamReadAndWrite, Notify = notify, SharingMessage = message });
Assert.IsNotNull(share);
}
@ -127,7 +125,7 @@ public partial class BaseFilesTests
[Description("put - files/file/{fileId} - update shared for read and write file")]
public async Task UpdateSharedFileReturnsFolderWrapperReadAndWrite(int fileId, string fileTitle, int lastVersion)
{
var sharedFile = await PutAsync<FolderDto<int>>("file/" + fileId, JsonContent.Create(new { Title = fileTitle, LastVersion = lastVersion }), _options);
var sharedFile = await PutAsync<FolderDto<int>>($"file/{fileId}", new { Title = fileTitle, LastVersion = lastVersion });
Assert.IsNotNull(sharedFile);
Assert.AreEqual(fileTitle + ".docx", sharedFile.Title);
@ -141,7 +139,7 @@ public partial class BaseFilesTests
[Description("get - files/folder/{folderId} - get shared folder")]
public async Task GetSharedFolderInfoReturnsFolderWrapperRead(int folderId, string folderName, int parentId)
{
var sharedFolder = await GetAsync<FolderDto<int>>("folder/" + folderId, _options);
var sharedFolder = await GetAsync<FolderDto<int>>($"folder/{folderId}");
Assert.IsNotNull(sharedFolder);
Assert.AreEqual(folderName, sharedFolder.Title);
@ -156,7 +154,7 @@ public partial class BaseFilesTests
[Description("get - files/file/{fileId} - get shared file")]
public async Task GetSharedFileInfoReturnsFolderWrapperRead(int fileId, string fileName)
{
var sharedFile = await GetAsync<FolderDto<int>>("file/" + fileId, _options);
var sharedFile = await GetAsync<FolderDto<int>>($"file/{fileId}");
Assert.IsNotNull(sharedFile);
Assert.AreEqual(fileName, sharedFile.Title);
@ -169,7 +167,7 @@ public partial class BaseFilesTests
[Description("delete - files/file/{fileId} - try delete shared file")]
public async Task DeleteSharedFileReturnsFolderWrapperRead(int fileId, bool deleteAfter, bool immediately)
{
var result = (await DeleteAsync<IEnumerable<FileOperationDto>>("file/" + fileId, JsonContent.Create(new { DeleteAfter = deleteAfter, Immediately = immediately }), _options)).FirstOrDefault();
var result = (await DeleteAsync<IEnumerable<FileOperationDto>>($"file/{fileId}", new { DeleteAfter = deleteAfter, Immediately = immediately })).FirstOrDefault();
await WaitLongOperation(result, FilesCommonResource.ErrorMassage_SecurityException_DeleteFile);
}
@ -181,7 +179,7 @@ public partial class BaseFilesTests
[Description("delete - files/folder/{folderId} - try delete shared folder")]
public async Task DeleteSharedFolderReturnsFolderWrapperRead(int folderId, bool deleteAfter, bool immediately)
{
var result = (await DeleteAsync<IEnumerable<FileOperationDto>>("folder/" + folderId, JsonContent.Create(new { DeleteAfter = deleteAfter, Immediately = immediately }), _options)).FirstOrDefault();
var result = (await DeleteAsync<IEnumerable<FileOperationDto>>($"folder/{folderId}", new { DeleteAfter = deleteAfter, Immediately = immediately })).FirstOrDefault();
await WaitLongOperation(result, FilesCommonResource.ErrorMassage_SecurityException_DeleteFolder);
}

View File

@ -301,6 +301,83 @@ namespace ASC.Core.Common.Migrations
b.ToTable("files_security");
});
modelBuilder.Entity("ASC.Files.Core.EF.DbFilesTag", b =>
{
b.Property<int>("Id")
.HasColumnType("int")
.HasColumnName("id");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("char(255)")
.HasColumnName("name")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("Owner")
.IsRequired()
.HasColumnType("char(38)")
.HasColumnName("owner")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<int>("Type")
.HasColumnType("int")
.HasColumnName("flag");
b.Property<int>("TenantId")
.HasColumnType("int")
.HasColumnName("tenant_id");
b.ToTable("files_tag");
});
modelBuilder.Entity("ASC.Files.Core.EF.DbFilesTagLink", b =>
{
b.Property<int>("TenantId")
.HasColumnType("int")
.HasColumnName("tenant_id");
b.Property<int>("TagId")
.HasColumnType("int")
.HasColumnName("tag_id");
b.Property<int>("EntryType")
.HasColumnType("int")
.HasColumnName("entry_type");
b.Property<string>("EntryId")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("entry_id")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("CreateBy")
.IsRequired()
.HasColumnType("char(38)")
.HasColumnName("create_by")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<string>("CreateBy")
.IsRequired()
.HasColumnType("char(38)")
.HasColumnName("create_by")
.UseCollation("utf8_general_ci")
.HasAnnotation("MySql:CharSet", "utf8");
b.Property<DateTime>("CreateOn")
.HasColumnType("datetime")
.HasColumnName("create_on");
b.Property<int>("Count")
.HasColumnType("int")
.HasColumnName("tag_count");
b.ToTable("files_tag_link");
});
#pragma warning restore 612, 618
}
}

View File

@ -231,6 +231,126 @@ public partial class TestFilesMigration : Migration
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 16, 6, 0 });
migrationBuilder.InsertData(
table: "files_folder",
columns: new[] { "id", "parent_id", "title", "folder_type", "create_by", "create_on", "modified_by", "modified_on", "tenant_id", "foldersCount", "filesCount" },
values: new object[] { 17, 0, "virtualrooms", 14, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 1, 0, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 17, 17, 0 });
migrationBuilder.InsertData(
table: "files_bunch_objects",
columns: new[] { "tenant_id", "right_node", "left_node" },
values: new object[] { 1, "files/virtualrooms/", "17" });
migrationBuilder.InsertData(
table: "files_folder",
columns: new[] { "id", "parent_id", "title", "folder_type", "create_by", "create_on", "modified_by", "modified_on", "tenant_id", "foldersCount", "filesCount" },
values: new object[] { 18, 17, "Room_Title", 19, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 1, 0, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 18, 18, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 18, 17, 0 });
migrationBuilder.InsertData(
table: "files_folder",
columns: new[] { "id", "parent_id", "title", "folder_type", "create_by", "create_on", "modified_by", "modified_on", "tenant_id", "foldersCount", "filesCount" },
values: new object[] { 19, 17, "Room_Title", 19, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 1, 0, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 19, 19, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 19, 17, 0 });
migrationBuilder.InsertData(
table: "files_folder",
columns: new[] { "id", "parent_id", "title", "folder_type", "create_by", "create_on", "modified_by", "modified_on", "tenant_id", "foldersCount", "filesCount" },
values: new object[] { 20, 17, "Room_Title", 19, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 1, 0, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 20, 20, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 20, 17, 0 });
migrationBuilder.InsertData(
table: "files_folder",
columns: new[] { "id", "parent_id", "title", "folder_type", "create_by", "create_on", "modified_by", "modified_on", "tenant_id", "foldersCount", "filesCount" },
values: new object[] { 21, 17, "Room_Title", 20, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 1, 0, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 21, 21, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 21, 17, 0 });
migrationBuilder.InsertData(
table: "files_folder",
columns: new[] { "id", "parent_id", "title", "folder_type", "create_by", "create_on", "modified_by", "modified_on", "tenant_id", "foldersCount", "filesCount" },
values: new object[] { 22, 23, "Room_Title", 19, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 1, 0, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 22, 22, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 22, 23, 0 });
migrationBuilder.InsertData(
table: "files_folder",
columns: new[] { "id", "parent_id", "title", "folder_type", "create_by", "create_on", "modified_by", "modified_on", "tenant_id", "foldersCount", "filesCount" },
values: new object[] { 23, 17, "arhive", 20, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 1, 0, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 23, 23, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 23, 17, 0 });
migrationBuilder.InsertData(
table: "files_folder",
columns: new[] { "id", "parent_id", "title", "folder_type", "create_by", "create_on", "modified_by", "modified_on", "tenant_id", "foldersCount", "filesCount" },
values: new object[] { 24, 17, "Room_Title", 19, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 1, 0, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 24, 24, 0 });
migrationBuilder.InsertData(
table: "files_folder_tree",
columns: new[] { "folder_id", "parent_id", "level" },
values: new object[] { 24, 17, 0 });
migrationBuilder.InsertData(
table: "files_file",
columns: new[] {
@ -326,15 +446,60 @@ public partial class TestFilesMigration : Migration
values: new object[]{
1, "16", 1, "66faa6e4-f133-11ea-b126-00ffeec8b4ef", "005bb3ff-7de3-47d2-9b3d-61b9ec8a76a5", 1, new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842)
});
/*
migrationBuilder.InsertData(
table: "files_tag",
columns: new[]{
"id","tenant_id","name","owner","flag",
},
values: new object[]{
1, 1, "recent", "66faa6e4-f133-11ea-b126-00ffeec8b4ef", 16
});*/
1, 1, "name1", "66faa6e4-f133-11ea-b126-00ffeec8b4ef", 64
});
migrationBuilder.InsertData(
table: "files_tag_link",
columns: new[]{
"tenant_id","tag_id","entry_type","entry_id","create_by","create_on","tag_count",
},
values: new object[]{
1, 1, 1, "24", "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 0
});
migrationBuilder.InsertData(
table: "files_tag",
columns: new[]{
"id","tenant_id","name","owner","flag",
},
values: new object[]{
2, 1, "name2", "66faa6e4-f133-11ea-b126-00ffeec8b4ef", 64
});
migrationBuilder.InsertData(
table: "files_tag_link",
columns: new[]{
"tenant_id","tag_id","entry_type","entry_id","create_by","create_on","tag_count",
},
values: new object[]{
1, 2, 1, "24", "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 0
});
migrationBuilder.InsertData(
table: "files_tag",
columns: new[]{
"id","tenant_id","name","owner","flag",
},
values: new object[]{
3, 1, "pin", "66faa6e4-f133-11ea-b126-00ffeec8b4ef", 128
});
migrationBuilder.InsertData(
table: "files_tag_link",
columns: new[]{
"tenant_id","tag_id","entry_type","entry_id","create_by","create_on","tag_count",
},
values: new object[]{
1, 3, 1, "21", "66faa6e4-f133-11ea-b126-00ffeec8b4ef", new DateTime(2021, 8, 4, 11, 1, 4, 513, DateTimeKind.Utc).AddTicks(1842), 0
});
}
protected override void Down(MigrationBuilder migrationBuilder)

View File

@ -31,12 +31,12 @@ public partial class BaseFilesTests
{
[Test]
[Category("Folder")]
[Order(1)]
[Order(100)]
[Description("put - files/fileops/emptytrash - empty trash")]
public async Task DeleteFileFromTrash()
{
var Empty = await PutAsync<IEnumerable<FileOperationDto>>("fileops/emptytrash", null, _options);
await PutAsync<IEnumerable<FileOperationDto>>("fileops/emptytrash");
var statuses = await WaitLongOperation();
Assert.IsTrue(statuses.TrueForAll(r => string.IsNullOrEmpty(r.Error)));
CheckStatuses(statuses);
}
}

View File

@ -202,7 +202,7 @@ public class UserController : PeopleControllerBase
[HttpPost]
[Authorize(AuthenticationSchemes = "confirm", Roles = "LinkInvite,Everyone")]
public EmployeeDto AddMember(MemberRequestDto inDto)
public async Task<EmployeeDto> AddMember(MemberRequestDto inDto)
{
_apiContext.AuthByClaim();
@ -226,7 +226,7 @@ public class UserController : PeopleControllerBase
if (success)
{
var folderDao = _daoFactory.GetFolderDao<int>();
var folder = folderDao.GetFolderAsync(id).Result;
var folder = await folderDao.GetFolderAsync(id);
if (folder == null)
{
@ -236,7 +236,7 @@ public class UserController : PeopleControllerBase
else
{
var folderDao = _daoFactory.GetFolderDao<string>();
var folder = folderDao.GetFolderAsync(inDto.RoomId).Result;
var folder = await folderDao.GetFolderAsync(inDto.RoomId);
if (folder == null)
{
@ -891,7 +891,7 @@ public class UserController : PeopleControllerBase
return _employeeFullDtoHelper.GetFull(user);
}
[HttpPut("{userid}")]
[HttpPut("{userid}", Order = 1)]
public async Task<EmployeeDto> UpdateMember(string userid, UpdateMemberRequestDto inDto)
{
var user = GetUserInfo(userid);

View File

@ -62,6 +62,14 @@ public class IpRestrictionsController : BaseSettingsController
return _iPRestrictionsService.Save(inDto.Ips, Tenant.Id);
}
[HttpGet("iprestrictions/settings")]
public IPRestrictionsSettings ReadIpRestrictionsSettings()
{
_permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
return _settingsManager.Load<IPRestrictionsSettings>();
}
[HttpPut("iprestrictions/settings")]
public IPRestrictionsSettings UpdateIpRestrictionsSettings(IpRestrictionsRequestsDto inDto)
{

View File

@ -219,6 +219,7 @@ public class SettingsController : BaseSettingsController
if (inDto.Type == TenantTrustedDomainsType.Custom)
{
Tenant.TrustedDomainsRaw = "";
Tenant.TrustedDomains.Clear();
foreach (var d in inDto.Domains.Select(domain => (domain ?? "").Trim().ToLower()))
{

View File

@ -154,7 +154,7 @@ public class TfaappController : BaseSettingsController
}
[HttpPut("tfaapp")]
public bool TfaSettings(TfaRequestsDto inDto)
public async Task<bool> TfaSettings(TfaRequestsDto inDto)
{
_permissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
@ -228,7 +228,7 @@ public class TfaappController : BaseSettingsController
if (result)
{
_cookiesManager.ResetTenantCookie();
await _cookiesManager.ResetTenantCookie();
}
_messageService.Send(action);
@ -299,8 +299,9 @@ public class TfaappController : BaseSettingsController
public object TfaAppNewApp(TfaRequestsDto inDto)
{
var id = inDto?.Id ?? Guid.Empty;
var isMe = id.Equals(Guid.Empty);
var user = _userManager.GetUsers(isMe ? _authContext.CurrentAccount.ID : id);
var isMe = id.Equals(Guid.Empty) || id.Equals(_authContext.CurrentAccount.ID);
var user = _userManager.GetUsers(id);
if (!isMe && !_permissionContext.CheckPermissions(new UserSecurityProvider(user.Id), Constants.Action_EditUser))
{

View File

@ -30,6 +30,7 @@ public class AdminHelperSettings : ISettings<AdminHelperSettings>
{
public bool Viewed { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{342CBBF7-FE08-4261-AB38-9C6BA8FA22B9}"); }

View File

@ -32,6 +32,7 @@ public class OpensourceGiftSettings : ISettings<OpensourceGiftSettings>
#region ISettings Members
[JsonIgnore]
public Guid ID
{
get { return new Guid("{1F4FEA2C-2D9F-47A6-ADEF-CEC4D1E1E243}"); }

View File

@ -8,12 +8,16 @@
"AccessRightsUsersFromList": "{{users}} from the list",
"AccessSettings": "Access settings",
"AddAdmins": "Add admins",
"AddAllowedIP": "Add allowed IP address",
"AddName": "Add Name",
"AddTrustedDomain": "Add trusted domain",
"AdminInModules": "Admin in modules",
"AdministratorsAddedSuccessfully": "Administrators added successfully",
"AdministratorsRemovedSuccessfully": "Administrators removed successfully",
"Admins": "Admins",
"AdminsMessage": "Administrator Message Settings",
"AdminsMessageDescription": "Administrator Message Settings is a way to contact the portal administrator.",
"AdminsMessageHelper": "Enable this option to display the contact form on the Sign In page so that people could send the message to the portal administrator in case they have troubles accessing the portal.",
"AllDomains": "Any domains",
"AutoBackup": "Automatic backup",
"AutoBackupDescription": "Use this option for automatic backup of the portal data.",
@ -67,11 +71,16 @@
"GroupLead": "Group Lead",
"Groups": "Groups",
"Guests": "Guests",
"IPSecurity": "IP Security",
"IPSecurityDescription": "IP Security is used to restrict login to the portal from all IP addresses except certain addresses.",
"IPSecurityHelper": "You can set the allowed IP addresses using either exact IP addresses in the IPv4 format (#.#.#.#, where # is a numeric value from 0 to 255) or IP range (in the #.#.#.#-#.#.#.# format).",
"IPSecurityWarningHelper": "First you need to specify your current IP or the IP range your current IP address belongs to, otherwise your portal access will be blocked right after you save the settings. The portal owner will have the portal access from any IP address.",
"Job/Title": "Job/Title",
"LanguageAndTimeZoneSettingsDescription": "Language and Time Zone Settings is a way to change the language of the whole portal for all portal users and to configure the time zone so that all the events of the portal will be shown with the correct date and time.",
"LanguageTimeSettingsTooltip": "<0>{{text}}</0> is a way to change the language of the whole portal for all portal users and to configure the time zone so that all the events of the ONLYOFFICE portal will be shown with the correct date and time.",
"LanguageTimeSettingsTooltipDescription": "To make the parameters you set take effect click the <1>{{save}}</1> button at the bottom of the section.<3>{{learnMore}}</3>",
"LocalFile": "Local file",
"Lifetime": "Lifetime (min)",
"LogoDark": "Logo for the About/Login page",
"LogoDocsEditor": "Logo for the editors header",
"LogoFavicon": "Favicon",
@ -122,6 +131,11 @@
"SendNotificationAboutRestoring": "Send notification about portal restoring to users",
"ServerSideEncryptionMethod": "Server Side Encryption Method",
"ServiceUrl": "Service Url",
"RecoveryFileNotSelected": "Recovery error. Recovery file not selected",
"SessionLifetime": "Session Lifetime",
"SessionLifetimeDescription": "Session Lifetime allows to set time (in minutes) before the portal users will need to enter the portal credentials again in order to access the portal.",
"SessionLifetimeHelper": "After save all the users will be logged out from portal.",
"SetDefaultTitle": "Set default title",
"SettingPasswordStrength": "Setting password strength",
"SettingPasswordStrengthDescription": "Password Strength Settings is a way to determine the effectiveness of a password in resisting guessing and brute-force attacks.",
"SettingPasswordStrengthHelper": "Use the Minimum Password Length bar to determine how long the password should be. Check the appropriate boxes below to determine the character set that must be used in the password.",

View File

@ -8,12 +8,16 @@
"AccessRightsUsersFromList": "Участников со статусом {{users}} из списка",
"AccessSettings": "Настройки доступа",
"AddAdmins": "Добавить администраторов",
"AddAllowedIP": "Добавить разрешенный IP-адрес",
"AddName": "Добавьте наименование",
"AddTrustedDomain": "Добавить доверенный домен",
"AdminInModules": "Администратор в модулях",
"AdministratorsAddedSuccessfully": "Администраторы успешно добавлены",
"AdministratorsRemovedSuccessfully": "Администраторы успешно удалены",
"Admins": "Администраторы",
"AdminsMessage": "Настройки сообщений администратору",
"AdminsMessageDescription": "Настройки сообщений администратору это способ связаться с администратором портала.",
"AdminsMessageHelper": "Включите эту опцию для отображения формы связи на странице Входа, чтобы пользователи могли отправить сообщение администратору портала в случае, если у них возникают проблемы со входом на портал.",
"AllDomains": "Любые домены",
"AutoBackup": "Автоматическое резервное копирование",
"AutoBackupDescription": "Используйте эту опцию для автоматического выполнения резервного копирования данных портала.",
@ -67,6 +71,10 @@
"GroupLead": "Руководитель группы",
"Groups": "Группы",
"Guests": "Гости",
"IPSecurity": "IP-безопасность",
"IPSecurityDescription": "Настройки IP-безопасности используются для ограничения возможности входа на портал со всех IP-адресов, кроме указанных.",
"IPSecurityHelper": "Вы можете задать разрешенные IP-адреса, указав конкретные значения IP-адресов в формате IPv4 (#.#.#.#, где # - это число от 0 до 255) или диапазон IP-адресов (в формате #.#.#.#-#.#.#.#).",
"IPSecurityWarningHelper": "Первым необходимо указать ваш текущий IP-адрес или диапазон IP-адресов, в который входит ваш текущий IP, иначе после сохранения настроек Вам будет заблокирован доступ к порталу. Владелец портала будет иметь доступ с любого IP-адреса.",
"Job/Title": "Должность/Позиция",
"LanguageAndTimeZoneSettingsDescription": "Настройки языка и часового пояса позволяют изменить язык всего портала для всех пользователей и настроить часовой пояс, чтобы все события на портале отображались с корректной датой и временем.",
"LanguageTimeSettingsTooltip": "<0>{{text}}</0> позволяют изменить язык всего портала для всех пользователей и настроить часовой пояс, чтобы все события на портале ONLYOFFICE отображались с корректной датой и временем.",

View File

@ -13,10 +13,9 @@ const Header = styled.header`
align-items: left;
background-color: ${(props) => props.theme.header.backgroundColor};
display: flex;
width: calc(100vw - 64px);
width: 100vw;
height: 48px;
justify-content: left;
padding: 0 32px;
justify-content: center;
.header-items-wrapper {
width: 960px;
@ -25,7 +24,7 @@ const Header = styled.header`
width: 475px;
}
@media (max-width: 375px) {
width: 311px;
padding: 0 16px;
}
}
@ -57,7 +56,7 @@ const HeaderUnAuth = ({
isAuthenticated,
isLoaded,
}) => {
const { t } = useTranslation();
const { t } = useTranslation("NavMenu");
return (
<Header isLoaded={isLoaded} className="navMenuHeaderUnAuth">
@ -110,7 +109,7 @@ export default inject(({ auth }) => {
const { enableAdmMess, wizardToken } = settingsStore;
return {
enableAdmMess,
wizardToken: wizardToken || "/",
wizardToken,
isAuthenticated,
isLoaded,
};

View File

@ -9,6 +9,7 @@ import UnionIcon from "../svg/union.react.svg";
import RecoverAccessModalDialog from "./recover-access-modal-dialog";
import { sendRecoverRequest } from "@appserver/common/api/settings/index";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import { Base } from "@appserver/components/themes";
const StyledUnionIcon = styled(UnionIcon)`
${commonIconsStyles}
@ -16,6 +17,7 @@ const StyledUnionIcon = styled(UnionIcon)`
const RecoverContainer = styled(Box)`
cursor: pointer;
background-color: ${(props) => props.theme.header.recoveryColor};
.recover-icon {
@media (max-width: 450px) {
@ -29,6 +31,8 @@ const RecoverContainer = styled(Box)`
}
`;
RecoverContainer.defaultProps = { theme: Base };
const RecoverAccess = ({ t }) => {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
@ -88,7 +92,6 @@ const RecoverAccess = ({ t }) => {
alignItems="center"
>
<RecoverContainer
backgroundProp="#27537F"
heightProp="100%"
displayProp="flex"
onClick={onRecoverClick}

View File

@ -12,12 +12,11 @@ import Box from "@appserver/components/box";
import withLoader from "../withLoader";
import toastr from "studio/toastr";
import ErrorContainer from "@appserver/common/components/ErrorContainer";
import { mobile, tablet } from "@appserver/components/utils/device";
import { hugeMobile, tablet } from "@appserver/components/utils/device";
import Link from "@appserver/components/link";
const StyledForm = styled(Box)`
margin: 63px auto auto 216px;
width: 960px;
display: flex;
flex: 1fr 1fr;
gap: 80px;
@ -31,9 +30,9 @@ const StyledForm = styled(Box)`
gap: 32px;
}
@media ${mobile} {
margin: 72px 16px auto 8px;
width: 311px;
@media ${hugeMobile} {
margin-top: 72px;
width: 100%;
flex: 1fr;
flex-direction: column;
gap: 0px;
@ -45,6 +44,11 @@ const StyledForm = styled(Box)`
}
}
.set-app-description {
width: 100%;
max-width: 500px;
}
.set-app-title {
margin-bottom: 14px;
}
@ -62,7 +66,7 @@ const StyledForm = styled(Box)`
border-radius: 6px;
margin-bottom: 32px;
@media ${mobile} {
@media ${hugeMobile} {
display: none;
}
}
@ -106,23 +110,17 @@ const TfaActivationForm = withLoader((props) => {
if (target.code === "Enter" || target.code === "NumpadEnter") onSubmit();
};
const width = window.innerWidth;
return (
<Section>
<Section.SectionBody>
<StyledForm className="set-app-container">
<div>
<Box className="set-app-description" marginProp="0 0 32px 0">
<Text isBold fontSize="14px" className="set-app-title">
{t("SetAppTitle")}
</Text>
<Trans t={t} i18nKey="SetAppDescription" ns="Confirm">
The two-factor authentication is enabled to provide additional
portal security. Configure your authenticator application to
continue work on the portal. For example you could use Google
Authenticator for
The two-factor authentication is enabled to provide additional portal
security. Configure your authenticator application to continue work on
the portal. For example you could use Google Authenticator for
<Link isHovered href={props.tfaAndroidAppUrl} target="_blank">
Android
</Link>
@ -144,26 +142,19 @@ const TfaActivationForm = withLoader((props) => {
ns="Confirm"
key={secretKey}
>
To connect your apllication scan the QR code or manually enter
your secret key <strong>{{ secretKey }}</strong> then enter
6-digit code from your application in the field below.
To connect your apllication scan the QR code or manually enter your
secret key <strong>{{ secretKey }}</strong> then enter 6-digit code
from your application in the field below.
</Trans>
</Text>
</Box>
</div>
<div>
<Box
displayProp="flex"
flexDirection="column"
className="app-code-wrapper"
>
<div className="qrcode-wrapper">
<img
src={qrCode}
height="180px"
width="180px"
alt="QR-code"
></img>
<img src={qrCode} height="180px" width="180px" alt="QR-code"></img>
</div>
<Box className="app-code-input">
<FieldContainer
@ -199,9 +190,7 @@ const TfaActivationForm = withLoader((props) => {
size="medium"
tabIndex={3}
label={
isLoading
? t("Common:LoadingProcessing")
: t("SetAppButton")
isLoading ? t("Common:LoadingProcessing") : t("SetAppButton")
}
isDisabled={!code.length || isLoading}
isLoading={isLoading}
@ -209,10 +198,7 @@ const TfaActivationForm = withLoader((props) => {
/>
</Box>
</Box>
</div>
</StyledForm>
</Section.SectionBody>
</Section>
);
});
@ -233,7 +219,7 @@ const TfaActivationWrapper = (props) => {
setSecretKey(manualEntryKey);
setQrCode(qrCodeSetupImageUrl);
} catch (e) {
setError(e);
setError(e.error);
toastr.error(e);
}
@ -244,7 +230,11 @@ const TfaActivationWrapper = (props) => {
return error ? (
<ErrorContainer bodyText={error} />
) : (
<Section>
<Section.SectionBody>
<TfaActivationForm secretKey={secretKey} qrCode={qrCode} {...props} />
</Section.SectionBody>
</Section>
);
};

View File

@ -11,7 +11,11 @@ import { inject, observer } from "mobx-react";
import Box from "@appserver/components/box";
import toastr from "studio/toastr";
import withLoader from "../withLoader";
import { mobile, tablet } from "@appserver/components/utils/device";
import {
hugeMobile,
smallTablet,
tablet,
} from "@appserver/components/utils/device";
const StyledForm = styled(Box)`
margin: 63px auto;
@ -24,9 +28,15 @@ const StyledForm = styled(Box)`
margin: 120px auto;
width: 480px;
}
@media ${mobile} {
margin: 72px 16px auto 8px;
width: 311px;
@media ${smallTablet} {
width: 400px;
}
@media ${hugeMobile} {
margin: 72px 8px auto 8px;
padding: 16px;
width: 100%;
}
.app-code-wrapper {

View File

@ -0,0 +1,183 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import RadioButtonGroup from "@appserver/components/radio-button-group";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import toastr from "@appserver/components/toast/toastr";
import { LearnMoreWrapper } from "../StyledSecurity";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import isEqual from "lodash/isEqual";
import { isMobile } from "react-device-detect";
import AdmMsgLoader from "../sub-components/loaders/admmsg-loader";
const MainContainer = styled.div`
width: 100%;
.page-subtitle {
margin-bottom: 10px;
}
.box {
margin-bottom: 24px;
}
`;
const AdminMessage = (props) => {
const {
t,
history,
enableAdmMess,
setMessageSettings,
initSettings,
isInit,
helpLink,
} = props;
const [type, setType] = useState("");
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const getSettings = () => {
const currentSettings = getFromSessionStorage(
"currentAdminMessageSettings"
);
const enable = enableAdmMess ? "enable" : "disabled";
saveToSessionStorage("defaultAdminMessageSettings", enable);
if (currentSettings) {
setType(currentSettings);
} else {
setType(enable);
}
};
useEffect(() => {
checkWidth();
if (!isInit) initSettings().then(() => setIsLoading(true));
else setIsLoading(true);
window.addEventListener("resize", checkWidth);
return () => window.removeEventListener("resize", checkWidth);
}, []);
useEffect(() => {
if (!isInit) return;
getSettings();
}, [isLoading]);
useEffect(() => {
if (!isLoading) return;
const defaultSettings = getFromSessionStorage(
"defaultAdminMessageSettings"
);
saveToSessionStorage("currentAdminMessageSettings", type);
if (isEqual(defaultSettings, type)) {
setShowReminder(false);
} else {
setShowReminder(true);
}
}, [type]);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("admin-message") &&
history.push("/settings/security/access-portal");
};
const onSelectType = (e) => {
if (type !== e.target.value) {
setType(e.target.value);
}
};
const onSaveClick = () => {
const turnOn = type === "enable" ? true : false;
setMessageSettings(turnOn);
toastr.success(t("SuccessfullySaveSettingsMessage"));
saveToSessionStorage("defaultAdminMessageSettings", type);
setShowReminder(false);
};
const onCancelClick = () => {
const defaultSettings = getFromSessionStorage(
"defaultAdminMessageSettings"
);
setType(defaultSettings);
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <AdmMsgLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>
<Text className="page-subtitle">{t("AdminsMessageHelper")}</Text>
<Link
color="#316DAA"
target="_blank"
isHovered
href={`${helpLink}/administration/configuration.aspx#ChangingSecuritySettings_block`}
>
{t("Common:LearnMore")}
</Link>
</LearnMoreWrapper>
<RadioButtonGroup
className="box"
fontSize="13px"
fontWeight="400"
name="group"
orientation="vertical"
spacing="8px"
options={[
{
label: t("Disabled"),
value: "disabled",
},
{
label: t("Common:Enable"),
value: "enable",
},
]}
selected={type}
onClick={onSelectType}
/>
<SaveCancelButtons
className="save-cancel-buttons"
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
showReminder={showReminder}
reminderTest={t("YouHaveUnsavedChanges")}
saveButtonLabel={t("Common:SaveButton")}
cancelButtonLabel={t("Common:CancelButton")}
displaySettings={true}
hasScroll={false}
/>
</MainContainer>
);
};
export default inject(({ auth, setup }) => {
const { enableAdmMess, setMessageSettings, helpLink } = auth.settingsStore;
const { initSettings, isInit } = setup;
return {
enableAdmMess,
setMessageSettings,
initSettings,
isInit,
helpLink,
};
})(withTranslation(["Settings", "Common"])(withRouter(observer(AdminMessage))));

View File

@ -7,6 +7,9 @@ import { MainContainer } from "../StyledSecurity";
import TfaSection from "./tfa";
import PasswordStrengthSection from "./passwordStrength";
import TrustedMailSection from "./trustedMail";
import IpSecuritySection from "./ipSecurity";
import AdminMessageSection from "./adminMessage";
import SessionLifetimeSection from "./sessionLifetime";
import MobileView from "./mobileView";
import CategoryWrapper from "../sub-components/category-wrapper";
import { size } from "@appserver/components/utils/device";
@ -56,6 +59,30 @@ const AccessPortal = (props) => {
tooltipUrl={`${helpLink}/administration/configuration.aspx#ChangingSecuritySettings_block`}
/>
<TrustedMailSection />
<hr />
<CategoryWrapper
t={t}
title={t("IPSecurity")}
tooltipContent={t("IPSecurityDescription")}
tooltipTitle={t("IPSecurityDescription")}
/>
<IpSecuritySection />
<hr />
<CategoryWrapper
t={t}
title={t("AdminsMessage")}
tooltipTitle={t("AdminsMessageDescription")}
tooltipUrl={`${helpLink}/administration/configuration.aspx#ChangingSecuritySettings_block`}
/>
<AdminMessageSection />
<hr />
<CategoryWrapper
t={t}
title={t("SessionLifetime")}
tooltipTitle={t("SessionLifetimeDescription")}
/>
<SessionLifetimeSection />
</MainContainer>
);
};

View File

@ -0,0 +1,260 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import Text from "@appserver/components/text";
import RadioButtonGroup from "@appserver/components/radio-button-group";
import toastr from "@appserver/components/toast/toastr";
import { LearnMoreWrapper } from "../StyledSecurity";
import UserFields from "../sub-components/user-fields";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import isEqual from "lodash/isEqual";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import { isMobile } from "react-device-detect";
import IpSecurityLoader from "../sub-components/loaders/ip-security-loader";
const MainContainer = styled.div`
width: 100%;
.page-subtitle {
margin-bottom: 10px;
}
.user-fields {
margin-bottom: 18px;
}
.box {
margin-bottom: 11px;
}
.warning-text {
margin-bottom: 9px;
}
.save-cancel-buttons {
margin-top: 24px;
}
`;
const IpSecurity = (props) => {
const {
t,
history,
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
initSettings,
isInit,
} = props;
const regexp = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$/; //check ip valid
const [enable, setEnable] = useState(false);
const [ips, setIps] = useState();
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const getSettings = () => {
const currentSettings = getFromSessionStorage("currentIPSettings");
const defaultData = {
enable: ipRestrictionEnable,
ips: ipRestrictions,
};
saveToSessionStorage("defaultIPSettings", defaultData);
if (currentSettings) {
setEnable(currentSettings.enable);
setIps(currentSettings.ips);
} else {
setEnable(ipRestrictionEnable);
setIps(ipRestrictions);
}
};
useEffect(() => {
checkWidth();
window.addEventListener("resize", checkWidth);
if (!isInit) initSettings().then(() => setIsLoading(true));
else setIsLoading(true);
return () => window.removeEventListener("resize", checkWidth);
}, []);
useEffect(() => {
if (!isInit) return;
getSettings();
}, [isLoading]);
useEffect(() => {
if (!isLoading) return;
const defaultSettings = getFromSessionStorage("defaultIPSettings");
const newSettings = {
enable: enable,
ips: ips,
};
saveToSessionStorage("currentIPSettings", newSettings);
if (isEqual(defaultSettings, newSettings)) {
setShowReminder(false);
} else {
setShowReminder(true);
}
}, [enable, ips]);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("ip") &&
history.push("/settings/security/access-portal");
};
const onSelectType = (e) => {
setEnable(e.target.value === "enable" ? true : false);
};
const onChangeInput = (e, index) => {
let newInputs = Array.from(ips);
newInputs[index] = e.target.value;
setIps(newInputs);
};
const onDeleteInput = (index) => {
let newInputs = Array.from(ips);
newInputs.splice(index, 1);
setIps(newInputs);
};
const onClickAdd = () => {
setIps([...ips, ""]);
};
const onSaveClick = async () => {
setIsSaving(true);
const valid = ips.map((ip) => regexp.test(ip));
if (valid.includes(false)) {
setIsSaving(false);
return;
}
try {
await setIpRestrictions(ips);
await setIpRestrictionsEnable(enable);
saveToSessionStorage("defaultIPSettings", {
enable: enable,
ips: ips,
});
setShowReminder(false);
toastr.success(t("SuccessfullySaveSettingsMessage"));
} catch (error) {
toastr.error(error);
}
setIsSaving(false);
};
const onCancelClick = () => {
const defaultSettings = getFromSessionStorage("defaultIPSettings");
setEnable(defaultSettings.enable);
setIps(defaultSettings.ips);
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <IpSecurityLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>
<Text className="page-subtitle">{t("IPSecurityHelper")}</Text>
</LearnMoreWrapper>
<RadioButtonGroup
className="box"
fontSize="13px"
fontWeight="400"
name="group"
orientation="vertical"
spacing="8px"
options={[
{
label: t("Disabled"),
value: "disabled",
},
{
label: t("Common:Enable"),
value: "enable",
},
]}
selected={enable ? "enable" : "disabled"}
onClick={onSelectType}
/>
{enable && (
<UserFields
className="user-fields"
inputs={ips}
buttonLabel={t("AddAllowedIP")}
onChangeInput={onChangeInput}
onDeleteInput={onDeleteInput}
onClickAdd={onClickAdd}
regexp={regexp}
/>
)}
{enable && (
<>
<Text
color="#F21C0E"
fontSize="16px"
fontWeight="700"
className="warning-text"
>
{t("Common:Warning")}!
</Text>
<Text>{t("IPSecurityWarningHelper")}</Text>
</>
)}
<SaveCancelButtons
className="save-cancel-buttons"
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
showReminder={showReminder}
reminderTest={t("YouHaveUnsavedChanges")}
saveButtonLabel={t("Common:SaveButton")}
cancelButtonLabel={t("Common:CancelButton")}
displaySettings={true}
hasScroll={false}
isSaving={isSaving}
/>
</MainContainer>
);
};
export default inject(({ auth, setup }) => {
const {
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
} = auth.settingsStore;
const { initSettings, isInit } = setup;
return {
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
initSettings,
isInit,
};
})(withTranslation(["Settings", "Common"])(withRouter(observer(IpSecurity))));

View File

@ -37,6 +37,24 @@ const MobileView = (props) => {
url="/settings/security/access-portal/trusted-mail"
onClickLink={onClickLink}
/>
<MobileCategoryWrapper
title={t("IPSecurity")}
subtitle={t("IPSecurityDescription")}
url="/settings/security/access-portal/ip"
onClickLink={onClickLink}
/>
<MobileCategoryWrapper
title={t("AdminsMessage")}
subtitle={t("AdminsMessageDescription")}
url="/settings/security/access-portal/admin-message"
onClickLink={onClickLink}
/>
<MobileCategoryWrapper
title={t("SessionLifetime")}
subtitle={t("SessionLifetimeDescription")}
url="/settings/security/access-portal/lifetime"
onClickLink={onClickLink}
/>
</MainContainer>
);
};

View File

@ -14,6 +14,8 @@ import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import isEqual from "lodash/isEqual";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import { isMobile } from "react-device-detect";
import PasswordLoader from "../sub-components/loaders/password-loader";
const MainContainer = styled.div`
width: 100%;
@ -175,6 +177,10 @@ const PasswordStrength = (props) => {
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <PasswordLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>

View File

@ -0,0 +1,230 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import RadioButtonGroup from "@appserver/components/radio-button-group";
import Text from "@appserver/components/text";
import TextInput from "@appserver/components/text-input";
import toastr from "@appserver/components/toast/toastr";
import { LearnMoreWrapper } from "../StyledSecurity";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import isEqual from "lodash/isEqual";
import { isMobile } from "react-device-detect";
import SessionLifetimeLoader from "../sub-components/loaders/session-lifetime-loader";
const MainContainer = styled.div`
width: 100%;
.lifetime {
margin-top: 16px;
margin-bottom: 8px;
}
.save-cancel-buttons {
margin-top: 24px;
}
`;
const SessionLifetime = (props) => {
const {
t,
history,
lifetime,
setSessionLifetimeSettings,
initSettings,
isInit,
} = props;
const [type, setType] = useState(false);
const [sessionLifetime, setSessionLifetime] = useState("0");
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(false);
const getSettings = () => {
const currentSettings = getFromSessionStorage(
"currentSessionLifetimeSettings"
);
const defaultData = {
lifetime: lifetime.toString(),
type: lifetime > 0 ? true : false,
};
saveToSessionStorage("defaultSessionLifetimeSettings", defaultData);
if (currentSettings) {
setSessionLifetime(currentSettings.lifetime);
setType(currentSettings.type);
} else {
setSessionLifetime(lifetime.toString());
setType(lifetime > 0 ? true : false);
}
if (currentSettings) {
setType(currentSettings.type);
setSessionLifetime(currentSettings.lifetime);
} else {
setType(lifetime > 0 ? true : false);
setSessionLifetime(lifetime.toString());
}
setIsLoading(true);
};
useEffect(() => {
checkWidth();
if (!isInit) initSettings().then(() => setIsLoading(true));
else setIsLoading(true);
window.addEventListener("resize", checkWidth);
return () => window.removeEventListener("resize", checkWidth);
}, []);
useEffect(() => {
if (!isInit) return;
getSettings();
}, [isLoading]);
useEffect(() => {
if (!isLoading) return;
const defaultSettings = getFromSessionStorage(
"defaultSessionLifetimeSettings"
);
const newSettings = {
lifetime: type === false ? "0" : sessionLifetime,
type: type,
};
saveToSessionStorage("currentSessionLifetimeSettings", newSettings);
if (isEqual(defaultSettings, newSettings)) {
setShowReminder(false);
} else {
setShowReminder(true);
}
}, [type, sessionLifetime]);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("lifetime") &&
history.push("/settings/security/access-portal");
};
const onSelectType = (e) => {
setType(e.target.value === "enable" ? true : false);
};
const onChangeInput = (e) => {
setSessionLifetime(e.target.value);
};
const onBlurInput = () => {
!sessionLifetime ? setError(true) : setError(false);
};
const onFocusInput = () => {
setError(false);
};
const onSaveClick = async () => {
if (error) return;
try {
const lft = type === false ? "0" : sessionLifetime;
setSessionLifetimeSettings(lft);
toastr.success(t("SuccessfullySaveSettingsMessage"));
saveToSessionStorage("defaultSessionLifetimeSettings", {
lifetime: lft,
type: type,
});
setShowReminder(false);
} catch (error) {
toastr.error(error);
}
};
const onCancelClick = () => {
const defaultSettings = getFromSessionStorage(
"defaultSessionLifetimeSettings"
);
setType(defaultSettings.type);
setSessionLifetime(defaultSettings.lifetime);
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <SessionLifetimeLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>
<Text>{t("SessionLifetimeHelper")}</Text>
</LearnMoreWrapper>
<RadioButtonGroup
className="box"
fontSize="13px"
fontWeight="400"
name="group"
orientation="vertical"
spacing="8px"
options={[
{
label: t("Disabled"),
value: "disabled",
},
{
label: t("Common:Enable"),
value: "enable",
},
]}
selected={type ? "enable" : "disabled"}
onClick={onSelectType}
/>
{type && (
<>
<Text className="lifetime" fontSize="15px" fontWeight="600">
{t("Lifetime")}
</Text>
<TextInput
isAutoFocussed={false}
value={sessionLifetime}
onChange={onChangeInput}
onBlur={onBlurInput}
onFocus={onFocusInput}
hasError={error}
/>
</>
)}
<SaveCancelButtons
className="save-cancel-buttons"
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
showReminder={showReminder}
reminderTest={t("YouHaveUnsavedChanges")}
saveButtonLabel={t("Common:SaveButton")}
cancelButtonLabel={t("Common:CancelButton")}
displaySettings={true}
hasScroll={false}
/>
</MainContainer>
);
};
export default inject(({ auth, setup }) => {
const { sessionLifetime, setSessionLifetimeSettings } = auth.settingsStore;
const { initSettings, isInit } = setup;
return {
lifetime: sessionLifetime,
setSessionLifetimeSettings,
initSettings,
isInit,
};
})(
withTranslation(["Settings", "Common"])(withRouter(observer(SessionLifetime)))
);

View File

@ -11,6 +11,8 @@ import { LearnMoreWrapper } from "../StyledSecurity";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import { isMobile } from "react-device-detect";
import TfaLoader from "../sub-components/loaders/tfa-loader";
const MainContainer = styled.div`
width: 100%;
@ -115,6 +117,11 @@ const TwoFactorAuth = (props) => {
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <TfaLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>

View File

@ -13,6 +13,8 @@ import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import isEqual from "lodash/isEqual";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import { isMobile } from "react-device-detect";
import TrustedMailLoader from "../sub-components/loaders/trusted-mail-loader";
const MainContainer = styled.div`
width: 100%;
@ -150,6 +152,11 @@ const TrustedMail = (props) => {
setShowReminder(false);
};
if (isMobile && !isLoading) {
return <TrustedMailLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>

View File

@ -9,7 +9,11 @@ import config from "../../../../../../package.json";
import AccessRights from "./access-rights/index.js";
import AccessPortal from "./access-portal/index.js";
import AppLoader from "@appserver/common/components/AppLoader";
import SecurityLoader from "./sub-components/loaders/security-loader";
import MobileSecurityLoader from "./sub-components/loaders/mobile-security-loader";
import AccessLoader from "./sub-components/loaders/access-loader";
import { isMobile } from "react-device-detect";
const SecurityWrapper = (props) => {
const { t, history, loadBaseInfo } = props;
@ -31,13 +35,14 @@ const SecurityWrapper = (props) => {
const load = async () => {
await loadBaseInfo();
const path = location.pathname;
const currentTab = data.findIndex((item) => path.includes(item.id));
if (currentTab !== -1) setCurrentTab(currentTab);
setIsLoading(true);
};
useEffect(() => {
const path = location.pathname;
const currentTab = data.findIndex((item) => path.includes(item.id));
if (currentTab !== -1) setCurrentTab(currentTab);
load();
}, []);
@ -51,7 +56,16 @@ const SecurityWrapper = (props) => {
);
};
if (!isLoading) return <AppLoader />;
if (!isLoading)
return currentTab === 0 ? (
isMobile ? (
<MobileSecurityLoader />
) : (
<SecurityLoader />
)
) : (
<AccessLoader />
);
return (
<Submenu
data={data}

View File

@ -10,10 +10,12 @@ const CategoryWrapper = (props) => {
const tooltip = () => (
<StyledTooltip>
<Text className="subtitle">{tooltipTitle}</Text>
<Text className={tooltipUrl ? "subtitle" : ""}>{tooltipTitle}</Text>
{tooltipUrl && (
<Link target="_blank" isHovered href={tooltipUrl}>
{t("Common:LearnMore")}
</Link>
)}
</StyledTooltip>
);

View File

@ -0,0 +1,86 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
import { tablet } from "@appserver/components/utils/device";
const StyledLoader = styled.div`
.header {
width: 296px;
height: 29px;
margin-bottom: 14px;
@media (${tablet}) {
width: 184px;
height: 37px;
}
@media (max-width: 428px) {
width: 273px;
height: 37px;
margin-bottom: 18px;
}
}
.submenu {
display: flex;
gap: 20px;
margin-bottom: 22px;
}
.owner {
width: 700px;
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 40px;
@media (max-width: 428px) {
width: 100%;
}
.header {
height: 40px;
@media (${tablet}) {
height: 60px;
}
}
}
.admins {
display: flex;
flex-direction: column;
gap: 8px;
.description {
width: 700px;
@media (${tablet}) {
width: 100%;
}
}
}
`;
const AccessLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="100%" />
<div className="submenu">
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
</div>
<div className="owner">
<Loaders.Rectangle className="header" height="100%" />
<Loaders.Rectangle height="82px" />
</div>
<div className="admins">
<Loaders.Rectangle height="22px" width="77px" />
<Loaders.Rectangle height="20px" width="56px" />
<Loaders.Rectangle className="description" height="40px" />
</div>
</StyledLoader>
);
};
export default AccessLoader;

View File

@ -0,0 +1,55 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 8px;
}
.link {
margin-bottom: 20px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
margin-bottom: 11px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const AdmMsgLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="80px" />
<div className="link">
<Loaders.Rectangle height="20px" width="57px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default AdmMsgLoader;

View File

@ -0,0 +1,66 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 12px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
margin-bottom: 15px;
}
.add-button {
width: 85px;
margin-bottom: 16px;
}
.block {
display: flex;
flex-direction: column;
gap: 8px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const IpSecurityLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="80px" />
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<Loaders.Rectangle className="add-button" height="20px" />
<div className="block">
<Loaders.Rectangle height="22px" width="72px" />
<Loaders.Rectangle height="64px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default IpSecurityLoader;

View File

@ -0,0 +1,59 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
.header {
width: 273px;
margin-bottom: 18px;
}
.submenu {
display: flex;
gap: 20px;
margin-bottom: 30px;
}
.category {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 30px;
}
`;
const MobileSecurityLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<div className="submenu">
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
</div>
<div className="category">
<Loaders.Rectangle height="22px" width="236px" />
<Loaders.Rectangle height="60px" />
</div>
<div className="category">
<Loaders.Rectangle height="22px" width="227px" />
<Loaders.Rectangle height="120px" />
</div>
<div className="category">
<Loaders.Rectangle height="22px" />
<Loaders.Rectangle height="40px" />
</div>
<div className="category">
<Loaders.Rectangle height="22px" width="101px" />
<Loaders.Rectangle height="40px" />
</div>
</StyledLoader>
);
};
export default MobileSecurityLoader;

View File

@ -0,0 +1,70 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 8px;
}
.link {
margin-bottom: 20px;
}
.subheader {
margin-bottom: 16px;
}
.slider {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const PasswordLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="80px" />
<div className="link">
<Loaders.Rectangle height="20px" width="57px" />
</div>
<Loaders.Rectangle className="subheader" height="16px" width="171px" />
<div className="slider">
<Loaders.Rectangle height="24px" width="160px" />
<Loaders.Rectangle height="20px" width="75px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default PasswordLoader;

View File

@ -0,0 +1,188 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
import { tablet } from "@appserver/components/utils/device";
const StyledLoader = styled.div`
hr {
margin: 24px 0;
border: none;
border-top: 1px solid #eceef1;
}
.submenu {
width: 296px;
height: 29px;
margin-bottom: 14px;
@media (${tablet}) {
width: 184px;
height: 37px;
}
}
.header {
display: flex;
margin-bottom: 22px;
.header-item {
width: 72px;
margin-right: 20px;
}
}
.description {
width: 591px;
margin-bottom: 20px;
@media (${tablet}) {
width: 100%;
}
}
.buttons {
width: 192px;
height: 32px;
@media (${tablet}) {
height: 40px;
}
}
.password-settings {
.header {
width: 132px;
margin-bottom: 16px;
}
.subheader {
width: 171px;
margin-bottom: 16px;
}
.slider {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
}
}
.tfa-settings {
.header {
width: 227px;
margin-bottom: 16px;
}
.radio-buttons {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
}
}
.domain-settings {
.header {
width: 132px;
margin-bottom: 16px;
}
.radio-buttons {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 11px;
}
.inputs {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
.input {
display: flex;
gap: 8px;
align-items: center;
}
}
.button {
width: 85px;
}
}
`;
const SecurityLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="submenu" height="100%" />
<div className="header">
<Loaders.Rectangle className="header-item" height="28px" />
<Loaders.Rectangle className="header-item" height="28px" />
<Loaders.Rectangle className="header-item" height="28px" />
<Loaders.Rectangle className="header-item" height="28px" />
</div>
<Loaders.Rectangle className="description" height="20px" />
<div className="password-settings">
<Loaders.Rectangle className="header" height="22px" />
<Loaders.Rectangle className="subheader" height="16px" />
<div className="slider">
<Loaders.Rectangle height="24px" width="160px" />
<Loaders.Rectangle height="20px" width="75px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" width="133px" />
<Loaders.Rectangle height="20px" width="83px" />
<Loaders.Rectangle height="20px" width="159px" />
</div>
<Loaders.Rectangle className="buttons" height="100%" />
</div>
<hr />
<div className="tfa-settings">
<Loaders.Rectangle className="header" height="22px" />
<div className="radio-buttons">
<Loaders.Rectangle height="20px" width="69px" />
<Loaders.Rectangle height="20px" width="69px" />
<Loaders.Rectangle height="20px" width="152px" />
</div>
<Loaders.Rectangle className="buttons" height="100%" />
</div>
<hr />
<div className="domain-settings">
<Loaders.Rectangle className="header" height="22px" />
<div className="radio-buttons">
<Loaders.Rectangle height="20px" width="77px" />
<Loaders.Rectangle height="20px" width="103px" />
<Loaders.Rectangle height="20px" width="127px" />
</div>
<div className="inputs">
<div className="input">
<Loaders.Rectangle height="32px" width="350px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<div className="input">
<Loaders.Rectangle height="32px" width="350px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<div className="input">
<Loaders.Rectangle height="32px" width="350px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<Loaders.Rectangle className="button" height="20px" />
</div>
</div>
</StyledLoader>
);
};
export default SecurityLoader;

View File

@ -0,0 +1,59 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 12px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
margin-bottom: 16px;
}
.input {
display: flex;
flex-direction: column;
gap: 4px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const SessionLifetimeLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="20px" />
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<div className="input">
<Loaders.Rectangle height="20px" width="95px" />
<Loaders.Rectangle height="32px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default SessionLifetimeLoader;

View File

@ -0,0 +1,55 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 8px;
}
.link {
margin-bottom: 20px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const TfaLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="40px" />
<div className="link">
<Loaders.Rectangle height="20px" width="57px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default TfaLoader;

View File

@ -0,0 +1,90 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 8px;
}
.link {
margin-bottom: 20px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
margin-bottom: 11px;
}
.inputs {
display: flex;
flex-direction: column;
gap: 8px;
.input {
display: flex;
gap: 8px;
align-items: center;
}
.add {
width: 85px;
margin-top: 16px;
}
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const TrustedMailLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="100px" />
<div className="link">
<Loaders.Rectangle height="20px" width="57px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<div className="inputs">
<div className="input">
<Loaders.Rectangle height="32px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<div className="input">
<Loaders.Rectangle height="32px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<div className="input">
<Loaders.Rectangle height="32px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<Loaders.Rectangle className="add" height="20px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default TrustedMailLoader;

View File

@ -92,13 +92,25 @@ const UserFields = (props) => {
<div className={className}>
{inputs ? (
inputs.map((input, index) => {
const error = !regexp.test(input);
let newInput1;
let newInput2;
if (input.includes("-")) {
newInput1 = input.split("-")[0];
newInput2 = input.split("-")[1];
}
const error = newInput2
? input.split("-").length - 1 > 1 ||
!regexp.test(newInput1) ||
!regexp.test(newInput2)
: !regexp.test(input);
return (
<StyledInputWrapper key={`domain-input-${index}`}>
<StyledInputWrapper key={`user-input-${index}`}>
<TextInput
id={`domain-input-${index}`}
isAutoFocussed={true}
id={`user-input-${index}`}
isAutoFocussed={false}
value={input}
onChange={(e) => onChangeInput(e, index)}
onBlur={() => onBlur(index)}

View File

@ -14,6 +14,15 @@ const PasswordStrengthPage = lazy(() =>
const TrustedMailPage = lazy(() =>
import("./categories/security/access-portal/trustedMail")
);
const IpSecurityPage = lazy(() =>
import("./categories/security/access-portal/ipSecurity")
);
const AdminMessagePage = lazy(() =>
import("./categories/security/access-portal/adminMessage")
);
const SessionLifetimePage = lazy(() =>
import("./categories/security/access-portal/sessionLifetime")
);
const CommonSettings = lazy(() => import("./categories/common/index.js"));
@ -97,6 +106,18 @@ const TRUSTED_MAIL_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/trusted-mail"
);
const IP_SECURITY_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/ip"
);
const ADMIN_MESSAGE_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/admin-message"
);
const SESSION_LIFETIME_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/lifetime"
);
const ADMINS_URL = combineUrl(PROXY_BASE_URL, "/security/access-rights/admins");
@ -153,6 +174,17 @@ const Settings = (props) => {
path={TRUSTED_MAIL_PAGE_URL}
component={TrustedMailPage}
/>
<Route exact path={IP_SECURITY_PAGE_URL} component={IpSecurityPage} />
<Route
exact
path={ADMIN_MESSAGE_PAGE_URL}
component={AdminMessagePage}
/>
<Route
exact
path={SESSION_LIFETIME_PAGE_URL}
component={SessionLifetimePage}
/>
<Route exact path={INTEGRATION_URLS} component={Integration} />
<Route

View File

@ -77,6 +77,24 @@ export const settingsTree = [
link: "trusted-mail",
tKey: "TrustedMail",
},
{
key: "1-0-3",
icon: "",
link: "ip",
tKey: "IPSecurity",
},
{
key: "1-0-4",
icon: "",
link: "admin-message",
tKey: "AdminsMessage",
},
{
key: "1-0-5",
icon: "",
link: "lifetime",
tKey: "SessionLifetime",
},
],
},
{

View File

@ -43,9 +43,7 @@ class ConfirmRoute extends React.Component {
.then((validationResult) => {
switch (validationResult) {
case ValidationResult.Ok:
const confirmHeader = `type=${confirmLinkData.type}&${search.slice(
1
)}`;
const confirmHeader = `${confirmLinkData}&${search.slice(1)}`;
const linkData = {
...confirmLinkData,
confirmHeader,

View File

@ -53,6 +53,9 @@ class SettingsSetupStore {
if (authStore.isAuthenticated) {
await authStore.settingsStore.getPortalPasswordSettings();
await authStore.tfaStore.getTfaType();
await authStore.settingsStore.getIpRestrictionsEnable();
await authStore.settingsStore.getIpRestrictions();
await authStore.settingsStore.getSessionLifetime();
}
};
@ -216,22 +219,6 @@ class SettingsSetupStore {
const res = await api.settings.setMailDomainSettings(dnsName, enable);
};
setIpRestrictions = async (data) => {
const res = await api.settings.setIpRestrictions(data);
};
setIpRestrictionsEnable = async (data) => {
const res = await api.settings.setIpRestrictionsEnable(data);
};
setMessageSettings = async (turnOn) => {
const res = await api.settings.setMessageSettings(turnOn);
};
setCookieSettings = async (lifeTime) => {
const res = await api.settings.setCookieSettings(lifeTime);
};
setLifetimeAuditSettings = async (data) => {
const res = await api.settings.setLifetimeAuditSettings(data);
};

View File

@ -1052,7 +1052,7 @@ Scenario("Trusted mail settings change test success", async ({ I }) => {
I.click("Add trusted domain");
I.seeElement("#domain-input-0");
I.fillField("#domain-input-0", "test.com");
I.fillField("#user-input-0", "test.com");
I.click("Save");
@ -1089,7 +1089,7 @@ Scenario("Trusted mail settings change test error", async ({ I }) => {
I.click("Add trusted domain");
I.seeElement("#domain-input-0");
I.fillField("#domain-input-0", "test");
I.fillField("#user-input-0", "test");
I.click("Save");

View File

@ -31,6 +31,7 @@ public class CollaboratorSettings : ISettings<CollaboratorSettings>
{
public bool FirstVisit { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{73537E08-17F6-4706-BFDA-1414108AA7D2}"); }

View File

@ -31,6 +31,7 @@ public class CustomNavigationSettings : ISettings<CustomNavigationSettings>
{
public List<CustomNavigationItem> Items { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{32E02E4C-925D-4391-BAA4-3B5D223A2104}"); }

View File

@ -31,6 +31,7 @@ public class EmailActivationSettings : ISettings<EmailActivationSettings>
{
public bool Show { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{85987929-1339-48EB-B06D-B9D097BDACF6}"); }

View File

@ -150,3 +150,4 @@ global using static ASC.Web.Core.Files.DocumentService;
global using License = ASC.Core.Billing.License;
global using SecurityContext = ASC.Core.SecurityContext;
global using JsonIgnoreAttribute = System.Text.Json.Serialization.JsonIgnoreAttribute;

View File

@ -33,6 +33,7 @@ public class SpamEmailSettings : ISettings<SpamEmailSettings>
public DateTime MailsSendedDate { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{A9819A62-60AF-48E3-989C-08259772FA57}"); }

View File

@ -36,6 +36,7 @@ public class PersonalSettings : ISettings<PersonalSettings>
[JsonPropertyName("IsNotActivated")]
public bool IsNotActivatedSetting { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{B3427865-8E32-4E66-B6F3-91C61922239F}"); }

View File

@ -31,6 +31,7 @@ public class PrivacyRoomSettings : ISettings<PrivacyRoomSettings>
[JsonPropertyName("enbaled")]
public bool EnabledSetting { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{FCF002BC-EC4B-4DAB-A6CE-BDE0ABDA44D3}"); }

View File

@ -31,6 +31,7 @@ public class PromotionsSettings : ISettings<PromotionsSettings>
{
public bool Show { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{D291A4C1-179D-4ced-895A-E094E809C859}"); }

View File

@ -29,6 +29,7 @@ namespace ASC.Web.Studio.Core.SMS;
[Serializable]
public class StudioSmsNotificationSettings : ISettings<StudioSmsNotificationSettings>
{
[JsonIgnore]
public Guid ID
{
get { return new Guid("{2802df61-af0d-40d4-abc5-a8506a5352ff}"); }

View File

@ -29,6 +29,7 @@ namespace ASC.Web.Studio.UserControls.Management.SingleSignOnSettings;
[Serializable]
public class SsoSettingsV2 : ISettings<SsoSettingsV2>
{
[JsonIgnore]
public Guid ID
{
get { return new Guid("{1500187F-B8AB-406F-97B8-04BFE8261DBE}"); }

View File

@ -30,6 +30,7 @@ public class StudioAdminMessageSettings : ISettings<StudioAdminMessageSettings>
{
public bool Enable { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{28902650-58A9-11E1-B6A9-0F194924019B}"); }

View File

@ -31,6 +31,7 @@ public class StudioDefaultPageSettings : ISettings<StudioDefaultPageSettings>
{
public Guid DefaultProductID { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{F3FF27C5-BDE3-43ae-8DD0-2E8E0D7044F1}"); }

View File

@ -30,6 +30,7 @@ public class StudioTrustedDomainSettings : ISettings<StudioTrustedDomainSettings
{
public bool InviteUsersAsVisitors { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{00A2DB01-BAE3-48aa-BE32-CE768D7C874E}"); }

View File

@ -50,6 +50,7 @@ public class TariffSettings : ISettings<TariffSettings>
};
}
[JsonIgnore]
public Guid ID
{
get { return new Guid("{07956D46-86F7-433b-A657-226768EF9B0D}"); }

View File

@ -29,6 +29,7 @@ namespace ASC.Web.Studio.Core.TFA;
[Serializable]
public class TfaAppAuthSettings : ISettings<TfaAppAuthSettings>
{
[JsonIgnore]
public Guid ID
{
get { return new Guid("{822CA059-AA8F-4588-BEE3-6CD2AA920CDB}"); }

View File

@ -35,6 +35,7 @@ public class TfaAppUserSettings : ISettings<TfaAppUserSettings>
[JsonPropertyName("Salt")]
public long SaltSetting { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{EAF10611-BE1E-4634-B7A1-57F913042F78}"); }

View File

@ -32,6 +32,7 @@ public class TipsSettings : ISettings<TipsSettings>
[DataMember(Name = "Show")]
public bool Show { get; set; }
[JsonIgnore]
public Guid ID
{
get { return new Guid("{27909339-B4D4-466F-8F40-A64C9D2FC041}"); }

View File

@ -29,6 +29,7 @@ namespace ASC.Web.Core.Users;
[Serializable]
public class PeopleNamesSettings : ISettings<PeopleNamesSettings>
{
[JsonIgnore]
public Guid ID
{
get { return new Guid("47F34957-6A70-4236-9681-C8281FB762FA"); }

View File

@ -29,6 +29,7 @@ namespace ASC.Web.Core.Users;
[Serializable]
public class UserHelpTourSettings : ISettings<UserHelpTourSettings>
{
[JsonIgnore]
public Guid ID
{
get { return new Guid("{DF4B94B7-42C8-4fce-AAE2-D479F3B39BDD}"); }

View File

@ -29,6 +29,7 @@ namespace ASC.Web.Core.Users;
[Serializable]
public class UserPhotoThumbnailSettings : ISettings<UserPhotoThumbnailSettings>
{
[JsonIgnore]
public Guid ID
{
get { return new Guid("{CC3AA821-43CA-421B-BDCD-81FB6D3361CF}"); }

View File

@ -46,6 +46,7 @@ public class ColorThemesSettings : ISettings<ColorThemesSettings>
};
}
[JsonIgnore]
public Guid ID
{
get { return new Guid("{AB5B3C97-A972-475C-BB13-71936186C4E6}"); }

Some files were not shown because too many files have changed in this diff Show More