diff --git a/build/install/OneClickInstall/install-Debian.sh b/build/install/OneClickInstall/install-Debian.sh index d73049cf98..b00d4ee8e7 100644 --- a/build/install/OneClickInstall/install-Debian.sh +++ b/build/install/OneClickInstall/install-Debian.sh @@ -37,6 +37,13 @@ while [ "$1" != "" ]; do fi ;; + -skiphc | --skiphardwarecheck ) + if [ "$2" != "" ]; then + SKIP_HARDWARE_CHECK=$2 + shift + fi + ;; + -? | -h | --help ) echo " Usage $0 [PARAMETER] [[PARAMETER], ...]" echo " Parameters:" @@ -60,6 +67,10 @@ if [ -z "${LOCAL_SCRIPTS}" ]; then LOCAL_SCRIPTS="false"; fi +if [ -z "${SKIP_HARDWARE_CHECK}" ]; then + SKIP_HARDWARE_CHECK="false"; +fi + if [ $(dpkg-query -W -f='${Status}' curl 2>/dev/null | grep -c "ok installed") -eq 0 ]; then apt-get update; apt-get install -yq curl; diff --git a/build/install/OneClickInstall/install-Debian/tools.sh b/build/install/OneClickInstall/install-Debian/tools.sh index c1631d0f0f..f8a939253d 100644 --- a/build/install/OneClickInstall/install-Debian/tools.sh +++ b/build/install/OneClickInstall/install-Debian/tools.sh @@ -6,6 +6,37 @@ command_exists () { type "$1" &> /dev/null; } +check_hardware () { + DISK_REQUIREMENTS=40960; + MEMORY_REQUIREMENTS=5500; + CORE_REQUIREMENTS=2; + + AVAILABLE_DISK_SPACE=$(df -m / | tail -1 | awk '{ print $4 }'); + + if [ ${AVAILABLE_DISK_SPACE} -lt ${DISK_REQUIREMENTS} ]; then + echo "Minimal requirements are not met: need at least $DISK_REQUIREMENTS MB of free HDD space" + exit 1; + fi + + TOTAL_MEMORY=$(free -m | grep -oP '\d+' | head -n 1); + + if [ ${TOTAL_MEMORY} -lt ${MEMORY_REQUIREMENTS} ]; then + echo "Minimal requirements are not met: need at least $MEMORY_REQUIREMENTS MB of RAM" + exit 1; + fi + + CPU_CORES_NUMBER=$(cat /proc/cpuinfo | grep processor | wc -l); + + if [ ${CPU_CORES_NUMBER} -lt ${CORE_REQUIREMENTS} ]; then + echo "The system does not meet the minimal hardware requirements. CPU with at least $CORE_REQUIREMENTS cores is required" + exit 1; + fi +} + +if [ "$SKIP_HARDWARE_CHECK" != "true" ]; then + check_hardware +fi + ARCH="$(dpkg --print-architecture)" if [ "$ARCH" != "amd64" ]; then echo "ONLYOFFICE ${product^^} doesn't support architecture '$ARCH'" diff --git a/build/install/OneClickInstall/install-RedHat.sh b/build/install/OneClickInstall/install-RedHat.sh index 16c5ba82a9..c82f0c5a77 100644 --- a/build/install/OneClickInstall/install-RedHat.sh +++ b/build/install/OneClickInstall/install-RedHat.sh @@ -47,6 +47,13 @@ while [ "$1" != "" ]; do fi ;; + -skiphc | --skiphardwarecheck ) + if [ "$2" != "" ]; then + SKIP_HARDWARE_CHECK=$2 + shift + fi + ;; + -? | -h | --help ) echo " Usage $0 [PARAMETER] [[PARAMETER], ...]" echo " Parameters:" @@ -69,6 +76,10 @@ if [ -z "${LOCAL_SCRIPTS}" ]; then LOCAL_SCRIPTS="false"; fi +if [ -z "${SKIP_HARDWARE_CHECK}" ]; then + SKIP_HARDWARE_CHECK="false"; +fi + cat > /etc/yum.repos.d/onlyoffice.repo < { @@ -104,6 +110,8 @@ export function regDesktop( break; } }; + + console.log("Created window.onSystemMessage", window.onSystemMessage); } export function relogin() { diff --git a/products/ASC.Files/Client/src/components/panels/SelectFileDialog/ModalView.js b/products/ASC.Files/Client/src/components/panels/SelectFileDialog/ModalView.js index f2b1c12031..4b2fc9746e 100644 --- a/products/ASC.Files/Client/src/components/panels/SelectFileDialog/ModalView.js +++ b/products/ASC.Files/Client/src/components/panels/SelectFileDialog/ModalView.js @@ -7,16 +7,12 @@ import FilesListBody from "./FilesListBody"; import Button from "@appserver/components/button"; import Text from "@appserver/components/text"; import { isArrayEqual } from "@appserver/components/utils/array"; -import { FolderType } from "@appserver/common/constants"; import { getFoldersTree } from "@appserver/common/api/files"; -const exceptSortedByTagsFolders = [ - FolderType.Recent, - FolderType.TRASH, - FolderType.Favorites, -]; +import { + exceptSortedByTagsFolders, + exceptPrivacyTrashFolders, +} from "../SelectFolderDialog/ExceptionFoldersConstants"; -const exceptTrashFolder = [FolderType.TRASH]; -const exceptPrivacyTrashFolders = [FolderType.Privacy, FolderType.TRASH]; class SelectFileDialogModalView extends React.Component { constructor(props) { super(props); @@ -59,20 +55,6 @@ class SelectFileDialogModalView extends React.Component { console.error(err); } - this.loadersCompletes(); - break; - case "exceptTrashFolder": - try { - const foldersTree = await getFoldersTree(); - [ - this.folderList, - this.noTreeSwitcher, - ] = SelectFolderDialog.convertFolders(foldersTree, exceptTrashFolder); - this.onSetSelectedFolder(); - } catch (err) { - console.error(err); - } - this.loadersCompletes(); break; case "exceptPrivacyTrashFolders": diff --git a/products/ASC.Files/Client/src/components/panels/SelectFileDialog/index.js b/products/ASC.Files/Client/src/components/panels/SelectFileDialog/index.js index 276ccbeae6..eb2e74de61 100644 --- a/products/ASC.Files/Client/src/components/panels/SelectFileDialog/index.js +++ b/products/ASC.Files/Client/src/components/panels/SelectFileDialog/index.js @@ -349,7 +349,6 @@ SelectFileDialogBody.propTypes = { "common", "third-party", "exceptSortedByTags", - "exceptTrashFolder", "exceptPrivacyTrashFolders", ]), folderId: PropTypes.string, diff --git a/products/ASC.Files/Client/src/components/panels/SelectFolderDialog/ExceptionFoldersConstants.js b/products/ASC.Files/Client/src/components/panels/SelectFolderDialog/ExceptionFoldersConstants.js new file mode 100644 index 0000000000..633221d90c --- /dev/null +++ b/products/ASC.Files/Client/src/components/panels/SelectFolderDialog/ExceptionFoldersConstants.js @@ -0,0 +1,10 @@ +import { FolderType } from "@appserver/common/constants"; + +export const exceptSortedByTagsFolders = [ + FolderType.Recent, + FolderType.TRASH, + FolderType.Favorites, + FolderType.Privacy, +]; + +export const exceptPrivacyTrashFolders = [FolderType.Privacy, FolderType.TRASH]; diff --git a/products/ASC.Files/Client/src/components/panels/SelectFolderDialog/index.js b/products/ASC.Files/Client/src/components/panels/SelectFolderDialog/index.js index 71342307ef..93f19957ae 100644 --- a/products/ASC.Files/Client/src/components/panels/SelectFolderDialog/index.js +++ b/products/ASC.Files/Client/src/components/panels/SelectFolderDialog/index.js @@ -23,6 +23,10 @@ import { FolderType } from "@appserver/common/constants"; import { isArrayEqual } from "@appserver/components/utils/array"; import store from "studio/store"; import toastr from "studio/toastr"; +import { + exceptSortedByTagsFolders, + exceptPrivacyTrashFolders, +} from "./ExceptionFoldersConstants"; const { auth: authStore } = store; @@ -31,14 +35,6 @@ const { desktop } = utils.device; let pathName = ""; let folderList; -const exceptSortedByTagsFolders = [ - FolderType.Recent, - FolderType.TRASH, - FolderType.Favorites, -]; - -const exceptTrashFolder = [FolderType.TRASH]; -const exceptPrivacyTrashFolders = [FolderType.Privacy, FolderType.TRASH]; class SelectFolderModalDialog extends React.Component { constructor(props) { super(props); @@ -116,6 +112,7 @@ class SelectFolderModalDialog extends React.Component { case "exceptSortedByTags": try { const foldersTree = await getFoldersTree(); + [folderList, this.noTreeSwitcher] = SelectFolderDialog.convertFolders( foldersTree, exceptSortedByTagsFolders @@ -126,19 +123,6 @@ class SelectFolderModalDialog extends React.Component { this.loadersCompletes(); } break; - case "exceptTrashFolder": - try { - const foldersTree = await getFoldersTree(); - [folderList, this.noTreeSwitcher] = SelectFolderDialog.convertFolders( - foldersTree, - exceptTrashFolder - ); - this.setBaseSettings(); - } catch (err) { - console.error(err); - this.loadersCompletes(); - } - break; case "exceptPrivacyTrashFolders": try { const foldersTree = await getFoldersTree(); @@ -146,13 +130,13 @@ class SelectFolderModalDialog extends React.Component { foldersTree, exceptPrivacyTrashFolders ); - console.log("folderList", folderList); this.setBaseSettings(); } catch (err) { console.error(err); this.loadersCompletes(); } break; + case "common": try { folderList = await SelectFolderDialog.getCommonFolders(); @@ -550,7 +534,6 @@ SelectFolderModalDialog.propTypes = { "common", "third-party", "exceptSortedByTags", - "exceptTrashFolder", "exceptPrivacyTrashFolders", ]), displayType: PropTypes.oneOf(["aside", "modal"]), diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/DaoFactory.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/DaoFactory.cs index 370d3d2d21..b89b74b5b7 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/DaoFactory.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/DaoFactory.cs @@ -97,7 +97,7 @@ namespace ASC.Files.Core.Data services.TryAdd(); - // AddSharpBoxDaoSelectorService + services.TryAdd(); } } } \ No newline at end of file diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs index a7a9922c35..6574a64270 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FileDao.cs @@ -1202,14 +1202,12 @@ namespace ASC.Files.Core.Data .Select(r => { var item = ServiceProvider.GetService(); - var editHistoryAuthor = ServiceProvider.GetService(); - editHistoryAuthor.Id = r.ModifiedBy; item.ID = r.Id; item.Version = r.Version; item.VersionGroup = r.VersionGroup; item.ModifiedOn = TenantUtil.DateTimeFromUtc(r.ModifiedOn); - item.ModifiedBy = editHistoryAuthor; + item.ModifiedBy = r.ModifiedBy; item.ChangesString = r.Changes; item.Key = documentServiceHelper.GetDocKey(item.ID, item.Version, TenantUtil.DateTimeFromUtc(r.CreateOn)); diff --git a/products/ASC.Files/Core/Core/Entries/EditHistory.cs b/products/ASC.Files/Core/Core/Entries/EditHistory.cs index 01ee58cb18..1798d62fd5 100644 --- a/products/ASC.Files/Core/Core/Entries/EditHistory.cs +++ b/products/ASC.Files/Core/Core/Entries/EditHistory.cs @@ -1,221 +1,225 @@ -/* - * - * (c) Copyright Ascensio System Limited 2010-2018 - * - * This program is freeware. You can redistribute it and/or modify it under the terms of the GNU - * General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html). - * In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that - * Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights. - * - * THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR - * FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html - * - * You can contact Ascensio System SIA by email at sales@onlyoffice.com - * - * The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display - * Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3. - * - * Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains - * relevant author attributions when distributing the software. If the display of the logo in its graphic - * form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE" - * in every copy of the program you distribute. - * Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks. - * -*/ - - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text.Json.Serialization; - -using ASC.Common.Logging; -using ASC.Core; -using ASC.Core.Tenants; -using ASC.Core.Users; -using ASC.Files.Core.Resources; -using ASC.Web.Core.Users; - -using Microsoft.Extensions.Options; - -using Newtonsoft.Json.Linq; - -namespace ASC.Files.Core -{ - [DebuggerDisplay("{ID} v{Version}")] - public class EditHistory - { - public EditHistory( - IOptionsMonitor options, - TenantUtil tenantUtil, - UserManager userManager, - DisplayUserSettingsHelper displayUserSettingsHelper) - { - Logger = options.CurrentValue; - TenantUtil = tenantUtil; - UserManager = userManager; - DisplayUserSettingsHelper = displayUserSettingsHelper; - } - - public int ID { get; set; } - public string Key { get; set; } - public int Version { get; set; } - public int VersionGroup { get; set; } - - [JsonPropertyName("user")] - public EditHistoryAuthor ModifiedBy { get; set; } - - [JsonPropertyName("changeshistory")] - public string ChangesString { get; set; } - - public List Changes - { - get - { - var changes = new List(); - if (string.IsNullOrEmpty(ChangesString)) return changes; - - try - { - var jObject = JObject.Parse(ChangesString); - ServerVersion = jObject.Value("serverVersion"); +/* + * + * (c) Copyright Ascensio System Limited 2010-2018 + * + * This program is freeware. You can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html). + * In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that + * Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights. + * + * THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html + * + * You can contact Ascensio System SIA by email at sales@onlyoffice.com + * + * The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display + * Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3. + * + * Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains + * relevant author attributions when distributing the software. If the display of the logo in its graphic + * form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE" + * in every copy of the program you distribute. + * Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks. + * +*/ + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; + +using ASC.Common; +using ASC.Common.Logging; +using ASC.Core; +using ASC.Core.Tenants; +using ASC.Core.Users; +using ASC.Files.Core.Resources; +using ASC.Web.Core.Users; + +using Microsoft.Extensions.Options; + +namespace ASC.Files.Core +{ + [Transient] + [DebuggerDisplay("{ID} v{Version}")] + public class EditHistory + { + private ILog Logger { get; } + private TenantUtil TenantUtil { get; } + private UserManager UserManager { get; } + private DisplayUserSettingsHelper DisplayUserSettingsHelper { get; } + + public EditHistory( + IOptionsMonitor options, + TenantUtil tenantUtil, + UserManager userManager, + DisplayUserSettingsHelper displayUserSettingsHelper) + { + Logger = options.CurrentValue; + TenantUtil = tenantUtil; + UserManager = userManager; + DisplayUserSettingsHelper = displayUserSettingsHelper; + } + + public int ID { get; set; } + public string Key { get; set; } + public int Version { get; set; } + public int VersionGroup { get; set; } + + public DateTime ModifiedOn { get; set; } + public Guid ModifiedBy { get; set; } + public string ChangesString { get; set; } + + public string ServerVersion { get; set; } + + public List Changes + { + get + { + var changes = new List(); + if (string.IsNullOrEmpty(ChangesString)) return changes; + + try + { + var options = new JsonSerializerOptions + { + AllowTrailingCommas = true, + PropertyNameCaseInsensitive = true + }; + + var jObject = JsonSerializer.Deserialize(ChangesString, options); + ServerVersion = jObject.ServerVersion; if (string.IsNullOrEmpty(ServerVersion)) return changes; - - var jChanges = jObject.Value("changes"); - - changes = jChanges.Children() - .Select(jChange => - { - var jUser = jChange.Value("user"); - return new EditHistoryChanges(TenantUtil) - { - Date = jChange.Value("created"), - Author = new EditHistoryAuthor(UserManager, DisplayUserSettingsHelper) - { - Id = new Guid(jUser.Value("id") ?? Guid.Empty.ToString()), - Name = jUser.Value("name"), - }, - }; - }) - .ToList(); - return changes; - } - catch (Exception ex) - { - Logger.Error("DeSerialize old scheme exception", ex); - } - - return changes; - } - set { throw new NotImplementedException(); } - } - - public DateTime ModifiedOn; - - [JsonPropertyName("created")] - public string ModifiedOnString - { - get { return ModifiedOn.Equals(default) ? null : ModifiedOn.ToString("g"); } - set { throw new NotImplementedException(); } - } - - public ILog Logger { get; } - private TenantUtil TenantUtil { get; } - private UserManager UserManager { get; } - private DisplayUserSettingsHelper DisplayUserSettingsHelper { get; } - - public string ServerVersion; - } - - [DebuggerDisplay("{Id} {Name}")] - public class EditHistoryAuthor - { - public EditHistoryAuthor( - UserManager userManager, - DisplayUserSettingsHelper displayUserSettingsHelper) - { - UserManager = userManager; - DisplayUserSettingsHelper = displayUserSettingsHelper; - } - - public Guid Id { get; set; } - - private string _name; - - public string Name - { - get - { - UserInfo user; - return - Id.Equals(Guid.Empty) - || Id.Equals(ASC.Core.Configuration.Constants.Guest.ID) - || (user = UserManager.GetUsers(Id)).Equals(Constants.LostUser) - ? string.IsNullOrEmpty(_name) - ? FilesCommonResource.Guest - : _name - : user.DisplayUserName(false, DisplayUserSettingsHelper); - } - set { _name = value; } - } - - private UserManager UserManager { get; } - private DisplayUserSettingsHelper DisplayUserSettingsHelper { get; } - } - - [DebuggerDisplay("{Author.Name}")] - public class EditHistoryChanges - { - public EditHistoryChanges(TenantUtil tenantUtil) - { - TenantUtil = tenantUtil; - } - - [JsonPropertyName("user")] - public EditHistoryAuthor Author { get; set; } - - private DateTime _date; - - [JsonPropertyName("created")] - public string Date - { - get { return _date.Equals(default) ? null : _date.ToString("g"); } - set - { - if (DateTime.TryParse(value, out _date)) - { - _date = TenantUtil.DateTimeFromUtc(_date); - } - } - } - - private TenantUtil TenantUtil { get; } - } - - [DebuggerDisplay("{Version}")] - public class EditHistoryData - { - public string ChangesUrl { get; set; } - - public string Key { get; set; } - - public EditHistoryUrl Previous { get; set; } - - public string Token { get; set; } - - public string Url { get; set; } - - public int Version { get; set; } - } - - [DebuggerDisplay("{Key} - {Url}")] - public class EditHistoryUrl - { - public string Key { get; set; } - - public string Url { get; set; } - } + + changes = jObject.Changes.Select(r => + { + var result = new EditHistoryChanges() + { + Author = new EditHistoryAuthor(UserManager, DisplayUserSettingsHelper) + { + Id = new Guid(r.User.Id ?? Guid.Empty.ToString()), + Name = r.User.Name, + } + }; + + + if (DateTime.TryParse(r.Created, out var _date)) + { + _date = TenantUtil.DateTimeFromUtc(_date); + } + result.Date = _date; + + return result; + }) + .ToList(); + + return changes; + } + catch (Exception ex) + { + Logger.Error("DeSerialize old scheme exception", ex); + } + + return changes; + } + set { throw new NotImplementedException(); } + } + } + + class ChangesDataList + { + public string ServerVersion { get; set; } + public ChangesData[] Changes { get; set; } + } + + class ChangesData + { + public string Created { get; set; } + public ChangesUserData User { get; set; } + } + + class ChangesUserData + { + public string Id { get; set; } + public string Name { get; set; } + } + + [Transient] + [DebuggerDisplay("{Id} {Name}")] + public class EditHistoryAuthor + { + public EditHistoryAuthor( + UserManager userManager, + DisplayUserSettingsHelper displayUserSettingsHelper) + { + UserManager = userManager; + DisplayUserSettingsHelper = displayUserSettingsHelper; + } + + public Guid Id { get; set; } + + private string _name; + + public string Name + { + get + { + UserInfo user; + return + Id.Equals(Guid.Empty) + || Id.Equals(ASC.Core.Configuration.Constants.Guest.ID) + || (user = UserManager.GetUsers(Id)).Equals(Constants.LostUser) + ? string.IsNullOrEmpty(_name) + ? FilesCommonResource.Guest + : _name + : user.DisplayUserName(false, DisplayUserSettingsHelper); + } + set { _name = value; } + } + + private UserManager UserManager { get; } + private DisplayUserSettingsHelper DisplayUserSettingsHelper { get; } + } + + [DebuggerDisplay("{Author.Name}")] + public class EditHistoryChanges + { + public EditHistoryAuthor Author { get; set; } + + public DateTime Date { get; set; } + + + } + + [DebuggerDisplay("{Version}")] + public class EditHistoryData + { + public string ChangesUrl { get; set; } + + public string Key { get; set; } + + public EditHistoryUrl Previous { get; set; } + + public string Token { get; set; } + + public string Url { get; set; } + + public int Version { get; set; } + + public string FileType { get; set; } + } + + [DebuggerDisplay("{Key} - {Url}")] + public class EditHistoryUrl + { + public string Key { get; set; } + + public string Url { get; set; } + + public string FileType { get; set; } + } } \ No newline at end of file diff --git a/products/ASC.Files/Core/Core/FileStorageService.cs b/products/ASC.Files/Core/Core/FileStorageService.cs index 052c2062dc..d97c7b02dc 100644 --- a/products/ASC.Files/Core/Core/FileStorageService.cs +++ b/products/ASC.Files/Core/Core/FileStorageService.cs @@ -1071,12 +1071,15 @@ namespace ASC.Web.Files.Services.WCFService Key = DocumentServiceHelper.GetDocKey(file), Url = DocumentServiceConnector.ReplaceCommunityAdress(PathProvider.GetFileStreamUrl(file, doc)), Version = version, + FileType = GetFileExtensionWithoutDot(FileUtility.GetFileExtension(file.Title)) }; if (fileDao.ContainChanges(file.ID, file.Version)) { string previouseKey; string sourceFileUrl; + string previousFileExt; + if (file.Version > 1) { var previousFileStable = fileDao.GetFileStable(file.ID, file.Version - 1); @@ -1085,6 +1088,7 @@ namespace ASC.Web.Files.Services.WCFService sourceFileUrl = PathProvider.GetFileStreamUrl(previousFileStable, doc); previouseKey = DocumentServiceHelper.GetDocKey(previousFileStable); + previousFileExt = FileUtility.GetFileExtension(previousFileStable.Title); } else { @@ -1105,12 +1109,14 @@ namespace ASC.Web.Files.Services.WCFService sourceFileUrl = BaseCommonLinkUtility.GetFullAbsolutePath(sourceFileUrl); previouseKey = DocumentServiceConnector.GenerateRevisionId(Guid.NewGuid().ToString()); + previousFileExt = fileExt; } result.Previous = new EditHistoryUrl { Key = previouseKey, Url = DocumentServiceConnector.ReplaceCommunityAdress(sourceFileUrl), + FileType = GetFileExtensionWithoutDot(previousFileExt) }; result.ChangesUrl = PathProvider.GetFileChangesUrl(file, doc); } @@ -1118,6 +1124,11 @@ namespace ASC.Web.Files.Services.WCFService result.Token = DocumentServiceHelper.GetSignature(result); return result; + + string GetFileExtensionWithoutDot(string ext) + { + return ext.Substring(ext.IndexOf('.') + 1); + } } public List RestoreVersion(T fileId, int version, string url = null, string doc = null) diff --git a/products/ASC.Files/Core/Model/EditHistoryWrapper.cs b/products/ASC.Files/Core/Model/EditHistoryWrapper.cs new file mode 100644 index 0000000000..55a946d32d --- /dev/null +++ b/products/ASC.Files/Core/Model/EditHistoryWrapper.cs @@ -0,0 +1,49 @@ + +using System.Collections.Generic; +using System.Linq; + +using ASC.Api.Core; +using ASC.Core; +using ASC.Web.Core.Users; + +namespace ASC.Files.Core.Model +{ + public class EditHistoryWrapper + { + public int ID { get; set; } + public string Key { get; set; } + public int Version { get; set; } + public int VersionGroup { get; set; } + public EditHistoryAuthor User { get; set; } + public ApiDateTime Created { get; set; } + public string ChangesHistory { get; set; } + public List Changes { get; set; } + public string ServerVersion { get; set; } + + public EditHistoryWrapper(EditHistory editHistory, ApiDateTimeHelper apiDateTimeHelper, UserManager userManager, DisplayUserSettingsHelper displayUserSettingsHelper) + { + ID = editHistory.ID; + Key = editHistory.Key; + Version = editHistory.Version; + VersionGroup = editHistory.VersionGroup; + Changes = editHistory.Changes.Select(r => new EditHistoryChangesWrapper(r, apiDateTimeHelper)).ToList(); + ChangesHistory = editHistory.ChangesString; + Created = apiDateTimeHelper.Get(editHistory.ModifiedOn); + User = new EditHistoryAuthor(userManager, displayUserSettingsHelper) { Id = editHistory.ModifiedBy }; + ServerVersion = editHistory.ServerVersion; + } + } + + public class EditHistoryChangesWrapper + { + public EditHistoryAuthor User { get; set; } + + public ApiDateTime Created { get; set; } + + public EditHistoryChangesWrapper(EditHistoryChanges historyChanges, ApiDateTimeHelper apiDateTimeHelper) + { + User = historyChanges.Author; + Created = apiDateTimeHelper.Get(historyChanges.Date); + } + } +} diff --git a/products/ASC.Files/Server/Controllers/FilesController.cs b/products/ASC.Files/Server/Controllers/FilesController.cs index 115d586f73..fd6248b416 100644 --- a/products/ASC.Files/Server/Controllers/FilesController.cs +++ b/products/ASC.Files/Server/Controllers/FilesController.cs @@ -1517,6 +1517,48 @@ namespace ASC.Api.Documents return FilesControllerHelperInt.LockFile(fileId, model.LockFile); } + [AllowAnonymous] + [Read("file/{fileId}/edit/history")] + public List GetEditHistory(string fileId, string doc = null) + { + return FilesControllerHelperString.GetEditHistory(fileId, doc); + } + + [AllowAnonymous] + [Read("file/{fileId:int}/edit/history")] + public List GetEditHistory(int fileId, string doc = null) + { + return FilesControllerHelperInt.GetEditHistory(fileId, doc); + } + + [AllowAnonymous] + [Read("file/{fileId}/edit/diff")] + public EditHistoryData GetEditDiffUrl(string fileId, int version = 0, string doc = null) + { + return FilesControllerHelperString.GetEditDiffUrl(fileId, version, doc); + } + + [AllowAnonymous] + [Read("file/{fileId:int}/edit/diff")] + public EditHistoryData GetEditDiffUrl(int fileId, int version = 0, string doc = null) + { + return FilesControllerHelperInt.GetEditDiffUrl(fileId, version, doc); + } + + [AllowAnonymous] + [Read("file/{fileId}/restoreversion")] + public List RestoreVersion(string fileId, int version = 0, string url = null, string doc = null) + { + return FilesControllerHelperString.RestoreVersion(fileId, version, url, doc); + } + + [AllowAnonymous] + [Read("file/{fileId:int}/restoreversion")] + public List RestoreVersion(int fileId, int version = 0, string url = null, string doc = null) + { + return FilesControllerHelperInt.RestoreVersion(fileId, version, url, doc); + } + [Update("file/{fileId}/comment")] public object UpdateCommentFromBody(string fileId, [FromBody] UpdateCommentModel model) { diff --git a/products/ASC.Files/Server/Helpers/FilesControllerHelper.cs b/products/ASC.Files/Server/Helpers/FilesControllerHelper.cs index 2cc6e69a9c..1b32120e35 100644 --- a/products/ASC.Files/Server/Helpers/FilesControllerHelper.cs +++ b/products/ASC.Files/Server/Helpers/FilesControllerHelper.cs @@ -21,6 +21,7 @@ using ASC.Files.Core; using ASC.Files.Core.Model; using ASC.Files.Model; using ASC.Web.Core.Files; +using ASC.Web.Core.Users; using ASC.Web.Files.Classes; using ASC.Web.Files.Core.Entries; using ASC.Web.Files.Services.DocumentService; @@ -66,6 +67,9 @@ namespace ASC.Files.Helpers private EncryptionKeyPairHelper EncryptionKeyPairHelper { get; } private IHttpContextAccessor HttpContextAccessor { get; } private FileConverter FileConverter { get; } + private ApiDateTimeHelper ApiDateTimeHelper { get; } + private UserManager UserManager { get; } + private DisplayUserSettingsHelper DisplayUserSettingsHelper { get; } private ILog Logger { get; set; } /// @@ -94,7 +98,10 @@ namespace ASC.Files.Helpers SettingsManager settingsManager, EncryptionKeyPairHelper encryptionKeyPairHelper, IHttpContextAccessor httpContextAccessor, - FileConverter fileConverter) + FileConverter fileConverter, + ApiDateTimeHelper apiDateTimeHelper, + UserManager userManager, + DisplayUserSettingsHelper displayUserSettingsHelper) { ApiContext = context; FileStorageService = fileStorageService; @@ -115,6 +122,9 @@ namespace ASC.Files.Helpers DocumentServiceTracker = documentServiceTracker; SettingsManager = settingsManager; EncryptionKeyPairHelper = encryptionKeyPairHelper; + ApiDateTimeHelper = apiDateTimeHelper; + UserManager = userManager; + DisplayUserSettingsHelper = displayUserSettingsHelper; HttpContextAccessor = httpContextAccessor; FileConverter = fileConverter; Logger = optionMonitor.Get("ASC.Files"); @@ -571,6 +581,23 @@ namespace ASC.Files.Helpers return FileStorageService.GetPresignedUri(fileId); } + public List GetEditHistory(T fileId, string doc = null) + { + var result = FileStorageService.GetEditHistory(fileId, doc); + return result.Select(r => new EditHistoryWrapper(r, ApiDateTimeHelper, UserManager, DisplayUserSettingsHelper)).ToList(); + } + + public EditHistoryData GetEditDiffUrl(T fileId, int version = 0, string doc = null) + { + return FileStorageService.GetEditDiffUrl(fileId, version, doc); + } + + public List RestoreVersion(T fileId, int version = 0, string url = null, string doc = null) + { + var result = FileStorageService.RestoreVersion(fileId, version, url, doc); + return result.Select(r => new EditHistoryWrapper(r, ApiDateTimeHelper, UserManager, DisplayUserSettingsHelper)).ToList(); + } + public string UpdateComment(T fileId, int version, string comment) { return FileStorageService.UpdateComment(fileId, version, comment); diff --git a/web/ASC.Web.Api/Controllers/SettingsController.cs b/web/ASC.Web.Api/Controllers/SettingsController.cs index 1790430496..9eea66734c 100644 --- a/web/ASC.Web.Api/Controllers/SettingsController.cs +++ b/web/ASC.Web.Api/Controllers/SettingsController.cs @@ -166,7 +166,9 @@ namespace ASC.Api.Settings private ILog Log { get; set; } private TelegramHelper TelegramHelper { get; } private PaymentManager PaymentManager { get; } - public Constants Constants { get; } + private Constants Constants { get; } + private InstanceCrypto InstanceCrypto { get; } + private Signature Signature { get; } public SettingsController( IOptionsMonitor option, @@ -228,7 +230,9 @@ namespace ASC.Api.Settings EncryptionWorker encryptionWorker, PasswordHasher passwordHasher, PaymentManager paymentManager, - Constants constants) + Constants constants, + InstanceCrypto instanceCrypto, + Signature signature) { Log = option.Get("ASC.Api"); WebHostEnvironment = webHostEnvironment; @@ -290,6 +294,8 @@ namespace ASC.Api.Settings TelegramHelper = telegramHelper; PaymentManager = paymentManager; Constants = constants; + InstanceCrypto = instanceCrypto; + Signature = signature; } [Read("", Check = false)] @@ -1598,7 +1604,7 @@ namespace ASC.Api.Settings if (currentUser.IsVisitor(UserManager) || currentUser.IsOutsider(UserManager)) throw new NotSupportedException("Not available."); - return SettingsManager.LoadForCurrentUser().CodesSetting.Select(r => new { r.IsUsed, r.Code }).ToList(); + return SettingsManager.LoadForCurrentUser().CodesSetting.Select(r => new { r.IsUsed, Code = r.GetEncryptedCode(InstanceCrypto, Signature) }).ToList(); } [Update("tfaappnewcodes")] @@ -1612,7 +1618,7 @@ namespace ASC.Api.Settings if (currentUser.IsVisitor(UserManager) || currentUser.IsOutsider(UserManager)) throw new NotSupportedException("Not available."); - var codes = TfaManager.GenerateBackupCodes().Select(r => new { r.IsUsed, r.Code }).ToList(); + var codes = TfaManager.GenerateBackupCodes().Select(r => new { r.IsUsed, Code = r.GetEncryptedCode(InstanceCrypto, Signature) }).ToList(); MessageService.Send(MessageAction.UserConnectedTfaApp, MessageTarget.Create(currentUser.ID), currentUser.DisplayUserName(false, DisplayUserSettingsHelper)); return codes; } diff --git a/web/ASC.Web.Core/Tfa/TfaAppUserSettings.cs b/web/ASC.Web.Core/Tfa/TfaAppUserSettings.cs index 1517b28bfc..684d378ae5 100644 --- a/web/ASC.Web.Core/Tfa/TfaAppUserSettings.cs +++ b/web/ASC.Web.Core/Tfa/TfaAppUserSettings.cs @@ -29,7 +29,9 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using ASC.Common.Utils; using ASC.Core.Common.Settings; +using ASC.Security.Cryptography; namespace ASC.Web.Studio.Core.TFA { @@ -65,7 +67,7 @@ namespace ASC.Web.Studio.Core.TFA var from = new DateTime(2018, 07, 07, 0, 0, 0, DateTimeKind.Utc); settings.SaltSetting = salt = (long)(DateTime.UtcNow - from).TotalMilliseconds; - settingsManager.SaveForUser(settings, userId); + settingsManager.SaveForUser(settings, userId); } return salt; } @@ -75,10 +77,10 @@ namespace ASC.Web.Studio.Core.TFA return settingsManager.LoadForUser(userId).CodesSetting; } - public static void DisableCodeForUser(SettingsManager settingsManager, Guid userId, string code) + public static void DisableCodeForUser(SettingsManager settingsManager, InstanceCrypto instanceCrypto, Signature signature, Guid userId, string code) { var settings = settingsManager.LoadForUser(userId); - var query = settings.CodesSetting.Where(x => x.Code == code).ToList(); + var query = settings.CodesSetting.Where(x => x.GetEncryptedCode(instanceCrypto, signature) == code).ToList(); if (query.Any()) query.First().IsUsed = true; diff --git a/web/ASC.Web.Core/Tfa/TfaManager.cs b/web/ASC.Web.Core/Tfa/TfaManager.cs index 34fbe03592..5156cf9467 100644 --- a/web/ASC.Web.Core/Tfa/TfaManager.cs +++ b/web/ASC.Web.Core/Tfa/TfaManager.cs @@ -51,35 +51,26 @@ namespace ASC.Web.Studio.Core.TFA [Serializable] public class BackupCode { - private string code; - private InstanceCrypto InstanceCrypto { get; } - private Signature Signature { get; } - - public string Code - { - get - { - try - { - return InstanceCrypto.Decrypt(code); - } - catch - { - //support old scheme stored in the DB - return Signature.Read(code); - } - } - set { code = InstanceCrypto.Encrypt(value); } - } - public bool IsUsed { get; set; } - public BackupCode(InstanceCrypto instanceCrypto, Signature signature, string code) + public string Code { get; set; } + + public string GetEncryptedCode(InstanceCrypto InstanceCrypto, Signature Signature) { - InstanceCrypto = instanceCrypto; - Signature = signature; - Code = code; - IsUsed = false; + try + { + return InstanceCrypto.Decrypt(Code); + } + catch + { + //support old scheme stored in the DB + return Signature.Read(Code); + } + } + + public void SetEncryptedCode(InstanceCrypto InstanceCrypto, string code) + { + Code = InstanceCrypto.Encrypt(code); } } @@ -145,9 +136,9 @@ namespace ASC.Web.Studio.Core.TFA if (!Tfa.ValidateTwoFactorPIN(GenerateAccessToken(user), code)) { - if (checkBackup && TfaAppUserSettings.BackupCodesForUser(SettingsManager, user.ID).Any(x => x.Code == code && !x.IsUsed)) + if (checkBackup && TfaAppUserSettings.BackupCodesForUser(SettingsManager, user.ID).Any(x => x.GetEncryptedCode(InstanceCrypto, Signature) == code && !x.IsUsed)) { - TfaAppUserSettings.DisableCodeForUser(SettingsManager, user.ID, code); + TfaAppUserSettings.DisableCodeForUser(SettingsManager, InstanceCrypto, Signature, user.ID, code); } else { @@ -195,7 +186,9 @@ namespace ASC.Web.Studio.Core.TFA result.Append(alphabet[b % (alphabet.Length)]); } - list.Add(new BackupCode(InstanceCrypto, Signature, result.ToString())); + var code = new BackupCode(); + code.SetEncryptedCode(InstanceCrypto, result.ToString()); + list.Add(code); } } var settings = SettingsManager.LoadForCurrentUser(); diff --git a/web/ASC.Web.Editor/src/Editor.jsx b/web/ASC.Web.Editor/src/Editor.jsx index dcaa4f3a76..5f63992f73 100644 --- a/web/ASC.Web.Editor/src/Editor.jsx +++ b/web/ASC.Web.Editor/src/Editor.jsx @@ -26,6 +26,9 @@ import { getPresignedUri, convertFile, checkFillFormDraft, + getEditHistory, + getEditDiff, + restoreDocumentsVersion, } from "@appserver/common/api/files"; import FilesFilter from "@appserver/common/api/files/filter"; @@ -33,12 +36,12 @@ import throttle from "lodash/throttle"; import { isIOS, deviceType } from "react-device-detect"; import { homepage } from "../package.json"; -import { AppServerConfig, FolderType } from "@appserver/common/constants"; +import { AppServerConfig } from "@appserver/common/constants"; import SharingDialog from "files/SharingDialog"; import { getDefaultFileName, SaveAs, canConvert } from "files/utils"; import SelectFileDialog from "files/SelectFileDialog"; import SelectFolderDialog from "files/SelectFolderDialog"; -import { StyledSelectFolder, StyledSelectFile } from "./StyledEditor"; +import { StyledSelectFolder } from "./StyledEditor"; import i18n from "./i18n"; import Text from "@appserver/components/text"; import TextInput from "@appserver/components/text-input"; @@ -67,7 +70,9 @@ let isSharingAccess; let user = null; let personal; let url = window.location.href; +let config; const filesUrl = url.substring(0, url.indexOf("/doceditor")); +const doc = url.indexOf("doc=") !== -1 ? url.split("doc=")[1] : null; toast.configure(); @@ -142,7 +147,7 @@ const Editor = () => { docEditor.setFavorite(favorite); }; - const initDesktop = (config) => { + const initDesktop = () => { const isEncryption = config?.editorConfig["encryptionKeys"] !== undefined; regDesktop( @@ -237,7 +242,7 @@ const Editor = () => { setIsAuthenticated(successAuth); } - const config = await openEdit(fileId, version, doc, view); + config = await openEdit(fileId, version, doc, view); if ( !view && @@ -269,7 +274,7 @@ const Editor = () => { setIsLoading(false); - loadScript(docApiUrl, "scripDocServiceAddress", () => onLoad(config)); + loadScript(docApiUrl, "scripDocServiceAddress", () => onLoad()); } catch (error) { console.log(error); toastr.error( @@ -332,7 +337,7 @@ const Editor = () => { document.title = title; }; - const onLoad = (config) => { + const onLoad = () => { try { if (!window.DocsAPI) throw new Error("DocsAPI is not defined"); @@ -408,7 +413,8 @@ const Editor = () => { onRequestSaveAs, onRequestInsertImage, onRequestMailMergeRecipients, - onRequestCompareFile; + onRequestCompareFile, + onRequestRestore; if (isSharingAccess) { onRequestSharingSettings = onSDKRequestSharingSettings; @@ -425,6 +431,9 @@ const Editor = () => { onRequestCompareFile = onSDKRequestCompareFile; } + if (!!config.document.permissions.changeHistory) { + onRequestRestore = onSDKRequestRestore; + } const events = { events: { onAppReady: onSDKAppReady, @@ -442,6 +451,10 @@ const Editor = () => { onRequestMailMergeRecipients, onRequestCompareFile, onRequestEditRights: onSDKRequestEditRights, + onRequestHistory: onSDKRequestHistory, + onRequestHistoryClose: onSDKRequestHistoryClose, + onRequestHistoryData: onSDKRequestHistoryData, + onRequestRestore, }, }; @@ -454,6 +467,114 @@ const Editor = () => { } }; + const onSDKRequestHistoryData = async (event) => { + const version = event.data; + + try { + const versionDifference = await getEditDiff(fileId, version, doc); + const changesUrl = versionDifference.changesUrl; + const previous = versionDifference.previous; + const token = versionDifference.token; + + docEditor.setHistoryData({ + ...(changesUrl && { changesUrl }), + key: versionDifference.key, + fileType: versionDifference.fileType, + ...(previous && { + previous: { + fileType: previous.fileType, + key: previous.key, + url: previous.url, + }, + }), + ...(token && { token }), + url: versionDifference.url, + version, + }); + } catch (e) { + docEditor.setHistoryData({ + error: `${e}`, //TODO: maybe need to display something else. + version, + }); + } + }; + + const onSDKRequestHistoryClose = () => { + document.location.reload(); + }; + + const getDocumentHistory = (fileHistory, historyLength) => { + let result = []; + + for (let i = 0; i < historyLength; i++) { + const changes = fileHistory[i].changes; + const serverVersion = fileHistory[i].serverVersion; + const version = fileHistory[i].version; + const versionGroup = fileHistory[i].versionGroup; + + let obj = { + ...(changes.length !== 0 && { changes }), + created: `${new Date(fileHistory[i].created).toLocaleString( + config.editorConfig.lang + )}`, + ...(serverVersion && { serverVersion }), + key: fileHistory[i].key, + user: { + id: fileHistory[i].user.id, + name: fileHistory[i].user.name, + }, + version, + versionGroup, + }; + + result.push(obj); + } + return result; + }; + const getCurrentDocumentVersion = (fileHistory, historyLength) => { + return url.indexOf("&version=") !== -1 + ? +url.split("&version=")[1] + : fileHistory[historyLength - 1].version; + }; + const onSDKRequestHistory = async () => { + try { + const fileHistory = await getEditHistory(fileId, doc); + const historyLength = fileHistory.length; + + docEditor.refreshHistory({ + currentVersion: getCurrentDocumentVersion(fileHistory, historyLength), + history: getDocumentHistory(fileHistory, historyLength), + }); + } catch (e) { + docEditor.refreshHistory({ + error: `${e}`, //TODO: maybe need to display something else. + }); + } + }; + + const onSDKRequestRestore = async (event) => { + const restoreVersion = event.data.version; + try { + const updateVersions = await restoreDocumentsVersion( + fileId, + restoreVersion, + doc + ); + const historyLength = updateVersions.length; + docEditor.refreshHistory({ + currentVersion: getCurrentDocumentVersion( + updateVersions, + historyLength + ), + history: getDocumentHistory(updateVersions, historyLength), + }); + } catch (e) { + docEditor.refreshHistory({ + error: `${e}`, //TODO: maybe need to display something else. + }); + } + }; + const onSDKAppReady = () => { console.log("ONLYOFFICE Document Editor is ready"); @@ -738,7 +859,7 @@ const Editor = () => { onSelectFile={onSelectFile} isPanelVisible={isFileDialogVisible} onClose={onCloseFileDialog} - foldersType="exceptTrashFolder" + foldersType="exceptPrivacyTrashFolders" {...fileTypeDetection()} titleFilesList={selectFilesListTitle()} headerName={i18n.t("SelectFileTitle")}