Merge branch 'develop' into feature/rtl-interface-direction
This commit is contained in:
commit
80bf83f2e2
@ -29,7 +29,9 @@ using HttpContext = Microsoft.AspNetCore.Http.HttpContext;
|
||||
namespace System.Web;
|
||||
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
{
|
||||
public static readonly string RequestTokenHeader = "Request-Token";
|
||||
|
||||
public static Uri Url(this HttpRequest request)
|
||||
{
|
||||
return request != null ? new Uri(request.GetDisplayUrl()) : null;
|
||||
|
@ -203,9 +203,9 @@ public enum MessageAction
|
||||
RoomUpdateAccessForUser = 5075,
|
||||
RoomRemoveUser = 5084,
|
||||
RoomCreateUser = 5085,// last
|
||||
RoomLinkUpdated = 5082,
|
||||
RoomLinkCreated = 5086,
|
||||
RoomLinkDeleted = 5087,
|
||||
RoomInvitationLinkUpdated = 5082,
|
||||
RoomInvitationLinkCreated = 5086,
|
||||
RoomInvitationLinkDeleted = 5087,
|
||||
|
||||
TagCreated = 5076,
|
||||
TagsDeleted = 5077,
|
||||
@ -214,6 +214,10 @@ public enum MessageAction
|
||||
|
||||
RoomLogoCreated = 5080,
|
||||
RoomLogoDeleted = 5081,
|
||||
|
||||
ExternalLinkCreated = 5088,
|
||||
ExternalLinkUpdated = 5089,
|
||||
ExternalLinkDeleted = 5090,
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -33,4 +33,5 @@ public static class Constants
|
||||
public const string QueryAuth = "auth";
|
||||
public const string QueryExpire = "expire";
|
||||
public const string QueryHeader = "headers";
|
||||
public const string SecureKeyHeader = "secure-key";
|
||||
}
|
||||
|
@ -111,6 +111,11 @@ public class S3Storage : BaseStorage
|
||||
|
||||
foreach (var h in headers)
|
||||
{
|
||||
if (h.StartsWith(Constants.SecureKeyHeader))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (h.StartsWith("Content-Disposition"))
|
||||
{
|
||||
headersOverrides.ContentDisposition = (h.Substring("Content-Disposition".Length + 1));
|
||||
|
@ -41,4 +41,39 @@ public static class SecureHelper
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GenerateSecureKeyHeader(string path, EmailValidationKeyProvider keyProvider)
|
||||
{
|
||||
var ticks = DateTime.UtcNow.Ticks;
|
||||
var data = path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar) + '.' + ticks;
|
||||
var key = keyProvider.GetEmailKey(data);
|
||||
|
||||
return Constants.SecureKeyHeader + ':' + ticks + '-' + key;
|
||||
}
|
||||
|
||||
public static async Task<bool> CheckSecureKeyHeader(string queryHeaders, string path, EmailValidationKeyProvider keyProvider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(queryHeaders))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var headers = queryHeaders.Length > 0 ? queryHeaders.Split('&').Select(HttpUtility.UrlDecode) : Array.Empty<string>();
|
||||
|
||||
var headerKey = headers.FirstOrDefault(h => h.StartsWith(Constants.SecureKeyHeader))?.
|
||||
Replace(Constants.SecureKeyHeader + ':', string.Empty);
|
||||
|
||||
if (string.IsNullOrEmpty(headerKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var separatorPosition = headerKey.IndexOf('-');
|
||||
var ticks = headerKey[..separatorPosition];
|
||||
var key = headerKey[(separatorPosition + 1)..];
|
||||
|
||||
var result = await keyProvider.ValidateEmailKeyAsync(path + '.' + ticks, key);
|
||||
|
||||
return result == EmailValidationKeyProvider.ValidationResult.Ok;
|
||||
}
|
||||
}
|
||||
|
@ -51,12 +51,12 @@ public class StorageHandler
|
||||
public async ValueTask InvokeAsync(HttpContext context, TenantManager tenantManager, SecurityContext securityContext, StorageFactory storageFactory, EmailValidationKeyProvider emailValidationKeyProvider)
|
||||
{
|
||||
var storage = await storageFactory.GetStorageAsync((await tenantManager.GetCurrentTenantAsync()).Id, _module);
|
||||
var path = CrossPlatform.PathCombine(_path, GetRouteValue("pathInfo", context).Replace('/', Path.DirectorySeparatorChar));
|
||||
var path = CrossPlatform.PathCombine(_path, GetRouteValue("pathInfo", context).Replace('/', Path.DirectorySeparatorChar));
|
||||
var header = context.Request.Query[Constants.QueryHeader].FirstOrDefault() ?? "";
|
||||
var auth = context.Request.Query[Constants.QueryAuth].FirstOrDefault() ?? "";
|
||||
var storageExpire = storage.GetExpire(_domain);
|
||||
|
||||
if (_checkAuth && !securityContext.IsAuthenticated && String.IsNullOrEmpty(auth))
|
||||
if (_checkAuth && !securityContext.IsAuthenticated && !await SecureHelper.CheckSecureKeyHeader(header, path, emailValidationKeyProvider))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
@ -74,31 +74,31 @@ public class StorageHandler
|
||||
if (validateResult != EmailValidationKeyProvider.ValidationResult.Ok)
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!await storage.IsFileAsync(_domain, path))
|
||||
{
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
var headers = header.Length > 0 ? header.Split('&').Select(HttpUtility.UrlDecode) : Array.Empty<string>();
|
||||
var headers = header.Length > 0 ? header.Split('&').Select(HttpUtility.UrlDecode) : Array.Empty<string>();
|
||||
|
||||
const int bigSize = 5 * 1024 * 1024;
|
||||
var fileSize = await storage.GetFileSizeAsync(_domain, path);
|
||||
|
||||
if (storage.IsSupportInternalUri && bigSize < fileSize)
|
||||
{
|
||||
var uri = await storage.GetInternalUriAsync(_domain, path, TimeSpan.FromMinutes(15), headers);
|
||||
var uri = await storage.GetInternalUriAsync(_domain, path, TimeSpan.FromMinutes(15), headers);
|
||||
|
||||
//TODO
|
||||
//context.Response.Cache.SetAllowResponseInBrowserHistory(false);
|
||||
//context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
|
||||
|
||||
context.Response.Redirect(uri.ToString());
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
//if (!String.IsNullOrEmpty(context.Request.Query["_"]))
|
||||
@ -119,7 +119,7 @@ public class StorageHandler
|
||||
|
||||
string encoding = null;
|
||||
|
||||
if (storage is DiscDataStore && await storage.IsFileAsync(_domain, path + ".gz"))
|
||||
if (storage is DiscDataStore && await storage.IsFileAsync(_domain, path + ".gz"))
|
||||
{
|
||||
path += ".gz";
|
||||
encoding = "gzip";
|
||||
@ -162,7 +162,7 @@ public class StorageHandler
|
||||
{
|
||||
var responseBufferingFeature = context.Features.Get<IHttpResponseBodyFeature>();
|
||||
responseBufferingFeature?.DisableBuffering();
|
||||
|
||||
|
||||
await stream.CopyToAsync(context.Response.Body);
|
||||
}
|
||||
|
||||
@ -233,4 +233,4 @@ public static class StorageHandlerExtensions
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.user) {
|
||||
if (!session.user && !session.anonymous) {
|
||||
logger.error("invalid session: unknown user");
|
||||
return;
|
||||
}
|
||||
@ -45,14 +45,18 @@
|
||||
return `${tenantId}-${roomPart}`;
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`connect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}'`
|
||||
);
|
||||
const connectMessage = !session.anonymous ?
|
||||
`connect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}'` :
|
||||
`connect anonymous user by share key on tenant='${tenantId}' socketId='${socket.id}'`;
|
||||
|
||||
logger.info(connectMessage);
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
logger.info(
|
||||
`disconnect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}' due to ${reason}`
|
||||
);
|
||||
const disconnectMessage = !session.anonymous ?
|
||||
`disconnect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}' due to ${reason}` :
|
||||
`disconnect anonymous user by share key on tenant='${tenantId}' socketId='${socket.id}' due to ${reason}`;
|
||||
|
||||
logger.info(disconnectMessage)
|
||||
});
|
||||
|
||||
socket.on("subscribe", ({ roomParts, individual }) => {
|
||||
@ -80,7 +84,7 @@
|
||||
|
||||
changeFunc(roomParts);
|
||||
|
||||
if (individual) {
|
||||
if (individual && !session.anonymous) {
|
||||
if (Array.isArray(roomParts)) {
|
||||
changeFunc(roomParts.map((p) => `${p}-${userId}`));
|
||||
} else {
|
||||
|
@ -9,9 +9,10 @@ module.exports = (socket, next) => {
|
||||
|
||||
const cookie = req?.cookies?.authorization || req?.cookies?.asc_auth_key;
|
||||
const token = req?.headers?.authorization;
|
||||
const share = socket.handshake.query?.share;
|
||||
|
||||
if (!cookie && !token) {
|
||||
const err = new Error("Authentication error (not token or cookie)");
|
||||
if (!cookie && !token && !share) {
|
||||
const err = new Error("Authentication error (not token or cookie or share key)");
|
||||
logger.error(err);
|
||||
socket.disconnect("unauthorized");
|
||||
next(err);
|
||||
@ -31,15 +32,13 @@ module.exports = (socket, next) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let headers;
|
||||
if (cookie)
|
||||
headers = {
|
||||
Authorization: cookie,
|
||||
};
|
||||
|
||||
const basePath = portalManager(req)?.replace(/\/$/g, "");
|
||||
let headers = {};
|
||||
|
||||
logger.info(`API basePath='${basePath}' Authorization='${cookie}'`);
|
||||
if (cookie) {
|
||||
headers.Authorization = cookie;
|
||||
|
||||
logger.info(`API basePath='${basePath}' Authorization='${cookie}'`);
|
||||
|
||||
const getUser = () => {
|
||||
return request({
|
||||
@ -59,17 +58,53 @@ module.exports = (socket, next) => {
|
||||
});
|
||||
};
|
||||
|
||||
return Promise.all([getUser(), getPortal()])
|
||||
.then(([user, portal]) => {
|
||||
logger.info("Get account info", { user, portal });
|
||||
session.user = user;
|
||||
session.portal = portal;
|
||||
session.save();
|
||||
next();
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error("Error of getting account info", err);
|
||||
return Promise.all([getUser(), getPortal()])
|
||||
.then(([user, portal]) => {
|
||||
logger.info("Get account info", { user, portal });
|
||||
session.user = user;
|
||||
session.portal = portal;
|
||||
session.save();
|
||||
next();
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error("Error of getting account info", err);
|
||||
socket.disconnect("Unauthorized");
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (share) {
|
||||
if (req?.cookies) {
|
||||
const pairs = Object.entries(req.cookies).map(([key, value]) => `${key}=${value}`);
|
||||
|
||||
if (pairs.length > 0) {
|
||||
let cookie = pairs.join(';');
|
||||
cookie += ';';
|
||||
headers.Cookie = cookie;
|
||||
}
|
||||
}
|
||||
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/files/share/${share}`,
|
||||
headers,
|
||||
basePath,
|
||||
}).then(validation => {
|
||||
if (validation.status !== 0) {
|
||||
const err = new Error("Invalid share key");
|
||||
logger.error("Share key validation failure:", err);
|
||||
next(err);
|
||||
} else {
|
||||
logger.info(`Share key validation successful: key=${share}`)
|
||||
session.anonymous = true;
|
||||
session.portal = { tenantId: validation.tenantId }
|
||||
session.save();
|
||||
next();
|
||||
}
|
||||
}).catch(err => {
|
||||
logger.error(err);
|
||||
socket.disconnect("Unauthorized");
|
||||
next(err);
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
};
|
@ -66,7 +66,8 @@ const options = {
|
||||
const token =
|
||||
req?.headers?.authorization ||
|
||||
req?.cookies?.authorization ||
|
||||
req?.cookies?.asc_auth_key;
|
||||
req?.cookies?.asc_auth_key ||
|
||||
req?._query?.share;
|
||||
|
||||
if (!token) {
|
||||
winston.info(`not allowed request: empty token`);
|
||||
|
@ -154,9 +154,9 @@ internal class RoomsActionMapper : IModuleActionMapper
|
||||
MessageAction.RoomCreateUser,
|
||||
MessageAction.RoomUpdateAccessForUser,
|
||||
MessageAction.RoomRemoveUser,
|
||||
MessageAction.RoomLinkCreated,
|
||||
MessageAction.RoomLinkUpdated,
|
||||
MessageAction.RoomLinkDeleted
|
||||
MessageAction.RoomInvitationLinkCreated,
|
||||
MessageAction.RoomInvitationLinkUpdated,
|
||||
MessageAction.RoomInvitationLinkDeleted
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -32,17 +32,8 @@ public enum RoomType
|
||||
EditingRoom = 2,
|
||||
ReviewRoom = 3,
|
||||
ReadOnlyRoom = 4,
|
||||
CustomRoom = 5
|
||||
}
|
||||
|
||||
public enum RoomFilterType
|
||||
{
|
||||
FillingFormsRoomOnly = 1,
|
||||
EditingRoomOnly = 2,
|
||||
ReviewRoomOnly = 3,
|
||||
ReadOnlyRoomOnly = 4,
|
||||
CustomRoomOnly = 5,
|
||||
FoldersOnly = 6,
|
||||
CustomRoom = 5,
|
||||
PublicRoom = 6,
|
||||
}
|
||||
|
||||
public class CreateRoomRequestDto
|
||||
|
@ -1,34 +1,32 @@
|
||||
// (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 InvintationLinkRequestDto
|
||||
public class ExternalShareRequestDto
|
||||
{
|
||||
public Guid LinkId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public FileShare Access { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
// (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 LinkRequestDto
|
||||
{
|
||||
public Guid LinkId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public FileShare Access { get; set; }
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
public LinkType LinkType { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
public bool DenyDownload { get; set; }
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
// (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 Profile = AutoMapper.Profile;
|
||||
using Status = ASC.Files.Core.Security.Status;
|
||||
|
||||
namespace ASC.Files.Core.ApiModels.ResponseDto;
|
||||
|
||||
public class ExternalShareDto : IMapFrom<ValidationInfo>
|
||||
{
|
||||
public Status Status { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public RoomType? RoomType { get; set; }
|
||||
public int TenantId { get; set; }
|
||||
public Logo Logo { get; set; }
|
||||
|
||||
public void Mapping(Profile profile)
|
||||
{
|
||||
profile.CreateMap<ValidationInfo, ExternalShareDto>()
|
||||
.ForMember(dest => dest.RoomType, opt =>
|
||||
opt.MapFrom(source => DocSpaceHelper.GetRoomType(source.FolderType)));
|
||||
}
|
||||
}
|
@ -54,6 +54,18 @@ public class FileShareLink
|
||||
public string Title { get; set; }
|
||||
public string ShareLink { get; set; }
|
||||
public ApiDateTime ExpirationDate { get; set; }
|
||||
public LinkType LinkType { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool? Disabled { get; set; }
|
||||
public bool? DenyDownload { get; set; }
|
||||
public bool IsTemplate { get; set; }
|
||||
public bool? IsExpired { get; set; }
|
||||
}
|
||||
|
||||
public enum LinkType
|
||||
{
|
||||
Invitation,
|
||||
External
|
||||
}
|
||||
|
||||
[Scope]
|
||||
@ -87,13 +99,25 @@ public class FileShareDtoHelper
|
||||
if (!string.IsNullOrEmpty(aceWrapper.Link))
|
||||
{
|
||||
var date = aceWrapper.FileShareOptions?.ExpirationDate;
|
||||
var expired = aceWrapper.FileShareOptions?.IsExpired;
|
||||
|
||||
result.SharedTo = new FileShareLink
|
||||
{
|
||||
Id = aceWrapper.Id,
|
||||
Title = aceWrapper.FileShareOptions?.Title,
|
||||
ShareLink = aceWrapper.Link,
|
||||
ExpirationDate = date.HasValue && date.Value != default ? _apiDateTimeHelper.Get(date) : null
|
||||
ExpirationDate = date.HasValue && date.Value != default ? _apiDateTimeHelper.Get(date) : null,
|
||||
Password = aceWrapper.FileShareOptions?.Password,
|
||||
Disabled = aceWrapper.FileShareOptions?.Disabled is true ? true : expired,
|
||||
DenyDownload = aceWrapper.FileShareOptions?.DenyDownload,
|
||||
IsTemplate = aceWrapper.IsTemplate,
|
||||
LinkType = aceWrapper.SubjectType switch
|
||||
{
|
||||
SubjectType.InvitationLink => LinkType.Invitation,
|
||||
SubjectType.ExternalLink => LinkType.External,
|
||||
_ => LinkType.Invitation
|
||||
},
|
||||
IsExpired = expired
|
||||
};
|
||||
}
|
||||
else
|
||||
@ -111,4 +135,4 @@ public class FileShareDtoHelper
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,22 +118,14 @@ public class FolderDtoHelper : FileEntryDtoHelper
|
||||
}
|
||||
|
||||
result.Logo = await _roomLogoManager.GetLogoAsync(folder);
|
||||
result.RoomType = folder.FolderType switch
|
||||
{
|
||||
FolderType.FillingFormsRoom => RoomType.FillingFormsRoom,
|
||||
FolderType.EditingRoom => RoomType.EditingRoom,
|
||||
FolderType.ReviewRoom => RoomType.ReviewRoom,
|
||||
FolderType.ReadOnlyRoom => RoomType.ReadOnlyRoom,
|
||||
FolderType.CustomRoom => RoomType.CustomRoom,
|
||||
_ => null,
|
||||
};
|
||||
result.RoomType = DocSpaceHelper.GetRoomType(folder.FolderType);
|
||||
|
||||
if (folder.ProviderEntry && folder.RootFolderType is FolderType.VirtualRooms)
|
||||
{
|
||||
result.ParentId = IdConverter.Convert<T>(await _globalFolderHelper.GetFolderVirtualRooms());
|
||||
|
||||
var isMuted = _roomsNotificationSettingsHelper.CheckMuteForRoom(result.Id.ToString());
|
||||
result.Mute = isMuted;
|
||||
var isMuted = _roomsNotificationSettingsHelper.CheckMuteForRoom(result.Id.ToString());
|
||||
result.Mute = isMuted;
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,4 +184,4 @@ public class FolderDtoHelper : FileEntryDtoHelper
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public class FilesSettings : ISettings<FilesSettings>
|
||||
HideTemplatesSetting = false,
|
||||
DownloadTarGzSetting = false,
|
||||
AutomaticallyCleanUpSetting = null,
|
||||
DefaultSharingAccessRightsSetting = null
|
||||
DefaultSharingAccessRightsSetting = null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -125,19 +125,23 @@ public class FilesSettingsHelper
|
||||
private readonly SetupInfo _setupInfo;
|
||||
private readonly FileUtility _fileUtility;
|
||||
private readonly FilesLinkUtility _filesLinkUtility;
|
||||
private readonly AuthContext _authContext;
|
||||
private static readonly FilesSettings _emptySettings = new();
|
||||
|
||||
public FilesSettingsHelper(
|
||||
SettingsManager settingsManager,
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
SetupInfo setupInfo,
|
||||
FileUtility fileUtility,
|
||||
FilesLinkUtility filesLinkUtility)
|
||||
FilesLinkUtility filesLinkUtility,
|
||||
AuthContext authContext)
|
||||
{
|
||||
_settingsManager = settingsManager;
|
||||
_coreBaseSettings = coreBaseSettings;
|
||||
_setupInfo = setupInfo;
|
||||
_fileUtility = fileUtility;
|
||||
_filesLinkUtility = filesLinkUtility;
|
||||
_authContext = authContext;
|
||||
}
|
||||
|
||||
public List<string> ExtsImagePreviewed => _fileUtility.ExtsImagePreviewed;
|
||||
@ -458,21 +462,31 @@ public class FilesSettingsHelper
|
||||
|
||||
private FilesSettings Load()
|
||||
{
|
||||
return _settingsManager.Load<FilesSettings>();
|
||||
return !_authContext.IsAuthenticated ? _emptySettings : _settingsManager.Load<FilesSettings>();
|
||||
}
|
||||
|
||||
private void Save(FilesSettings settings)
|
||||
{
|
||||
if (!_authContext.IsAuthenticated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settingsManager.Save(settings);
|
||||
}
|
||||
|
||||
private FilesSettings LoadForCurrentUser()
|
||||
{
|
||||
return _settingsManager.LoadForCurrentUser<FilesSettings>();
|
||||
return !_authContext.IsAuthenticated ? _emptySettings : _settingsManager.LoadForCurrentUser<FilesSettings>();
|
||||
}
|
||||
|
||||
private void SaveForCurrentUser(FilesSettings settings)
|
||||
{
|
||||
if (!_authContext.IsAuthenticated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settingsManager.SaveForCurrentUser(settings);
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public static class DaoFactoryExtension
|
||||
public static void Register(DIHelper services)
|
||||
{
|
||||
services.TryAdd<TenantDateTimeConverter>();
|
||||
services.TryAdd<FolderMappingAction>();
|
||||
services.TryAdd<FilesMappingAction>();
|
||||
|
||||
services.TryAdd<File<int>>();
|
||||
services.TryAdd<IFileDao<int>, FileDao>();
|
||||
|
@ -46,6 +46,7 @@ internal class FileDao : AbstractDao, IFileDao<int>
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ThumbnailSettings _thumbnailSettings;
|
||||
private readonly IQuotaService _quotaService;
|
||||
private readonly EmailValidationKeyProvider _emailValidationKeyProvider;
|
||||
|
||||
public FileDao(
|
||||
ILogger<FileDao> logger,
|
||||
@ -73,7 +74,7 @@ internal class FileDao : AbstractDao, IFileDao<int>
|
||||
Settings settings,
|
||||
IMapper mapper,
|
||||
ThumbnailSettings thumbnailSettings,
|
||||
IQuotaService quotaService)
|
||||
IQuotaService quotaService, EmailValidationKeyProvider emailValidationKeyProvider)
|
||||
: base(
|
||||
dbContextManager,
|
||||
userManager,
|
||||
@ -102,6 +103,7 @@ internal class FileDao : AbstractDao, IFileDao<int>
|
||||
_mapper = mapper;
|
||||
_thumbnailSettings = thumbnailSettings;
|
||||
_quotaService = quotaService;
|
||||
_emailValidationKeyProvider = emailValidationKeyProvider;
|
||||
}
|
||||
|
||||
public Task InvalidateCacheAsync(int fileId)
|
||||
@ -342,11 +344,20 @@ internal class FileDao : AbstractDao, IFileDao<int>
|
||||
|
||||
public async Task<Uri> GetPreSignedUriAsync(File<int> file, TimeSpan expires)
|
||||
{
|
||||
return await (await _globalStore.GetStoreAsync()).GetPreSignedUriAsync(string.Empty, GetUniqFilePath(file), expires,
|
||||
new List<string>
|
||||
{
|
||||
string.Concat("Content-Disposition:", ContentDispositionUtil.GetHeaderValue(file.Title, withoutBase: true))
|
||||
});
|
||||
var path = GetUniqFilePath(file);
|
||||
var headers = new List<string>
|
||||
{
|
||||
string.Concat("Content-Disposition:", ContentDispositionUtil.GetHeaderValue(file.Title, withoutBase: true))
|
||||
};
|
||||
|
||||
if (!_authContext.IsAuthenticated)
|
||||
{
|
||||
headers.Add(SecureHelper.GenerateSecureKeyHeader(path, _emailValidationKeyProvider));
|
||||
}
|
||||
|
||||
var store = await _globalStore.GetStoreAsync();
|
||||
|
||||
return await store.GetPreSignedUriAsync(string.Empty, path, expires, headers);
|
||||
}
|
||||
|
||||
public async Task<bool> IsSupportedPreSignedUriAsync(File<int> file)
|
||||
|
@ -334,7 +334,15 @@ internal class FolderDao : AbstractDao, IFolderDao<int>
|
||||
|
||||
public async IAsyncEnumerable<ParentRoomPair> GetParentRoomsAsync(IEnumerable<int> foldersIds)
|
||||
{
|
||||
var roomTypes = new List<FolderType> { FolderType.CustomRoom, FolderType.ReviewRoom, FolderType.FillingFormsRoom, FolderType.EditingRoom, FolderType.ReadOnlyRoom };
|
||||
var roomTypes = new List<FolderType>
|
||||
{
|
||||
FolderType.CustomRoom,
|
||||
FolderType.ReviewRoom,
|
||||
FolderType.FillingFormsRoom,
|
||||
FolderType.EditingRoom,
|
||||
FolderType.ReadOnlyRoom,
|
||||
FolderType.PublicRoom,
|
||||
};
|
||||
|
||||
await using var filesDbContext = _dbContextFactory.CreateDbContext();
|
||||
|
||||
@ -1223,7 +1231,16 @@ internal class FolderDao : AbstractDao, IFolderDao<int>
|
||||
|
||||
public async IAsyncEnumerable<FolderWithShare> GetFeedsForRoomsAsync(int tenant, DateTime from, DateTime to)
|
||||
{
|
||||
var roomTypes = new List<FolderType> { FolderType.CustomRoom, FolderType.ReviewRoom, FolderType.FillingFormsRoom, FolderType.EditingRoom, FolderType.ReadOnlyRoom };
|
||||
var roomTypes = new List<FolderType>
|
||||
{
|
||||
FolderType.CustomRoom,
|
||||
FolderType.ReviewRoom,
|
||||
FolderType.FillingFormsRoom,
|
||||
FolderType.EditingRoom,
|
||||
FolderType.ReadOnlyRoom,
|
||||
FolderType.PublicRoom
|
||||
};
|
||||
|
||||
Expression<Func<DbFolder, bool>> filter = f => roomTypes.Contains(f.FolderType);
|
||||
|
||||
await foreach (var e in GetFeedsInternalAsync(tenant, from, to, filter, null))
|
||||
@ -1295,7 +1312,16 @@ internal class FolderDao : AbstractDao, IFolderDao<int>
|
||||
|
||||
public async IAsyncEnumerable<int> GetTenantsWithRoomsFeedsAsync(DateTime fromTime)
|
||||
{
|
||||
var roomTypes = new List<FolderType> { FolderType.CustomRoom, FolderType.ReviewRoom, FolderType.FillingFormsRoom, FolderType.EditingRoom, FolderType.ReadOnlyRoom };
|
||||
var roomTypes = new List<FolderType>
|
||||
{
|
||||
FolderType.CustomRoom,
|
||||
FolderType.ReviewRoom,
|
||||
FolderType.FillingFormsRoom,
|
||||
FolderType.EditingRoom,
|
||||
FolderType.ReadOnlyRoom,
|
||||
FolderType.PublicRoom,
|
||||
};
|
||||
|
||||
Expression<Func<DbFolder, bool>> filter = f => roomTypes.Contains(f.FolderType);
|
||||
|
||||
await foreach (var q in GetTenantsWithFeeds(fromTime, filter, true))
|
||||
@ -1504,6 +1530,7 @@ internal class FolderDao : AbstractDao, IFolderDao<int>
|
||||
FilterType.ReviewRooms => FolderType.ReviewRoom,
|
||||
FilterType.ReadOnlyRooms => FolderType.ReadOnlyRoom,
|
||||
FilterType.CustomRooms => FolderType.CustomRoom,
|
||||
FilterType.PublicRooms => FolderType.PublicRoom,
|
||||
_ => FolderType.CustomRoom,
|
||||
};
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ using Profile = AutoMapper.Profile;
|
||||
|
||||
namespace ASC.Files.Core.EF;
|
||||
|
||||
public class DbFilesSecurity : BaseEntity, IDbFile, IMapFrom<FileShareRecord>
|
||||
public class DbFilesSecurity : BaseEntity, IDbFile
|
||||
{
|
||||
public int TenantId { get; set; }
|
||||
public string EntryId { get; set; }
|
||||
@ -38,7 +38,7 @@ public class DbFilesSecurity : BaseEntity, IDbFile, IMapFrom<FileShareRecord>
|
||||
public Guid Owner { get; set; }
|
||||
public FileShare Share { get; set; }
|
||||
public DateTime TimeStamp { get; set; }
|
||||
public string FileShareOptions { get; set; }
|
||||
public string Options { get; set; }
|
||||
|
||||
public DbTenant Tenant { get; set; }
|
||||
|
||||
@ -46,16 +46,6 @@ public class DbFilesSecurity : BaseEntity, IDbFile, IMapFrom<FileShareRecord>
|
||||
{
|
||||
return new object[] { TenantId, EntryId, EntryType, Subject };
|
||||
}
|
||||
|
||||
public void Mapping(Profile profile)
|
||||
{
|
||||
profile.CreateMap<FileShareRecord, DbFilesSecurity>()
|
||||
.AfterMap((src, dest) =>
|
||||
{
|
||||
dest.TimeStamp = DateTime.UtcNow;
|
||||
dest.FileShareOptions = src.FileShareOptions != null ? JsonSerializer.Serialize(src.FileShareOptions) : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class DbFilesSecurityExtension
|
||||
@ -118,7 +108,7 @@ public static class DbFilesSecurityExtension
|
||||
|
||||
entity.Property(e => e.SubjectType).HasColumnName("subject_type");
|
||||
|
||||
entity.Property(e => e.FileShareOptions)
|
||||
entity.Property(e => e.Options)
|
||||
.HasColumnName("options")
|
||||
.HasColumnType("text")
|
||||
.HasCharSet("utf8")
|
||||
@ -167,8 +157,8 @@ public static class DbFilesSecurityExtension
|
||||
|
||||
entity.Property(e => e.SubjectType).HasColumnName("subject_type");
|
||||
|
||||
entity.Property(e => e.FileShareOptions).HasColumnName("options");
|
||||
entity.Property(e => e.Options).HasColumnName("options");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
// 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;
|
||||
|
||||
public enum FolderType
|
||||
@ -46,7 +46,8 @@ public enum FolderType
|
||||
ReadOnlyRoom = 18,
|
||||
CustomRoom = 19,
|
||||
Archive = 20,
|
||||
ThirdpartyBackup = 21
|
||||
ThirdpartyBackup = 21,
|
||||
PublicRoom = 22
|
||||
}
|
||||
|
||||
public interface IFolder
|
||||
@ -100,4 +101,4 @@ public class Folder<T> : FileEntry<T>, IFolder
|
||||
}
|
||||
|
||||
public override string UniqID => $"folder_{Id}";
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,9 @@ public class FileStorageService //: IFileStorageService
|
||||
private readonly StudioNotifyService _studioNotifyService;
|
||||
private readonly TenantQuotaFeatureStatHelper _tenantQuotaFeatureStatHelper;
|
||||
private readonly QuotaSocketManager _quotaSocketManager;
|
||||
private readonly ExternalShare _externalShare;
|
||||
private readonly TenantUtil _tenantUtil;
|
||||
|
||||
public FileStorageService(
|
||||
Global global,
|
||||
GlobalStore globalStore,
|
||||
@ -144,7 +147,9 @@ public class FileStorageService //: IFileStorageService
|
||||
InvitationLinkHelper invitationLinkHelper,
|
||||
StudioNotifyService studioNotifyService,
|
||||
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper,
|
||||
QuotaSocketManager quotaSocketManager)
|
||||
QuotaSocketManager quotaSocketManager,
|
||||
ExternalShare externalShare,
|
||||
TenantUtil tenantUtil)
|
||||
{
|
||||
_global = global;
|
||||
_globalStore = globalStore;
|
||||
@ -202,6 +207,8 @@ public class FileStorageService //: IFileStorageService
|
||||
_studioNotifyService = studioNotifyService;
|
||||
_tenantQuotaFeatureStatHelper = tenantQuotaFeatureStatHelper;
|
||||
_quotaSocketManager = quotaSocketManager;
|
||||
_externalShare = externalShare;
|
||||
_tenantUtil = tenantUtil;
|
||||
}
|
||||
|
||||
public async Task<Folder<T>> GetFolderAsync<T>(T folderId)
|
||||
@ -510,7 +517,8 @@ public class FileStorageService //: IFileStorageService
|
||||
var room = roomType switch
|
||||
{
|
||||
RoomType.CustomRoom => await CreateCustomRoomAsync(title, parentId, @private),
|
||||
RoomType.EditingRoom => await CreateEditingRoom(title, parentId, @private),
|
||||
RoomType.EditingRoom => await CreateEditingRoomAsync(title, parentId, @private),
|
||||
RoomType.PublicRoom => await CreatePublicRoomAsync(title, parentId, @private),
|
||||
_ => await CreateCustomRoomAsync(title, parentId, @private),
|
||||
};
|
||||
|
||||
@ -522,6 +530,15 @@ public class FileStorageService //: IFileStorageService
|
||||
return room;
|
||||
}
|
||||
|
||||
private async Task<Folder<T>> CreatePublicRoomAsync<T>(string title, T parentId, bool @private)
|
||||
{
|
||||
var room = await InternalCreateNewFolderAsync(parentId, title, FolderType.PublicRoom, @private);
|
||||
|
||||
_ = await SetAceLinkAsync(room, SubjectType.ExternalLink, Guid.NewGuid(), FilesCommonResource.DefaultExternalLinkTitle, FileShare.Read, _actions[SubjectType.ExternalLink]);
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
public async Task<Folder<T>> CreateThirdPartyRoomAsync<T>(string title, RoomType roomType, T parentId, bool @private, IEnumerable<FileShareParams> share, bool notify, string sharingMessage)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(title, nameof(title));
|
||||
@ -552,7 +569,8 @@ public class FileStorageService //: IFileStorageService
|
||||
var result = roomType switch
|
||||
{
|
||||
RoomType.CustomRoom => (await CreateCustomRoomAsync(title, parentId, @private), FolderType.CustomRoom),
|
||||
RoomType.EditingRoom => (await CreateEditingRoom(title, parentId, @private), FolderType.EditingRoom),
|
||||
RoomType.EditingRoom => (await CreateEditingRoomAsync(title, parentId, @private), FolderType.EditingRoom),
|
||||
RoomType.PublicRoom => (await CreatePublicRoomAsync(title, parentId, @private), FolderType.PublicRoom),
|
||||
_ => (await CreateCustomRoomAsync(title, parentId, @private), FolderType.CustomRoom),
|
||||
};
|
||||
|
||||
@ -573,22 +591,7 @@ public class FileStorageService //: IFileStorageService
|
||||
return await InternalCreateNewFolderAsync(parentId, title, FolderType.CustomRoom, privacy);
|
||||
}
|
||||
|
||||
private async Task<Folder<T>> CreateFillingFormsRoom<T>(string title, T parentId, bool privacy)
|
||||
{
|
||||
return await InternalCreateNewFolderAsync(parentId, title, FolderType.FillingFormsRoom, privacy);
|
||||
}
|
||||
|
||||
private async Task<Folder<T>> CreateReviewRoom<T>(string title, T parentId, bool privacy)
|
||||
{
|
||||
return await InternalCreateNewFolderAsync(parentId, title, FolderType.ReviewRoom, privacy);
|
||||
}
|
||||
|
||||
private async Task<Folder<T>> CreateReadOnlyRoom<T>(string title, T parentId, bool privacy)
|
||||
{
|
||||
return await InternalCreateNewFolderAsync(parentId, title, FolderType.ReadOnlyRoom, privacy);
|
||||
}
|
||||
|
||||
private async Task<Folder<T>> CreateEditingRoom<T>(string title, T parentId, bool privacy)
|
||||
private async Task<Folder<T>> CreateEditingRoomAsync<T>(string title, T parentId, bool privacy)
|
||||
{
|
||||
return await InternalCreateNewFolderAsync(parentId, title, FolderType.EditingRoom, privacy);
|
||||
}
|
||||
@ -1649,7 +1652,8 @@ public class FileStorageService //: IFileStorageService
|
||||
return GetTasksStatuses();
|
||||
}
|
||||
|
||||
return _fileOperationsManager.MarkAsRead(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), foldersId, filesId, GetHttpHeaders());
|
||||
return _fileOperationsManager.MarkAsRead(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), foldersId, filesId, GetHttpHeaders(),
|
||||
await _externalShare.GetCurrentShareDataAsync());
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<ThirdPartyParams> GetThirdPartyAsync()
|
||||
@ -1949,15 +1953,11 @@ public class FileStorageService //: IFileStorageService
|
||||
|
||||
public List<FileOperationResult> GetTasksStatuses()
|
||||
{
|
||||
ErrorIf(!_authContext.IsAuthenticated, FilesCommonResource.ErrorMassage_SecurityException);
|
||||
|
||||
return _fileOperationsManager.GetOperationResults(_authContext.CurrentAccount.ID);
|
||||
}
|
||||
|
||||
public List<FileOperationResult> TerminateTasks()
|
||||
{
|
||||
ErrorIf(!_authContext.IsAuthenticated, FilesCommonResource.ErrorMassage_SecurityException);
|
||||
|
||||
return _fileOperationsManager.CancelOperations(_authContext.CurrentAccount.ID);
|
||||
}
|
||||
|
||||
@ -1965,7 +1965,8 @@ public class FileStorageService //: IFileStorageService
|
||||
{
|
||||
ErrorIf(folders.Count == 0 && files.Count == 0, FilesCommonResource.ErrorMassage_BadRequest);
|
||||
|
||||
return _fileOperationsManager.Download(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), folders, files, GetHttpHeaders());
|
||||
return _fileOperationsManager.Download(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), folders, files, GetHttpHeaders(),
|
||||
await _externalShare.GetCurrentShareDataAsync());
|
||||
}
|
||||
|
||||
public async Task<(List<object>, List<object>)> MoveOrCopyFilesCheckAsync<T1>(List<JsonElement> filesId, List<JsonElement> foldersId, T1 destFolderId)
|
||||
@ -2070,7 +2071,8 @@ public class FileStorageService //: IFileStorageService
|
||||
List<FileOperationResult> result;
|
||||
if (foldersId.Count > 0 || filesId.Count > 0)
|
||||
{
|
||||
result = _fileOperationsManager.MoveOrCopy(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), foldersId, filesId, destFolderId, ic, resolve, !deleteAfter, GetHttpHeaders());
|
||||
result = _fileOperationsManager.MoveOrCopy(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), foldersId, filesId, destFolderId, ic, resolve,
|
||||
!deleteAfter, GetHttpHeaders(), await _externalShare.GetCurrentShareDataAsync());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2082,21 +2084,25 @@ public class FileStorageService //: IFileStorageService
|
||||
|
||||
public async Task<List<FileOperationResult>> DeleteFileAsync<T>(string action, T fileId, bool ignoreException = false, bool deleteAfter = false, bool immediately = false)
|
||||
{
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), new List<T>(), new List<T>() { fileId }, ignoreException, !deleteAfter, immediately, GetHttpHeaders());
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), new List<T>(), new List<T>() { fileId }, ignoreException,
|
||||
!deleteAfter, immediately, GetHttpHeaders(), await _externalShare.GetCurrentShareDataAsync());
|
||||
}
|
||||
public async Task<List<FileOperationResult>> DeleteFolderAsync<T>(string action, T folderId, bool ignoreException = false, bool deleteAfter = false, bool immediately = false)
|
||||
{
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), new List<T>() { folderId }, new List<T>(), ignoreException, !deleteAfter, immediately, GetHttpHeaders());
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), new List<T>() { folderId }, new List<T>(), ignoreException,
|
||||
!deleteAfter, immediately, GetHttpHeaders(), await _externalShare.GetCurrentShareDataAsync());
|
||||
}
|
||||
|
||||
public async Task<List<FileOperationResult>> DeleteItemsAsync(string action, List<JsonElement> files, List<JsonElement> folders, bool ignoreException = false, bool deleteAfter = false, bool immediately = false)
|
||||
{
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), folders, files, ignoreException, !deleteAfter, immediately, GetHttpHeaders());
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), folders, files, ignoreException, !deleteAfter, immediately,
|
||||
GetHttpHeaders(), await _externalShare.GetCurrentShareDataAsync());
|
||||
}
|
||||
|
||||
public async Task<List<FileOperationResult>> DeleteItemsAsync<T>(string action, List<T> files, List<T> folders, bool ignoreException = false, bool deleteAfter = false, bool immediately = false)
|
||||
{
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), folders, files, ignoreException, !deleteAfter, immediately, GetHttpHeaders());
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), folders, files, ignoreException, !deleteAfter, immediately,
|
||||
GetHttpHeaders(), await _externalShare.GetCurrentShareDataAsync());
|
||||
}
|
||||
|
||||
public async Task<List<FileOperationResult>> EmptyTrashAsync()
|
||||
@ -2107,7 +2113,8 @@ public class FileStorageService //: IFileStorageService
|
||||
var foldersIdTask = await folderDao.GetFoldersAsync(trashId).Select(f => f.Id).ToListAsync();
|
||||
var filesIdTask = await fileDao.GetFilesAsync(trashId).ToListAsync();
|
||||
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), foldersIdTask, filesIdTask, false, true, false, GetHttpHeaders(), true);
|
||||
return _fileOperationsManager.Delete(_authContext.CurrentAccount.ID, await _tenantManager.GetCurrentTenantAsync(), foldersIdTask, filesIdTask, false, true,
|
||||
false, GetHttpHeaders(), await _externalShare.GetCurrentShareDataAsync(), true);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<FileOperationResult> CheckConversionAsync<T>(List<CheckConversionRequestDto<T>> filesInfoJSON, bool sync = false)
|
||||
@ -2401,9 +2408,10 @@ public class FileStorageService //: IFileStorageService
|
||||
|
||||
#endregion
|
||||
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(IEnumerable<T> fileIds, IEnumerable<T> folderIds)
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(IEnumerable<T> fileIds, IEnumerable<T> folderIds, IEnumerable<SubjectType> subjectTypes = null,
|
||||
bool withoutTemplates = false)
|
||||
{
|
||||
return await _fileSharing.GetSharedInfoAsync(fileIds, folderIds);
|
||||
return await _fileSharing.GetSharedInfoAsync(fileIds, folderIds, subjectTypes, withoutTemplates);
|
||||
}
|
||||
|
||||
public async Task<List<AceShortWrapper>> GetSharedInfoShortFileAsync<T>(T fileId)
|
||||
@ -2564,58 +2572,22 @@ public class FileStorageService //: IFileStorageService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<AceWrapper>> SetInvitationLink<T>(T roomId, Guid linkId, string title, FileShare share)
|
||||
public async Task<List<AceWrapper>> SetInvitationLinkAsync<T>(T roomId, Guid linkId, string title, FileShare share)
|
||||
{
|
||||
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(title);
|
||||
var expirationDate = DateTime.UtcNow.Add(_invitationLinkHelper.IndividualLinkExpirationInterval);
|
||||
|
||||
var folderDao = GetFolderDao<T>();
|
||||
var room = (await folderDao.GetFolderAsync(roomId)).NotFoundIfNull();
|
||||
var messageAction = MessageAction.RoomLinkCreated;
|
||||
var room = await GetFolderDao<T>().GetFolderAsync(roomId).NotFoundIfNull();
|
||||
|
||||
if (share == FileShare.None)
|
||||
{
|
||||
messageAction = MessageAction.RoomLinkDeleted;
|
||||
}
|
||||
else
|
||||
{
|
||||
var linkExist = (await _fileSecurity.GetSharesAsync(room))
|
||||
.Any(r => r.Subject == linkId && r.SubjectType == SubjectType.InvitationLink);
|
||||
return await SetAceLinkAsync(room, SubjectType.InvitationLink, linkId, title, share, _actions[SubjectType.InvitationLink], expirationDate);
|
||||
}
|
||||
|
||||
if (linkExist)
|
||||
{
|
||||
messageAction = MessageAction.RoomLinkUpdated;
|
||||
}
|
||||
}
|
||||
public async Task<List<AceWrapper>> SetExternalLinkAsync<T>(T entryId, FileEntryType entryType, Guid linkId, string title, FileShare share, DateTime expirationDate = default,
|
||||
string password = null, bool disabled = false, bool denyDownload = false)
|
||||
{
|
||||
FileEntry<T> entry = entryType == FileEntryType.File ? (await GetFileDao<T>().GetFileAsync(entryId)).NotFoundIfNull()
|
||||
: (await GetFolderDao<T>().GetFolderAsync(entryId)).NotFoundIfNull();
|
||||
|
||||
var aces = new List<AceWrapper>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Access = share,
|
||||
Id = linkId,
|
||||
SubjectType = SubjectType.InvitationLink,
|
||||
FileShareOptions = new FileShareOptions
|
||||
{
|
||||
Title = title,
|
||||
ExpirationDate = DateTime.UtcNow.Add(_invitationLinkHelper.IndividualLinkExpirationInterval)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, room, false, null, null);
|
||||
if (changed)
|
||||
{
|
||||
_ = _filesMessageService.SendAsync(room, GetHttpHeaders(), messageAction, room.Title, GetAccessString(share));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw GenerateException(e);
|
||||
}
|
||||
|
||||
return await GetSharedInfoAsync(Array.Empty<T>(), new[] { roomId });
|
||||
return await SetAceLinkAsync(entry, SubjectType.ExternalLink, linkId, title, share, _actions[SubjectType.ExternalLink], expirationDate, password, disabled, denyDownload, 10);
|
||||
}
|
||||
|
||||
public async Task<bool> SetAceLinkAsync<T>(T fileId, FileShare share)
|
||||
@ -2917,15 +2889,16 @@ public class FileStorageService //: IFileStorageService
|
||||
public async IAsyncEnumerable<FileEntry> ChangeOwnerAsync<T>(IEnumerable<T> foldersId, IEnumerable<T> filesId, Guid userId)
|
||||
{
|
||||
var userInfo = await _userManager.GetUsersAsync(userId);
|
||||
ErrorIf(Equals(userInfo, Constants.LostUser) || await _userManager.IsUserAsync(userInfo), FilesCommonResource.ErrorMassage_ChangeOwner);
|
||||
ErrorIf(Equals(userInfo, Constants.LostUser) || await _userManager.IsUserAsync(userInfo) || await _userManager.IsCollaboratorAsync(userInfo), FilesCommonResource.ErrorMassage_ChangeOwner);
|
||||
|
||||
var folderDao = GetFolderDao<T>();
|
||||
var folders = folderDao.GetFoldersAsync(foldersId);
|
||||
|
||||
await foreach (var folder in folders)
|
||||
{
|
||||
ErrorIf(folder.RootFolderType is not FolderType.COMMON and not FolderType.VirtualRooms, FilesCommonResource.ErrorMassage_SecurityException);
|
||||
ErrorIf(!await _fileSecurity.CanEditAsync(folder), FilesCommonResource.ErrorMassage_SecurityException);
|
||||
ErrorIf(folder.RootFolderType != FolderType.COMMON, FilesCommonResource.ErrorMassage_SecurityException);
|
||||
|
||||
if (folder.ProviderEntry)
|
||||
{
|
||||
continue;
|
||||
@ -3142,6 +3115,11 @@ public class FileStorageService //: IFileStorageService
|
||||
|
||||
public async Task<IEnumerable<JsonElement>> CreateThumbnailsAsync(List<JsonElement> fileIds)
|
||||
{
|
||||
if (!_authContext.IsAuthenticated && (await _externalShare.GetLinkIdAsync()) == default)
|
||||
{
|
||||
throw GenerateException(new SecurityException(FilesCommonResource.ErrorMassage_SecurityException));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var (fileIntIds, _) = FileOperationsManager.GetIds(fileIds);
|
||||
@ -3332,6 +3310,85 @@ public class FileStorageService //: IFileStorageService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<AceWrapper>> SetAceLinkAsync<T>(FileEntry<T> entry, SubjectType subjectType, Guid linkId, string title, FileShare share,
|
||||
IReadOnlyDictionary<EventType, MessageAction> messageActions, DateTime expirationDate = default, string password = null, bool disabled = false, bool denyDownload = false,
|
||||
int maxLinksCount = int.MaxValue)
|
||||
{
|
||||
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(title);
|
||||
|
||||
if (linkId == default)
|
||||
{
|
||||
linkId = Guid.NewGuid();
|
||||
}
|
||||
|
||||
var action = EventType.Create;
|
||||
|
||||
var links = (await _fileSecurity.GetSharesAsync(entry))
|
||||
.Where(r => r.SubjectType == subjectType).ToList();
|
||||
|
||||
if (share == FileShare.None)
|
||||
{
|
||||
action = EventType.Remove;
|
||||
}
|
||||
else if (links.Any(r => r.Subject == linkId))
|
||||
{
|
||||
action = EventType.Update;
|
||||
}
|
||||
|
||||
if (action == EventType.Create && links.Count == maxLinksCount)
|
||||
{
|
||||
throw GenerateException(new InvalidOperationException(FilesCommonResource.ErrorMessage_MaxLinksCount));
|
||||
}
|
||||
|
||||
var options = new FileShareOptions
|
||||
{
|
||||
Title = title,
|
||||
Disabled = disabled,
|
||||
DenyDownload = denyDownload
|
||||
};
|
||||
|
||||
var expirationDateUtc = _tenantUtil.DateTimeToUtc(expirationDate);
|
||||
|
||||
if (expirationDateUtc != DateTime.MinValue && expirationDateUtc > DateTime.UtcNow)
|
||||
{
|
||||
options.ExpirationDate = expirationDateUtc;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
options.Password = await _externalShare.CreatePasswordKeyAsync(password);
|
||||
}
|
||||
|
||||
var aces = new List<AceWrapper>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Access = share,
|
||||
Id = linkId,
|
||||
SubjectType = subjectType,
|
||||
FileShareOptions = options
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var (changed, _) = await _fileSharingAceHelper.SetAceObjectAsync(aces, entry, false, null, null);
|
||||
|
||||
if (changed)
|
||||
{
|
||||
_ = _filesMessageService.SendAsync(entry, GetHttpHeaders(), messageActions[action], entry.Title, GetAccessString(share));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw GenerateException(e);
|
||||
}
|
||||
|
||||
return entry.FileEntryType == FileEntryType.File ?
|
||||
await GetSharedInfoAsync(new[] { entry.Id }, Array.Empty<T>()) :
|
||||
await GetSharedInfoAsync(Array.Empty<T>(), new[] { entry.Id });
|
||||
}
|
||||
|
||||
private async Task<List<AceWrapper>> GetFullAceWrappersAsync(IEnumerable<FileShareParams> share)
|
||||
{
|
||||
var dict = await share.ToAsyncEnumerable().SelectAwait(async s => await _fileShareParamsHelper.ToAceObjectAsync(s)).ToDictionaryAsync(k => k.Id, v => v);
|
||||
@ -3393,6 +3450,27 @@ public class FileStorageService //: IFileStorageService
|
||||
Create,
|
||||
Remove
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyDictionary<SubjectType, IReadOnlyDictionary<EventType, MessageAction>> _actions =
|
||||
new Dictionary<SubjectType, IReadOnlyDictionary<EventType, MessageAction>>
|
||||
{
|
||||
{
|
||||
SubjectType.InvitationLink, new Dictionary<EventType, MessageAction>
|
||||
{
|
||||
{ EventType.Create, MessageAction.RoomInvitationLinkCreated },
|
||||
{ EventType.Update, MessageAction.RoomInvitationLinkUpdated },
|
||||
{ EventType.Remove, MessageAction.RoomInvitationLinkDeleted }
|
||||
}
|
||||
},
|
||||
{
|
||||
SubjectType.ExternalLink, new Dictionary<EventType, MessageAction>
|
||||
{
|
||||
{ EventType.Create, MessageAction.ExternalLinkCreated },
|
||||
{ EventType.Update, MessageAction.ExternalLinkDeleted },
|
||||
{ EventType.Remove, MessageAction.ExternalLinkUpdated }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public class FileModel<T, TTempate>
|
||||
|
@ -46,5 +46,6 @@ public enum FilterType
|
||||
[EnumMember] ReadOnlyRooms = 16,
|
||||
[EnumMember] CustomRooms = 17,
|
||||
[EnumMember] OFormTemplateOnly = 18,
|
||||
[EnumMember] OFormOnly = 19
|
||||
[EnumMember] OFormOnly = 19,
|
||||
[EnumMember] PublicRooms = 20
|
||||
}
|
320
products/ASC.Files/Core/Core/Security/ExternalShare.cs
Normal file
320
products/ASC.Files/Core/Core/Security/ExternalShare.cs
Normal file
@ -0,0 +1,320 @@
|
||||
// (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.Security;
|
||||
|
||||
[Scope]
|
||||
public class ExternalShare
|
||||
{
|
||||
private readonly Global _global;
|
||||
private readonly IDaoFactory _daoFactory;
|
||||
private readonly CookiesManager _cookiesManager;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly CommonLinkUtility _commonLinkUtility;
|
||||
private Guid _linkId;
|
||||
private Guid _sessionId;
|
||||
private string _passwordKey;
|
||||
private string _dbKey;
|
||||
|
||||
public ExternalShare(
|
||||
Global global,
|
||||
IDaoFactory daoFactory,
|
||||
CookiesManager cookiesManager,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
CommonLinkUtility commonLinkUtility)
|
||||
{
|
||||
_global = global;
|
||||
_daoFactory = daoFactory;
|
||||
_cookiesManager = cookiesManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_commonLinkUtility = commonLinkUtility;
|
||||
}
|
||||
|
||||
public async Task<string> GetLinkAsync(Guid linkId)
|
||||
{
|
||||
var key = await CreateShareKeyAsync(linkId);
|
||||
|
||||
return _commonLinkUtility.GetFullAbsolutePath($"rooms/share?key={key}");
|
||||
}
|
||||
|
||||
public async Task<Status> ValidateAsync(Guid linkId)
|
||||
{
|
||||
var record = await _daoFactory.GetSecurityDao<int>().GetSharesAsync(new [] { linkId }).FirstOrDefaultAsync();
|
||||
|
||||
return record == null ? Status.Invalid : await ValidateRecordAsync(record, null);
|
||||
}
|
||||
|
||||
public async Task<Status> ValidateRecordAsync(FileShareRecord record, string password)
|
||||
{
|
||||
if (record.SubjectType != SubjectType.ExternalLink ||
|
||||
record.Subject == FileConstant.ShareLinkId ||
|
||||
record.Options == null)
|
||||
{
|
||||
return Status.Ok;
|
||||
}
|
||||
|
||||
if (record.Options.IsExpired)
|
||||
{
|
||||
return Status.Expired;
|
||||
}
|
||||
|
||||
if (record.Options.Disabled)
|
||||
{
|
||||
return Status.Invalid;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(record.Options.Password))
|
||||
{
|
||||
return Status.Ok;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_passwordKey))
|
||||
{
|
||||
_passwordKey = _cookiesManager.GetCookies(CookiesType.ShareLink, record.Subject.ToString(), true);
|
||||
}
|
||||
|
||||
if (_passwordKey == record.Options.Password)
|
||||
{
|
||||
return Status.Ok;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
return Status.RequiredPassword;
|
||||
}
|
||||
|
||||
if (await CreatePasswordKeyAsync(password) == record.Options.Password)
|
||||
{
|
||||
await _cookiesManager.SetCookiesAsync(CookiesType.ShareLink, record.Options.Password, true, record.Subject.ToString());
|
||||
return Status.Ok;
|
||||
}
|
||||
|
||||
_cookiesManager.ClearCookies(CookiesType.ShareLink, record.Subject.ToString());
|
||||
|
||||
return Status.InvalidPassword;
|
||||
}
|
||||
|
||||
public async Task<string> CreatePasswordKeyAsync(string password)
|
||||
{
|
||||
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(password);
|
||||
|
||||
return Signature.Create(password, await GetDbKeyAsync());
|
||||
}
|
||||
|
||||
public async Task<string> GetPasswordAsync(string passwordKey)
|
||||
{
|
||||
return string.IsNullOrEmpty(passwordKey) ? null : Signature.Read<string>(passwordKey, await GetDbKeyAsync());
|
||||
}
|
||||
|
||||
public string GetKey()
|
||||
{
|
||||
var key = _httpContextAccessor.HttpContext?.Request.Headers[HttpRequestExtensions.RequestTokenHeader].FirstOrDefault();
|
||||
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
key = _httpContextAccessor.HttpContext?.Request.Query.GetRequestValue(FilesLinkUtility.FolderShareKey);
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(key) ? null : key;
|
||||
}
|
||||
|
||||
public async Task<Guid> ParseShareKeyAsync(string key)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||
|
||||
return Signature.Read<Guid>(key, await GetDbKeyAsync());
|
||||
}
|
||||
|
||||
public Guid GetLinkId()
|
||||
{
|
||||
return GetLinkIdAsync().Result;
|
||||
}
|
||||
|
||||
public async Task<Guid> GetLinkIdAsync()
|
||||
{
|
||||
if (_linkId != default)
|
||||
{
|
||||
return _linkId;
|
||||
}
|
||||
|
||||
var key = GetKey();
|
||||
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var linkId = await ParseShareKeyAsync(key);
|
||||
|
||||
if (linkId == default)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
_linkId = linkId;
|
||||
|
||||
return linkId;
|
||||
}
|
||||
|
||||
public Guid GetSessionId()
|
||||
{
|
||||
return GetSessionIdAsync().Result;
|
||||
}
|
||||
|
||||
public async Task<Guid> GetSessionIdAsync()
|
||||
{
|
||||
if (_sessionId != default)
|
||||
{
|
||||
return _sessionId;
|
||||
}
|
||||
|
||||
var sessionKey = _cookiesManager.GetCookies(CookiesType.AnonymousSessionKey);
|
||||
|
||||
if (string.IsNullOrEmpty(sessionKey))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var id = Signature.Read<Guid>(sessionKey, await GetDbKeyAsync());
|
||||
|
||||
if (id == default)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
_sessionId = id;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public async Task<ExternalShareData> GetCurrentShareDataAsync()
|
||||
{
|
||||
var linkId = await GetLinkIdAsync();
|
||||
var sessionId = await GetSessionIdAsync();
|
||||
var password = string.IsNullOrEmpty(_passwordKey) ? _cookiesManager.GetCookies(CookiesType.ShareLink, _linkId.ToString(), true) : _passwordKey;
|
||||
|
||||
return new ExternalShareData(linkId, sessionId, password);
|
||||
}
|
||||
|
||||
public void SetCurrentShareData(ExternalShareData data)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
if (_linkId == default)
|
||||
{
|
||||
_linkId = data.LinkId;
|
||||
}
|
||||
|
||||
if (_sessionId == default)
|
||||
{
|
||||
_sessionId = data.SessionId;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_passwordKey))
|
||||
{
|
||||
_passwordKey = data.PasswordKey;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> CreateDownloadSessionKeyAsync()
|
||||
{
|
||||
var linkId = await GetLinkIdAsync();
|
||||
var sessionId = await GetSessionIdAsync();
|
||||
|
||||
var session = new DownloadSession
|
||||
{
|
||||
Id = sessionId,
|
||||
LinkId = linkId
|
||||
};
|
||||
|
||||
return Signature.Create(session, await GetDbKeyAsync());
|
||||
}
|
||||
|
||||
public async Task<DownloadSession> ParseDownloadSessionKeyAsync(string sessionKey)
|
||||
{
|
||||
return Signature.Read<DownloadSession>(sessionKey, await GetDbKeyAsync());
|
||||
}
|
||||
|
||||
public string GetAnonymousSessionKey()
|
||||
{
|
||||
return _cookiesManager.GetCookies(CookiesType.AnonymousSessionKey);
|
||||
}
|
||||
|
||||
public async Task SetAnonymousSessionKeyAsync()
|
||||
{
|
||||
await _cookiesManager.SetCookiesAsync(CookiesType.AnonymousSessionKey, Signature.Create(Guid.NewGuid(), await GetDbKeyAsync()), true);
|
||||
}
|
||||
|
||||
private async Task<string> CreateShareKeyAsync(Guid linkId)
|
||||
{
|
||||
return Signature.Create(linkId, await GetDbKeyAsync());
|
||||
}
|
||||
|
||||
private async Task<string> GetDbKeyAsync()
|
||||
{
|
||||
return _dbKey ??= await _global.GetDocDbKeyAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class ValidationInfo
|
||||
{
|
||||
public Status Status { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public FileShare Access { get; set; }
|
||||
public FolderType FolderType { get; set; }
|
||||
public Logo Logo { get; set; }
|
||||
public int TenantId { get; set; }
|
||||
}
|
||||
|
||||
public class ExternalShareData
|
||||
{
|
||||
public Guid LinkId { get; }
|
||||
public Guid SessionId { get; }
|
||||
public string PasswordKey { get; }
|
||||
|
||||
public ExternalShareData(Guid linkId, Guid sessionId, string passwordKey)
|
||||
{
|
||||
LinkId = linkId;
|
||||
SessionId = sessionId;
|
||||
PasswordKey = passwordKey;
|
||||
}
|
||||
}
|
||||
|
||||
public class DownloadSession
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid LinkId { get; set; }
|
||||
}
|
||||
|
||||
public enum Status
|
||||
{
|
||||
Ok,
|
||||
Invalid,
|
||||
Expired,
|
||||
RequiredPassword,
|
||||
InvalidPassword
|
||||
}
|
@ -87,6 +87,12 @@ public class FileSecurity : IFileSecurity
|
||||
{
|
||||
FileShare.RoomAdmin, FileShare.Collaborator, FileShare.Read, FileShare.None
|
||||
}
|
||||
},
|
||||
{
|
||||
FolderType.PublicRoom, new HashSet<FileShare>
|
||||
{
|
||||
FileShare.RoomAdmin, FileShare.Collaborator, FileShare.Read, FileShare.None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -138,6 +144,7 @@ public class FileSecurity : IFileSecurity
|
||||
FilesSecurityActions.Copy,
|
||||
FilesSecurityActions.Move,
|
||||
FilesSecurityActions.Duplicate,
|
||||
FilesSecurityActions.Download,
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -156,6 +163,7 @@ public class FileSecurity : IFileSecurity
|
||||
FilesSecurityActions.Mute,
|
||||
FilesSecurityActions.EditAccess,
|
||||
FilesSecurityActions.Duplicate,
|
||||
FilesSecurityActions.Download
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -169,6 +177,7 @@ public class FileSecurity : IFileSecurity
|
||||
private readonly FileUtility _fileUtility;
|
||||
private readonly StudioNotifyHelper _studioNotifyHelper;
|
||||
private readonly BadgesSettingsHelper _badgesSettingsHelper;
|
||||
private readonly ExternalShare _externalShare;
|
||||
|
||||
public FileSecurity(
|
||||
IDaoFactory daoFactory,
|
||||
@ -180,7 +189,8 @@ public class FileSecurity : IFileSecurity
|
||||
FileSecurityCommon fileSecurityCommon,
|
||||
FileUtility fileUtility,
|
||||
StudioNotifyHelper studioNotifyHelper,
|
||||
BadgesSettingsHelper badgesSettingsHelper)
|
||||
BadgesSettingsHelper badgesSettingsHelper,
|
||||
ExternalShare externalShare)
|
||||
{
|
||||
_daoFactory = daoFactory;
|
||||
_userManager = userManager;
|
||||
@ -192,6 +202,7 @@ public class FileSecurity : IFileSecurity
|
||||
_fileUtility = fileUtility;
|
||||
_studioNotifyHelper = studioNotifyHelper;
|
||||
_badgesSettingsHelper = badgesSettingsHelper;
|
||||
_externalShare = externalShare;
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<Tuple<FileEntry<T>, bool>> CanReadAsync<T>(IAsyncEnumerable<FileEntry<T>> entries, Guid userId)
|
||||
@ -271,7 +282,7 @@ public class FileSecurity : IFileSecurity
|
||||
|
||||
public async Task<bool> CanDownloadAsync<T>(FileEntry<T> entry, Guid userId)
|
||||
{
|
||||
if (!await CanReadAsync(entry, userId))
|
||||
if (!await CanAsync(entry, userId, FilesSecurityActions.Download))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -631,9 +642,9 @@ public class FileSecurity : IFileSecurity
|
||||
|
||||
private async Task<bool> CanAsync<T>(FileEntry<T> entry, Guid userId, FilesSecurityActions action, IEnumerable<FileShareRecord> shares = null)
|
||||
{
|
||||
if (entry.Security != null && entry.Security.ContainsKey(action))
|
||||
if (entry.Security != null && entry.Security.TryGetValue(action, out var result))
|
||||
{
|
||||
return entry.Security[action];
|
||||
return result;
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUsersAsync(userId);
|
||||
@ -711,11 +722,11 @@ public class FileSecurity : IFileSecurity
|
||||
|
||||
private async Task<bool> FilterEntry<T>(FileEntry<T> e, FilesSecurityActions action, Guid userId, IEnumerable<FileShareRecord> shares, bool isOutsider, bool isUser, bool isAuthenticated, bool isDocSpaceAdmin, bool isCollaborator)
|
||||
{
|
||||
if (!isAuthenticated && userId != FileConstant.ShareLinkId)
|
||||
if (!isAuthenticated && action is not (FilesSecurityActions.Read or FilesSecurityActions.Download))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var file = e as File<T>;
|
||||
var folder = e as Folder<T>;
|
||||
var isRoom = folder != null && DocSpaceHelper.IsRoom(folder.FolderType);
|
||||
@ -756,7 +767,7 @@ public class FileSecurity : IFileSecurity
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action == FilesSecurityActions.Copy && isRoom)
|
||||
if (action is FilesSecurityActions.Copy or FilesSecurityActions.Duplicate && isRoom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -788,11 +799,10 @@ public class FileSecurity : IFileSecurity
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (isAuthenticated)
|
||||
{
|
||||
if (folder.FolderType == FolderType.VirtualRooms)
|
||||
{
|
||||
// all can read VirtualRooms folder
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -914,6 +924,12 @@ public class FileSecurity : IFileSecurity
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (ace is { SubjectType: SubjectType.ExternalLink } && ace.Subject != userId &&
|
||||
await _externalShare.ValidateRecordAsync(ace, null) != Status.Ok)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var defaultShare = userId == FileConstant.ShareLinkId
|
||||
? FileShare.Restrict
|
||||
: e.RootFolderType == FolderType.VirtualRooms
|
||||
@ -924,7 +940,7 @@ public class FileSecurity : IFileSecurity
|
||||
? DefaultPrivacyShare
|
||||
: DefaultCommonShare;
|
||||
|
||||
e.Access = ace != null ? ace.Share : defaultShare;
|
||||
e.Access = ace?.Share ?? defaultShare;
|
||||
|
||||
e.Access = e.RootFolderType == FolderType.ThirdpartyBackup ? FileShare.Restrict : e.Access;
|
||||
|
||||
@ -935,8 +951,7 @@ public class FileSecurity : IFileSecurity
|
||||
case FilesSecurityActions.Mute:
|
||||
return e.Access != FileShare.Restrict;
|
||||
case FilesSecurityActions.Comment:
|
||||
if (e.Access == FileShare.Comment ||
|
||||
e.Access == FileShare.Review ||
|
||||
if (e.Access is FileShare.Comment or FileShare.Review ||
|
||||
e.Access == FileShare.CustomFilter ||
|
||||
e.Access == FileShare.ReadWrite ||
|
||||
e.Access == FileShare.RoomAdmin ||
|
||||
@ -1076,6 +1091,12 @@ public class FileSecurity : IFileSecurity
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case FilesSecurityActions.Download:
|
||||
if (e.Access != FileShare.Restrict && ace?.Options is not { DenyDownload: true })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.Access != FileShare.Restrict &&
|
||||
@ -1095,7 +1116,7 @@ public class FileSecurity : IFileSecurity
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task ShareAsync<T>(T entryId, FileEntryType entryType, Guid @for, FileShare share, SubjectType subjectType = default, FileShareOptions fileShareOptions = null)
|
||||
public async Task ShareAsync<T>(T entryId, FileEntryType entryType, Guid @for, FileShare share, SubjectType subjectType = default, FileShareOptions options = null)
|
||||
{
|
||||
var securityDao = _daoFactory.GetSecurityDao<T>();
|
||||
var r = new FileShareRecord
|
||||
@ -1107,7 +1128,7 @@ public class FileSecurity : IFileSecurity
|
||||
Owner = _authContext.CurrentAccount.ID,
|
||||
Share = share,
|
||||
SubjectType = subjectType,
|
||||
FileShareOptions = fileShareOptions,
|
||||
Options = options,
|
||||
};
|
||||
|
||||
await securityDao.SetShareAsync(r);
|
||||
@ -1598,6 +1619,14 @@ public class FileSecurity : IFileSecurity
|
||||
// User, Departments, admin, everyone
|
||||
|
||||
var result = new List<Guid> { userId };
|
||||
|
||||
var linkId = await _externalShare.GetLinkIdAsync();
|
||||
|
||||
if (linkId != default)
|
||||
{
|
||||
result.Add(linkId);
|
||||
}
|
||||
|
||||
if (userId == FileConstant.ShareLinkId)
|
||||
{
|
||||
return result;
|
||||
@ -1726,5 +1755,6 @@ public class FileSecurity : IFileSecurity
|
||||
Mute,
|
||||
EditAccess,
|
||||
Duplicate,
|
||||
Download,
|
||||
}
|
||||
}
|
||||
|
@ -31,4 +31,10 @@ public class FileShareOptions
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public DateTime ExpirationDate { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool DenyDownload { get; set; }
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsExpired => ExpirationDate != DateTime.MinValue && ExpirationDate<DateTime.UtcNow;
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
|
||||
namespace ASC.Files.Core.Security;
|
||||
|
||||
public class FileShareRecord : IMapFrom<DbFilesSecurity>
|
||||
public class FileShareRecord
|
||||
{
|
||||
public int TenantId { get; set; }
|
||||
public object EntryId { get; set; }
|
||||
@ -35,15 +35,9 @@ public class FileShareRecord : IMapFrom<DbFilesSecurity>
|
||||
public Guid Subject { get; set; }
|
||||
public Guid Owner { get; set; }
|
||||
public FileShare Share { get; set; }
|
||||
public FileShareOptions FileShareOptions { get; set; }
|
||||
public FileShareOptions Options { get; set; }
|
||||
public int Level { get; set; }
|
||||
public bool IsLink => SubjectType == SubjectType.InvitationLink || SubjectType == SubjectType.ExternalLink;
|
||||
|
||||
public void Mapping(AutoMapper.Profile profile)
|
||||
{
|
||||
profile.CreateMap<DbFilesSecurity, FileShareRecord>()
|
||||
.ForMember(dest => dest.FileShareOptions, opt => opt.MapFrom(src => JsonSerializer.Deserialize<FileShareOptions>(src.FileShareOptions, new JsonSerializerOptions())));
|
||||
}
|
||||
public bool IsLink => SubjectType is SubjectType.InvitationLink or SubjectType.ExternalLink;
|
||||
|
||||
public class ShareComparer : IComparer<FileShare>
|
||||
{
|
||||
|
@ -745,6 +745,7 @@ internal abstract class BaseFolderDao
|
||||
FilterType.ReviewRooms => FolderType.ReviewRoom,
|
||||
FilterType.ReadOnlyRooms => FolderType.ReadOnlyRoom,
|
||||
FilterType.CustomRooms => FolderType.CustomRoom,
|
||||
FilterType.PublicRooms => FolderType.PublicRoom,
|
||||
_ => FolderType.DEFAULT,
|
||||
};
|
||||
|
||||
|
@ -345,6 +345,7 @@ internal abstract class ThirdPartyProviderDao
|
||||
FilterType.ReviewRooms => FolderType.ReviewRoom,
|
||||
FilterType.ReadOnlyRooms => FolderType.ReadOnlyRoom,
|
||||
FilterType.CustomRooms => FolderType.CustomRoom,
|
||||
FilterType.PublicRooms => FolderType.PublicRoom,
|
||||
_ => FolderType.DEFAULT,
|
||||
};
|
||||
|
||||
|
@ -137,13 +137,13 @@ public class InvitationLinkService
|
||||
|
||||
var record = await GetLinkRecordAsync(validationResult.LinkId);
|
||||
|
||||
if (record?.FileShareOptions == null)
|
||||
if (record?.Options == null)
|
||||
{
|
||||
linkData.Result = EmailValidationKeyProvider.ValidationResult.Invalid;
|
||||
return linkData;
|
||||
}
|
||||
|
||||
linkData.Result = record.FileShareOptions.ExpirationDate > DateTime.UtcNow ?
|
||||
linkData.Result = record.Options.ExpirationDate > DateTime.UtcNow ?
|
||||
EmailValidationKeyProvider.ValidationResult.Ok : EmailValidationKeyProvider.ValidationResult.Expired;
|
||||
linkData.Share = record.Share;
|
||||
linkData.RoomId = record.EntryId.ToString();
|
||||
|
@ -32,4 +32,9 @@ public class Logo
|
||||
public string Large { get; set; }
|
||||
public string Medium { get; set; }
|
||||
public string Small { get; set; }
|
||||
|
||||
public bool IsDefault()
|
||||
{
|
||||
return string.IsNullOrEmpty(Original);
|
||||
}
|
||||
}
|
@ -35,10 +35,10 @@ public class RoomLogoManager
|
||||
private const string ModuleName = "room_logos";
|
||||
private const string TempDomainPath = "logos_temp";
|
||||
|
||||
private static (SizeName, Size) _originalLogoSize = (SizeName.Original, new Size(1280, 1280));
|
||||
private static (SizeName, Size) _largeLogoSize = (SizeName.Large, new Size(96, 96));
|
||||
private static (SizeName, Size) _mediumLogoSize = (SizeName.Medium, new Size(32, 32));
|
||||
private static (SizeName, Size) _smallLogoSize = (SizeName.Small, new Size(16, 16));
|
||||
private static readonly (SizeName, Size) _originalLogoSize = (SizeName.Original, new Size(1280, 1280));
|
||||
private static readonly (SizeName, Size) _largeLogoSize = (SizeName.Large, new Size(96, 96));
|
||||
private static readonly (SizeName, Size) _mediumLogoSize = (SizeName.Medium, new Size(32, 32));
|
||||
private static readonly (SizeName, Size) _smallLogoSize = (SizeName.Small, new Size(16, 16));
|
||||
|
||||
private readonly IDaoFactory _daoFactory;
|
||||
private readonly FileSecurity _fileSecurity;
|
||||
@ -48,6 +48,8 @@ public class RoomLogoManager
|
||||
private IDataStore _dataStore;
|
||||
private readonly FilesMessageService _filesMessageService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly EmailValidationKeyProvider _emailValidationKeyProvider;
|
||||
private readonly SecurityContext _securityContext;
|
||||
|
||||
public RoomLogoManager(
|
||||
StorageFactory storageFactory,
|
||||
@ -56,7 +58,9 @@ public class RoomLogoManager
|
||||
FileSecurity fileSecurity,
|
||||
ILogger<RoomLogoManager> logger,
|
||||
FilesMessageService filesMessageService,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
EmailValidationKeyProvider emailValidationKeyProvider,
|
||||
SecurityContext securityContext)
|
||||
{
|
||||
_storageFactory = storageFactory;
|
||||
_tenantManager = tenantManager;
|
||||
@ -65,11 +69,13 @@ public class RoomLogoManager
|
||||
_logger = logger;
|
||||
_filesMessageService = filesMessageService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_emailValidationKeyProvider = emailValidationKeyProvider;
|
||||
_securityContext = securityContext;
|
||||
}
|
||||
|
||||
public bool EnableAudit { get; set; } = true;
|
||||
private int TenantId => _tenantManager.GetCurrentTenant().Id;
|
||||
private IDictionary<string, StringValues> Headers => _httpContextAccessor?.HttpContext?.Request?.Headers;
|
||||
private IDictionary<string, StringValues> Headers => _httpContextAccessor?.HttpContext?.Request.Headers;
|
||||
|
||||
private async ValueTask<IDataStore> GetDataStoreAsync()
|
||||
{
|
||||
@ -178,13 +184,14 @@ public class RoomLogoManager
|
||||
var id = GetId(room);
|
||||
|
||||
var cacheKey = Math.Abs(room.ModifiedOn.GetHashCode());
|
||||
var secure = !_securityContext.IsAuthenticated;
|
||||
|
||||
return new Logo
|
||||
{
|
||||
Original = await GetLogoPathAsync(id, SizeName.Original) + $"?hash={cacheKey}",
|
||||
Large = await GetLogoPathAsync(id, SizeName.Large) + $"?hash={cacheKey}",
|
||||
Medium = await GetLogoPathAsync(id, SizeName.Medium) + $"?hash={cacheKey}",
|
||||
Small = await GetLogoPathAsync(id, SizeName.Small) + $"?hash={cacheKey}"
|
||||
Original = await GetLogoPathAsync(id, SizeName.Original, cacheKey, secure),
|
||||
Large = await GetLogoPathAsync(id, SizeName.Large, cacheKey, secure),
|
||||
Medium = await GetLogoPathAsync(id, SizeName.Medium, cacheKey, secure),
|
||||
Small = await GetLogoPathAsync(id, SizeName.Small, cacheKey, secure)
|
||||
};
|
||||
}
|
||||
|
||||
@ -280,12 +287,16 @@ public class RoomLogoManager
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<string> GetLogoPathAsync<T>(T id, SizeName size)
|
||||
private async ValueTask<string> GetLogoPathAsync<T>(T id, SizeName size, int hash, bool secure = false)
|
||||
{
|
||||
var fileName = string.Format(LogosPath, ProcessFolderId(id), size.ToStringLowerFast());
|
||||
var uri = await (await GetDataStoreAsync()).GetUriAsync(fileName);
|
||||
var headers = secure ? new[] { SecureHelper.GenerateSecureKeyHeader(fileName, _emailValidationKeyProvider) } : null;
|
||||
|
||||
return uri.ToString();
|
||||
var store = await GetDataStoreAsync();
|
||||
|
||||
var uri = await store.GetPreSignedUriAsync(string.Empty, fileName, TimeSpan.MaxValue, headers);
|
||||
|
||||
return uri + (secure ? "&" : "?") + $"hash={hash}";
|
||||
}
|
||||
|
||||
private async Task<byte[]> GetTempAsync(string fileName)
|
||||
|
@ -35,7 +35,22 @@ public static class DocSpaceHelper
|
||||
FolderType.EditingRoom or
|
||||
FolderType.ReviewRoom or
|
||||
FolderType.ReadOnlyRoom or
|
||||
FolderType.FillingFormsRoom;
|
||||
FolderType.FillingFormsRoom or
|
||||
FolderType.PublicRoom;
|
||||
}
|
||||
|
||||
public static RoomType? GetRoomType(FolderType folderType)
|
||||
{
|
||||
return folderType switch
|
||||
{
|
||||
FolderType.FillingFormsRoom => RoomType.FillingFormsRoom,
|
||||
FolderType.EditingRoom => RoomType.EditingRoom,
|
||||
FolderType.ReviewRoom => RoomType.ReviewRoom,
|
||||
FolderType.ReadOnlyRoom => RoomType.ReadOnlyRoom,
|
||||
FolderType.CustomRoom => RoomType.CustomRoom,
|
||||
FolderType.PublicRoom => RoomType.PublicRoom,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<bool> LocatedInPrivateRoomAsync<T>(File<T> file, IFolderDao<T> folderDao)
|
||||
|
122
products/ASC.Files/Core/Helpers/ExternalLinkHelper.cs
Normal file
122
products/ASC.Files/Core/Helpers/ExternalLinkHelper.cs
Normal file
@ -0,0 +1,122 @@
|
||||
// (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 Status = ASC.Files.Core.Security.Status;
|
||||
|
||||
namespace ASC.Files.Core.Helpers;
|
||||
|
||||
[Scope]
|
||||
public class ExternalLinkHelper
|
||||
{
|
||||
private readonly ExternalShare _externalShare;
|
||||
private readonly RoomLogoManager _roomLogoManager;
|
||||
private readonly SecurityContext _securityContext;
|
||||
private readonly IDaoFactory _daoFactory;
|
||||
|
||||
public ExternalLinkHelper(ExternalShare externalShare, RoomLogoManager roomLogoManager, SecurityContext securityContext, IDaoFactory daoFactory)
|
||||
{
|
||||
_externalShare = externalShare;
|
||||
_roomLogoManager = roomLogoManager;
|
||||
_securityContext = securityContext;
|
||||
_daoFactory = daoFactory;
|
||||
}
|
||||
|
||||
public async Task<ValidationInfo> ValidateAsync(string key, string password = null)
|
||||
{
|
||||
var result = new ValidationInfo
|
||||
{
|
||||
Status = Status.Invalid,
|
||||
Access = FileShare.Restrict
|
||||
};
|
||||
|
||||
var linkId = await _externalShare.ParseShareKeyAsync(key);
|
||||
|
||||
var record = await _daoFactory.GetSecurityDao<int>().GetSharesAsync(new[] { linkId }).FirstOrDefaultAsync();
|
||||
|
||||
if (record == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var status = await _externalShare.ValidateRecordAsync(record, password);
|
||||
result.Status = status;
|
||||
|
||||
if (status != Status.Ok && status != Status.RequiredPassword)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var entry = int.TryParse(record.EntryId.ToString(), out var id)
|
||||
? await GetEntryAndProcessAsync(id, result)
|
||||
: await GetEntryAndProcessAsync(record.EntryId.ToString(), result);
|
||||
|
||||
if (entry == null || entry.RootFolderType is FolderType.TRASH or FolderType.Archive)
|
||||
{
|
||||
result.Status = Status.Invalid;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (status == Status.RequiredPassword)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result.Access = record.Share;
|
||||
result.TenantId = record.TenantId;
|
||||
|
||||
if (_securityContext.IsAuthenticated || !string.IsNullOrEmpty(_externalShare.GetAnonymousSessionKey()))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
await _externalShare.SetAnonymousSessionKeyAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<FileEntry> GetEntryAndProcessAsync<T>(T id, ValidationInfo info)
|
||||
{
|
||||
var folder = await _daoFactory.GetFolderDao<T>().GetFolderAsync(id);
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
info.Id = folder.Id.ToString();
|
||||
info.Title = folder.Title;
|
||||
info.FolderType = folder.FolderType;
|
||||
|
||||
var logo = await _roomLogoManager.GetLogoAsync(folder);
|
||||
|
||||
if (!logo.IsDefault())
|
||||
{
|
||||
info.Logo = logo;
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@ public class FilesLinkUtility
|
||||
private readonly CoreSettings _coreSettings;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly InstanceCrypto _instanceCrypto;
|
||||
private readonly ExternalShare _externalShare;
|
||||
|
||||
public FilesLinkUtility(
|
||||
CommonLinkUtility commonLinkUtility,
|
||||
@ -46,7 +47,8 @@ public class FilesLinkUtility
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
InstanceCrypto instanceCrypto)
|
||||
InstanceCrypto instanceCrypto,
|
||||
ExternalShare externalShare)
|
||||
{
|
||||
_commonLinkUtility = commonLinkUtility;
|
||||
_baseCommonLinkUtility = baseCommonLinkUtility;
|
||||
@ -54,6 +56,7 @@ public class FilesLinkUtility
|
||||
_coreSettings = coreSettings;
|
||||
_configuration = configuration;
|
||||
_instanceCrypto = instanceCrypto;
|
||||
_externalShare = externalShare;
|
||||
_filesUploaderURL = _configuration["files:uploader:url"] ?? "~";
|
||||
}
|
||||
|
||||
@ -75,6 +78,7 @@ public class FilesLinkUtility
|
||||
public const string AuthKey = "stream_auth";
|
||||
public const string Anchor = "anchor";
|
||||
public const string Size = "size";
|
||||
public const string FolderShareKey = "share";
|
||||
|
||||
public string FileHandlerPath
|
||||
{
|
||||
@ -262,14 +266,18 @@ public class FilesLinkUtility
|
||||
|
||||
public string GetFileDownloadUrl(object fileId, int fileVersion, string convertToExtension)
|
||||
{
|
||||
return string.Format(FileDownloadUrlString, HttpUtility.UrlEncode(fileId.ToString()))
|
||||
var url = string.Format(FileDownloadUrlString, HttpUtility.UrlEncode(fileId.ToString()))
|
||||
+ (fileVersion > 0 ? "&" + Version + "=" + fileVersion : string.Empty)
|
||||
+ (string.IsNullOrEmpty(convertToExtension) ? string.Empty : "&" + OutType + "=" + convertToExtension);
|
||||
|
||||
return GetUrlWithShare(url);
|
||||
}
|
||||
|
||||
public string GetFileWebMediaViewUrl(object fileId)
|
||||
{
|
||||
return FilesBaseAbsolutePath + "#preview/" + HttpUtility.UrlEncode(fileId.ToString());
|
||||
var url = FilesBaseAbsolutePath + "#preview/" + HttpUtility.UrlEncode(fileId.ToString());
|
||||
|
||||
return GetUrlWithShare(url);
|
||||
}
|
||||
|
||||
public string FileWebViewerUrlString
|
||||
@ -289,8 +297,10 @@ public class FilesLinkUtility
|
||||
|
||||
public string GetFileWebEditorUrl<T>(T fileId, int fileVersion = 0)
|
||||
{
|
||||
return string.Format(FileWebEditorUrlString, HttpUtility.UrlEncode(fileId.ToString()))
|
||||
var url = string.Format(FileWebEditorUrlString, HttpUtility.UrlEncode(fileId.ToString()))
|
||||
+ (fileVersion > 0 ? "&" + Version + "=" + fileVersion : string.Empty);
|
||||
|
||||
return GetUrlWithShare(url);
|
||||
}
|
||||
|
||||
public string GetFileWebEditorTryUrl(FileType fileType)
|
||||
@ -330,7 +340,8 @@ public class FilesLinkUtility
|
||||
{
|
||||
if (fileUtility.ExtsMustConvert.Contains(FileUtility.GetFileExtension(fileTitle)))
|
||||
{
|
||||
return string.Format(FileWebViewerUrlString, HttpUtility.UrlEncode(fileId.ToString()));
|
||||
var url = string.Format(FileWebViewerUrlString, HttpUtility.UrlEncode(fileId.ToString()));
|
||||
return GetUrlWithShare(url);
|
||||
}
|
||||
|
||||
return GetFileWebEditorUrl(fileId, fileVersion);
|
||||
@ -356,8 +367,10 @@ public class FilesLinkUtility
|
||||
|
||||
public string GetFileThumbnailUrl(object fileId, int fileVersion)
|
||||
{
|
||||
return string.Format(FileThumbnailUrlString, HttpUtility.UrlEncode(fileId.ToString()))
|
||||
var url = string.Format(FileThumbnailUrlString, HttpUtility.UrlEncode(fileId.ToString()))
|
||||
+ (fileVersion > 0 ? "&" + Version + "=" + fileVersion : string.Empty);
|
||||
|
||||
return GetUrlWithShare(url);
|
||||
}
|
||||
|
||||
|
||||
@ -438,4 +451,21 @@ public class FilesLinkUtility
|
||||
{
|
||||
return "DocKey_" + key;
|
||||
}
|
||||
|
||||
private string GetUrlWithShare(string url)
|
||||
{
|
||||
if (_externalShare.GetLinkId() == default)
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
var key = _externalShare.GetKey();
|
||||
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
url += $"&{FolderShareKey}={key}";
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ public class PathProvider
|
||||
return uriBuilder.Uri + "?" + query;
|
||||
}
|
||||
|
||||
public string GetFileStreamUrl<T>(File<T> file, string doc = null, bool lastVersion = false)
|
||||
public string GetFileStreamUrl<T>(File<T> file, string key = null, string keyName = null, bool lastVersion = false)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
@ -162,15 +162,13 @@ public class PathProvider
|
||||
}
|
||||
|
||||
query += FilesLinkUtility.AuthKey + "=" + _emailValidationKeyProvider.GetEmailKey(file.Id.ToString() + version);
|
||||
if (!string.IsNullOrEmpty(doc))
|
||||
{
|
||||
query += "&" + FilesLinkUtility.DocShareKey + "=" + HttpUtility.UrlEncode(doc);
|
||||
}
|
||||
|
||||
query += GetExternalShareKey(keyName, key);
|
||||
|
||||
return uriBuilder.Uri + "?" + query;
|
||||
}
|
||||
|
||||
public async Task<string> GetFileChangesUrlAsync<T>(File<T> file, string doc = null)
|
||||
public async Task<string> GetFileChangesUrlAsync<T>(File<T> file, string key = null, string keyName = null)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
@ -183,10 +181,8 @@ public class PathProvider
|
||||
query += $"{FilesLinkUtility.FileId}={HttpUtility.UrlEncode(file.Id.ToString())}&";
|
||||
query += $"{FilesLinkUtility.Version}={file.Version}&";
|
||||
query += $"{FilesLinkUtility.AuthKey}={await _emailValidationKeyProvider.GetEmailKeyAsync(file.Id + file.Version.ToString(CultureInfo.InvariantCulture))}";
|
||||
if (!string.IsNullOrEmpty(doc))
|
||||
{
|
||||
query += $"&{FilesLinkUtility.DocShareKey}={HttpUtility.UrlEncode(doc)}";
|
||||
}
|
||||
|
||||
query += GetExternalShareKey(keyName, key);
|
||||
|
||||
return $"{uriBuilder.Uri}?{query}";
|
||||
}
|
||||
@ -229,4 +225,19 @@ public class PathProvider
|
||||
|
||||
return $"{uriBuilder.Uri}?{query}";
|
||||
}
|
||||
|
||||
private static string GetExternalShareKey(string keyName, string keyValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(keyName))
|
||||
{
|
||||
return "&" + keyName + '=' + HttpUtility.UrlEncode(keyValue);
|
||||
}
|
||||
|
||||
return "&" + FilesLinkUtility.DocShareKey + '=' + HttpUtility.UrlEncode(keyValue);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ using SixLabors.ImageSharp.Processing;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using JsonException = System.Text.Json.JsonException;
|
||||
using MimeMapping = ASC.Common.Web.MimeMapping;
|
||||
using Status = ASC.Files.Core.Security.Status;
|
||||
|
||||
namespace ASC.Web.Files;
|
||||
|
||||
@ -82,6 +83,8 @@ public class FileHandlerService
|
||||
private readonly SocketManager _socketManager;
|
||||
private readonly ILogger<FileHandlerService> _logger;
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
private readonly ExternalLinkHelper _externalLinkHelper;
|
||||
private readonly ExternalShare _externalShare;
|
||||
|
||||
public FileHandlerService(
|
||||
FilesLinkUtility filesLinkUtility,
|
||||
@ -112,7 +115,9 @@ public class FileHandlerService
|
||||
CompressToArchive compressToArchive,
|
||||
InstanceCrypto instanceCrypto,
|
||||
IHttpClientFactory clientFactory,
|
||||
ThumbnailSettings thumbnailSettings)
|
||||
ThumbnailSettings thumbnailSettings,
|
||||
ExternalLinkHelper externalLinkHelper,
|
||||
ExternalShare externalShare)
|
||||
{
|
||||
_filesLinkUtility = filesLinkUtility;
|
||||
_tenantExtra = tenantExtra;
|
||||
@ -143,6 +148,8 @@ public class FileHandlerService
|
||||
_logger = logger;
|
||||
_clientFactory = clientFactory;
|
||||
_thumbnailSettings = thumbnailSettings;
|
||||
_externalLinkHelper = externalLinkHelper;
|
||||
_externalShare = externalShare;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
@ -207,14 +214,15 @@ public class FileHandlerService
|
||||
|
||||
private async ValueTask BulkDownloadFile(HttpContext context)
|
||||
{
|
||||
if (!_securityContext.IsAuthenticated)
|
||||
var filename = context.Request.Query["filename"].FirstOrDefault();
|
||||
var sessionKey = context.Request.Query["session"].FirstOrDefault();
|
||||
|
||||
if (!_securityContext.IsAuthenticated && string.IsNullOrEmpty(sessionKey))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
}
|
||||
|
||||
var filename = context.Request.Query["filename"].FirstOrDefault();
|
||||
|
||||
if (String.IsNullOrEmpty(filename))
|
||||
{
|
||||
var ext = _compressToArchive.GetExt(context.Request.Query["ext"]);
|
||||
@ -225,7 +233,29 @@ public class FileHandlerService
|
||||
filename = _instanceCrypto.Decrypt(Uri.UnescapeDataString(filename));
|
||||
}
|
||||
|
||||
var path = string.Format(@"{0}\{1}", _securityContext.CurrentAccount.ID, filename);
|
||||
string path;
|
||||
|
||||
if (!string.IsNullOrEmpty(sessionKey))
|
||||
{
|
||||
var session = await _externalShare.ParseDownloadSessionKeyAsync(sessionKey);
|
||||
var sessionId = await _externalShare.GetSessionIdAsync();
|
||||
|
||||
if (session != null && sessionId != default && session.Id == sessionId &&
|
||||
(await _externalShare.ValidateAsync(session.LinkId)) == Status.Ok)
|
||||
{
|
||||
path = string.Format(@"{0}\{1}\{2}", session.LinkId, session.Id, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
path = string.Format(@"{0}\{1}", _securityContext.CurrentAccount.ID, filename);
|
||||
}
|
||||
|
||||
var store = await _globalStore.GetStoreAsync();
|
||||
|
||||
if (!await store.IsFileAsync(FileConstant.StorageDomainTmp, path))
|
||||
@ -237,7 +267,9 @@ public class FileHandlerService
|
||||
|
||||
if (store.IsSupportedPreSignedUri)
|
||||
{
|
||||
var tmp = await store.GetPreSignedUriAsync(FileConstant.StorageDomainTmp, path, TimeSpan.FromHours(1), null);
|
||||
var headers = _securityContext.IsAuthenticated ? null : new[] { SecureHelper.GenerateSecureKeyHeader(path, _emailValidationKeyProvider) };
|
||||
|
||||
var tmp = await store.GetPreSignedUriAsync(FileConstant.StorageDomainTmp, path, TimeSpan.FromHours(1), headers);
|
||||
var url = tmp.ToString();
|
||||
context.Response.Redirect(HttpUtility.UrlPathEncode(url));
|
||||
return;
|
||||
@ -315,8 +347,13 @@ public class FileHandlerService
|
||||
|
||||
if (!readLink && !await _fileSecurity.CanDownloadAsync(file))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
var linkId = await _externalShare.GetLinkIdAsync();
|
||||
|
||||
if (!(_fileUtility.CanImageView(file.Title) || _fileUtility.CanMediaView(file.Title)) || linkId == default || !await _fileSecurity.CanReadAsync(file, linkId))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (readLink && (linkShare == FileShare.Comment || linkShare == FileShare.Read) && file.DenyDownload)
|
||||
@ -620,10 +657,34 @@ public class FileHandlerService
|
||||
version = 0;
|
||||
}
|
||||
var doc = context.Request.Query[FilesLinkUtility.DocShareKey];
|
||||
var share = context.Request.Query[FilesLinkUtility.FolderShareKey];
|
||||
|
||||
await fileDao.InvalidateCacheAsync(id);
|
||||
|
||||
var (linkRight, file) = await _fileShareLink.CheckAsync(doc, fileDao);
|
||||
var linkRight = FileShare.Restrict;
|
||||
File<T> file = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(share))
|
||||
{
|
||||
var result = await _externalLinkHelper.ValidateAsync(share);
|
||||
|
||||
if (result.Access != FileShare.Restrict)
|
||||
{
|
||||
file = version > 0
|
||||
? await fileDao.GetFileAsync(id, version)
|
||||
: await fileDao.GetFileAsync(id);
|
||||
|
||||
if (file != null && await _fileSecurity.CanDownloadAsync(file))
|
||||
{
|
||||
linkRight = result.Access;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(doc))
|
||||
{
|
||||
(linkRight, file) = await _fileShareLink.CheckAsync(doc, fileDao);
|
||||
}
|
||||
|
||||
if (linkRight == FileShare.Restrict && !_securityContext.IsAuthenticated)
|
||||
{
|
||||
var auth = context.Request.Query[FilesLinkUtility.AuthKey];
|
||||
|
@ -27,8 +27,15 @@
|
||||
namespace ASC.Files.Core.Mapping;
|
||||
|
||||
[Scope]
|
||||
public class FolderMappingAction : IMappingAction<DbFolderQuery, Folder<int>>
|
||||
public class FilesMappingAction : IMappingAction<DbFolderQuery, Folder<int>>, IMappingAction<FileShareRecord, DbFilesSecurity>
|
||||
{
|
||||
private readonly TenantUtil _tenantUtil;
|
||||
|
||||
public FilesMappingAction(TenantUtil tenantUtil)
|
||||
{
|
||||
_tenantUtil = tenantUtil;
|
||||
}
|
||||
|
||||
public void Process(DbFolderQuery source, Folder<int> destination, ResolutionContext context)
|
||||
{
|
||||
switch (destination.FolderType)
|
||||
@ -93,4 +100,14 @@ public class FolderMappingAction : IMappingAction<DbFolderQuery, Folder<int>>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(FileShareRecord source, DbFilesSecurity destination, ResolutionContext context)
|
||||
{
|
||||
if (source.Options == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
source.Options.ExpirationDate = _tenantUtil.DateTimeToUtc(source.Options.ExpirationDate);
|
||||
}
|
||||
}
|
@ -46,7 +46,18 @@ public class FilesMappingProfile : AutoMapper.Profile
|
||||
.IncludeMembers(r => r.Folder)
|
||||
.ForMember(r => r.CreateOn, r => r.ConvertUsing<TenantDateTimeConverter, DateTime>(s => s.Folder.CreateOn))
|
||||
.ForMember(r => r.ModifiedOn, r => r.ConvertUsing<TenantDateTimeConverter, DateTime>(s => s.Folder.ModifiedOn))
|
||||
.AfterMap<FolderMappingAction>()
|
||||
.AfterMap<FilesMappingAction>()
|
||||
.ConstructUsingServiceLocator();
|
||||
|
||||
CreateMap<FileShareRecord, DbFilesSecurity>()
|
||||
.ForMember(dest => dest.TimeStamp, cfg =>
|
||||
cfg.MapFrom(_ => DateTime.UtcNow))
|
||||
.ForMember(dest => dest.Options, cfg =>
|
||||
cfg.MapFrom(src => src.Options != null ? JsonSerializer.Serialize(src.Options, JsonSerializerOptions.Default) : null))
|
||||
.BeforeMap<FilesMappingAction>();
|
||||
|
||||
CreateMap<DbFilesSecurity, FileShareRecord>()
|
||||
.ForMember(dest => dest.Options, cfg =>
|
||||
cfg.MapFrom(src => JsonSerializer.Deserialize<FileShareOptions>(src.Options, JsonSerializerOptions.Default)));
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -475,4 +475,13 @@ Highest compatibility with docx, xlsx, pptx.</value>
|
||||
<data name="ErrorMessage_SecurityException_ArchiveRoom" xml:space="preserve">
|
||||
<value>You don't have enough permission to archive the room</value>
|
||||
</data>
|
||||
<data name="ErrorMessage_MaxLinksCount" xml:space="preserve">
|
||||
<value>The maximum number of links may not exceed {0}</value>
|
||||
</data>
|
||||
<data name="DefaultExternalLinkTitle" xml:space="preserve">
|
||||
<value>External link</value>
|
||||
</data>
|
||||
<data name="ErrorMessage_SharePasswordManyAttempts" xml:space="preserve">
|
||||
<value>You have tried too many times to enter your password. Please try again later</value>
|
||||
</data>
|
||||
</root>
|
@ -34,12 +34,12 @@ public enum EditorType
|
||||
Embedded,
|
||||
External,
|
||||
}
|
||||
|
||||
|
||||
public class ActionLinkConfig
|
||||
{
|
||||
[JsonPropertyName("action")]
|
||||
public ActionConfig Action { get; set; }
|
||||
|
||||
|
||||
public static string Serialize(ActionLinkConfig actionLinkConfig)
|
||||
{
|
||||
return JsonSerializer.Serialize(actionLinkConfig);
|
||||
@ -74,9 +74,9 @@ public class Configuration<T>
|
||||
{ FileType.Spreadsheet, "cell" },
|
||||
{ FileType.Presentation, "slide" }
|
||||
};
|
||||
|
||||
|
||||
private FileType _fileTypeCache = FileType.Unknown;
|
||||
|
||||
|
||||
public DocumentConfig<T> Document { get; set; }
|
||||
|
||||
public string DocumentType
|
||||
@ -139,7 +139,7 @@ public class Configuration<T>
|
||||
}
|
||||
}
|
||||
|
||||
#region Nested Classes
|
||||
#region Nested Classes
|
||||
|
||||
[Transient]
|
||||
public class DocumentConfig<T>
|
||||
@ -162,6 +162,7 @@ public class DocumentConfig<T>
|
||||
}
|
||||
|
||||
public PermissionsConfig Permissions { get; set; }
|
||||
public string SharedLinkParam { get; set; }
|
||||
public string SharedLinkKey { get; set; }
|
||||
public FileReferenceData<T> ReferenceData
|
||||
{
|
||||
@ -179,7 +180,7 @@ public class DocumentConfig<T>
|
||||
return _referenceData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string Title
|
||||
{
|
||||
set => _title = value;
|
||||
@ -197,7 +198,7 @@ public class DocumentConfig<T>
|
||||
}
|
||||
|
||||
var last = Permissions.Edit || Permissions.Review || Permissions.Comment;
|
||||
_fileUri = _documentServiceConnector.ReplaceCommunityAdress(_pathProvider.GetFileStreamUrl(Info.GetFile(), SharedLinkKey, last));
|
||||
_fileUri = _documentServiceConnector.ReplaceCommunityAdress(_pathProvider.GetFileStreamUrl(Info.GetFile(), SharedLinkKey, SharedLinkParam, last));
|
||||
|
||||
return _fileUri;
|
||||
}
|
||||
@ -285,7 +286,7 @@ public class EditorConfiguration<T>
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (!_authContext.IsAuthenticated || _userManager.IsUser(_authContext.CurrentAccount.ID))
|
||||
{
|
||||
return null;
|
||||
@ -498,9 +499,9 @@ public class EditorConfiguration<T>
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Configuration<T>.DocType.TryGetValue(fileType, out var documentType);
|
||||
|
||||
|
||||
return _baseCommonLinkUtility.GetFullAbsolutePath(_filesLinkUtility.FileHandlerPath)
|
||||
+ "?" + FilesLinkUtility.Action + "=create"
|
||||
+ "&doctype=" + documentType
|
||||
@ -532,13 +533,13 @@ public class InfoConfig<T>
|
||||
if (!_securityContext.IsAuthenticated || _userManager.IsUser(_securityContext.CurrentAccount.ID))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_file.ParentId == null || _file.Encrypted)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return _file.IsFavorite;
|
||||
}
|
||||
set
|
||||
@ -560,11 +561,11 @@ public class InfoConfig<T>
|
||||
if (string.IsNullOrEmpty(_breadCrumbs))
|
||||
{
|
||||
const string crumbsSeporator = " \\ ";
|
||||
|
||||
|
||||
var breadCrumbsList = _breadCrumbsManager.GetBreadCrumbsAsync(_file.ParentId).Result;
|
||||
_breadCrumbs = string.Join(crumbsSeporator, breadCrumbsList.Select(folder => folder.Title).ToArray());
|
||||
}
|
||||
|
||||
|
||||
return _breadCrumbs;
|
||||
}
|
||||
}
|
||||
@ -581,7 +582,7 @@ public class InfoConfig<T>
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
return _fileSharing.GetSharedInfoShortFileAsync(_file.Id).Result;
|
||||
@ -609,13 +610,13 @@ public class InfoConfig<T>
|
||||
{
|
||||
return _file;
|
||||
}
|
||||
|
||||
|
||||
public void SetFile(File<T> file)
|
||||
{
|
||||
_file = file;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class PermissionsConfig
|
||||
{
|
||||
public bool ChangeHistory { get; set; }
|
||||
@ -645,7 +646,7 @@ public class FileReferenceData<T>
|
||||
public string InstanceId { get; set; }
|
||||
}
|
||||
|
||||
#endregion Nested Classes
|
||||
#endregion Nested Classes
|
||||
|
||||
[Transient]
|
||||
public class CustomerConfig<T>
|
||||
@ -926,7 +927,7 @@ public class EmbeddedConfig
|
||||
+ FilesLinkUtility.EditorPage + "?" + FilesLinkUtility.Action + "=view" + ShareLinkParam);
|
||||
|
||||
public string ToolbarDocked => "top";
|
||||
|
||||
|
||||
public EmbeddedConfig(BaseCommonLinkUtility baseCommonLinkUtility, FilesLinkUtility filesLinkUtility)
|
||||
{
|
||||
_baseCommonLinkUtility = baseCommonLinkUtility;
|
||||
|
@ -43,6 +43,7 @@ public class DocumentServiceHelper
|
||||
private readonly FileTrackerHelper _fileTracker;
|
||||
private readonly EntryStatusManager _entryStatusManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ExternalShare _externalShare;
|
||||
|
||||
public DocumentServiceHelper(
|
||||
IDaoFactory daoFactory,
|
||||
@ -58,7 +59,8 @@ public class DocumentServiceHelper
|
||||
LockerManager lockerManager,
|
||||
FileTrackerHelper fileTracker,
|
||||
EntryStatusManager entryStatusManager,
|
||||
IServiceProvider serviceProvider)
|
||||
IServiceProvider serviceProvider,
|
||||
ExternalShare externalShare)
|
||||
{
|
||||
_daoFactory = daoFactory;
|
||||
_fileShareLink = fileShareLink;
|
||||
@ -74,6 +76,7 @@ public class DocumentServiceHelper
|
||||
_fileTracker = fileTracker;
|
||||
_entryStatusManager = entryStatusManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
_externalShare = externalShare;
|
||||
}
|
||||
|
||||
public async Task<(File<T> File, Configuration<T> Configuration, bool LocatedInPrivateRoom)> GetParamsAsync<T>(T fileId, int version, string doc, bool editPossible, bool tryEdit, bool tryCoauth)
|
||||
@ -324,24 +327,27 @@ public class DocumentServiceHelper
|
||||
configuration.Document.IsLinkedForMe = !string.IsNullOrEmpty(sourceId);
|
||||
}
|
||||
|
||||
if (await _externalShare.GetLinkIdAsync() == default)
|
||||
{
|
||||
return (file, configuration, locatedInPrivateRoom);
|
||||
}
|
||||
|
||||
configuration.Document.SharedLinkParam = FilesLinkUtility.FolderShareKey;
|
||||
configuration.Document.SharedLinkKey = _externalShare.GetKey();
|
||||
|
||||
return (file, configuration, locatedInPrivateRoom);
|
||||
}
|
||||
|
||||
private async Task<bool> CanDownloadAsync<T>(FileSecurity fileSecurity, File<T> file, FileShare linkRight)
|
||||
{
|
||||
if (!file.DenyDownload)
|
||||
var canDownload = linkRight != FileShare.Restrict && linkRight != FileShare.Read && linkRight != FileShare.Comment;
|
||||
|
||||
if (canDownload)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var canDownload = linkRight != FileShare.Restrict && linkRight != FileShare.Read && linkRight != FileShare.Comment;
|
||||
|
||||
if (canDownload || _authContext.CurrentAccount.ID.Equals(ASC.Core.Configuration.Constants.Guest.ID))
|
||||
{
|
||||
return canDownload;
|
||||
}
|
||||
|
||||
if (linkRight == FileShare.Read || linkRight == FileShare.Comment)
|
||||
if (linkRight is FileShare.Read or FileShare.Comment)
|
||||
{
|
||||
var fileDao = _daoFactory.GetFileDao<T>();
|
||||
file = await fileDao.GetFileAsync(file.Id); // reset Access prop
|
||||
@ -457,4 +463,4 @@ public class DocumentServiceHelper
|
||||
|
||||
return await _documentServiceConnector.CommandAsync(CommandMethod.Meta, docKeyForTrack, file.Id, meta: meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ internal class FileDeleteOperationData<T> : FileOperationData<T>
|
||||
public IDictionary<string, StringValues> Headers { get; }
|
||||
public bool IsEmptyTrash { get; }
|
||||
|
||||
public FileDeleteOperationData(IEnumerable<T> folders, IEnumerable<T> files, Tenant tenant,
|
||||
bool holdResult = true, bool ignoreException = false, bool immediately = false, IDictionary<string, StringValues> headers = null, bool isEmptyTrash = false)
|
||||
: base(folders, files, tenant, holdResult)
|
||||
public FileDeleteOperationData(IEnumerable<T> folders, IEnumerable<T> files, Tenant tenant, ExternalShareData externalShareData, bool holdResult = true,
|
||||
bool ignoreException = false, bool immediately = false, IDictionary<string, StringValues> headers = null, bool isEmptyTrash = false)
|
||||
: base(folders, files, tenant, externalShareData, holdResult)
|
||||
{
|
||||
IgnoreException = ignoreException;
|
||||
Immediately = immediately;
|
||||
@ -81,6 +81,10 @@ class FileDeleteOperation<T> : FileOperation<FileDeleteOperationData<T>, T>
|
||||
var filesMessageService = scope.ServiceProvider.GetService<FilesMessageService>();
|
||||
var tenantManager = scope.ServiceProvider.GetService<TenantManager>();
|
||||
tenantManager.SetCurrentTenant(CurrentTenant);
|
||||
|
||||
var externalShare = scope.ServiceProvider.GetRequiredService<ExternalShare>();
|
||||
externalShare.SetCurrentShareData(CurrentShareData);
|
||||
|
||||
_trashId = await folderDao.GetFolderIDTrashAsync(true);
|
||||
|
||||
Folder<T> root = null;
|
||||
|
@ -31,8 +31,9 @@ internal class FileDownloadOperationData<T> : FileOperationData<T>
|
||||
public Dictionary<T, string> FilesDownload { get; }
|
||||
public IDictionary<string, StringValues> Headers { get; }
|
||||
|
||||
public FileDownloadOperationData(Dictionary<T, string> folders, Dictionary<T, string> files, Tenant tenant, IDictionary<string, StringValues> headers, bool holdResult = true)
|
||||
: base(folders.Select(f => f.Key).ToList(), files.Select(f => f.Key).ToList(), tenant, holdResult)
|
||||
public FileDownloadOperationData(Dictionary<T, string> folders, Dictionary<T, string> files, Tenant tenant, IDictionary<string, StringValues> headers,
|
||||
ExternalShareData externalShareData, bool holdResult = true)
|
||||
: base(folders.Select(f => f.Key).ToList(), files.Select(f => f.Key).ToList(), tenant, externalShareData, holdResult)
|
||||
{
|
||||
FilesDownload = files;
|
||||
Headers = headers;
|
||||
@ -59,6 +60,7 @@ class FileDownloadOperation : ComposeFileOperation<FileDownloadOperationData<str
|
||||
var tenantManager = scope.ServiceProvider.GetRequiredService<TenantManager>();
|
||||
var instanceCrypto = scope.ServiceProvider.GetRequiredService<InstanceCrypto>();
|
||||
var daoFactory = scope.ServiceProvider.GetRequiredService<IDaoFactory>();
|
||||
var externalShare = scope.ServiceProvider.GetRequiredService<ExternalShare>();
|
||||
var scopeClass = scope.ServiceProvider.GetService<FileDownloadOperationScope>();
|
||||
var (globalStore, filesLinkUtility, _, _, _, log) = scopeClass;
|
||||
var stream = _tempStream.Create();
|
||||
@ -99,7 +101,28 @@ class FileDownloadOperation : ComposeFileOperation<FileDownloadOperationData<str
|
||||
}
|
||||
|
||||
var store = await globalStore.GetStoreAsync();
|
||||
var path = string.Format(@"{0}\{1}", ((IAccount)_principal.Identity).ID, fileName);
|
||||
string path;
|
||||
string sessionKey = null;
|
||||
|
||||
var isAuthenticated = _principal.Identity is IAccount;
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
path = string.Format(@"{0}\{1}", ((IAccount)_principal.Identity).ID, fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sessionId = await externalShare.GetSessionIdAsync();
|
||||
var linkId = await externalShare.GetLinkIdAsync();
|
||||
|
||||
if (sessionId == default || linkId == default)
|
||||
{
|
||||
throw new SecurityException(FilesCommonResource.ErrorMassage_SecurityException);
|
||||
}
|
||||
|
||||
path = string.Format(@"{0}\{1}\{2}", linkId, sessionId, fileName);
|
||||
sessionKey = await externalShare.CreateDownloadSessionKeyAsync();
|
||||
}
|
||||
|
||||
if (await store.IsFileAsync(FileConstant.StorageDomainTmp, path))
|
||||
{
|
||||
@ -112,13 +135,17 @@ class FileDownloadOperation : ComposeFileOperation<FileDownloadOperationData<str
|
||||
stream,
|
||||
MimeMapping.GetMimeMapping(path),
|
||||
"attachment; filename=\"" + Uri.EscapeDataString(fileName) + "\"");
|
||||
|
||||
|
||||
this[Res] = $"{filesLinkUtility.FileHandlerPath}?{FilesLinkUtility.Action}=bulk&filename={Uri.EscapeDataString(instanceCrypto.Encrypt(fileName))}";
|
||||
|
||||
if (!isAuthenticated)
|
||||
{
|
||||
this[Res] += $"&session={HttpUtility.UrlEncode(sessionKey)}";
|
||||
}
|
||||
}
|
||||
|
||||
this[Finish] = true;
|
||||
PublishChanges();
|
||||
|
||||
}
|
||||
|
||||
public override void PublishChanges(DistributedTask task)
|
||||
|
@ -30,7 +30,8 @@ class FileMarkAsReadOperationData<T> : FileOperationData<T>
|
||||
{
|
||||
public IDictionary<string, StringValues> Headers { get; }
|
||||
|
||||
public FileMarkAsReadOperationData(IEnumerable<T> folders, IEnumerable<T> files, Tenant tenant, IDictionary<string, StringValues> headers, bool holdResult = true) : base(folders, files, tenant, holdResult)
|
||||
public FileMarkAsReadOperationData(IEnumerable<T> folders, IEnumerable<T> files, Tenant tenant, IDictionary<string, StringValues> headers, ExternalShareData externalShareData,
|
||||
bool holdResult = true) : base(folders, files, tenant, externalShareData, holdResult)
|
||||
{
|
||||
Headers = headers;
|
||||
}
|
||||
@ -67,17 +68,17 @@ class FileMarkAsReadOperation<T> : FileOperation<FileMarkAsReadOperationData<T>,
|
||||
var scopeClass = scope.ServiceProvider.GetService<FileMarkAsReadOperationScope>();
|
||||
var filesMessageService = scope.ServiceProvider.GetRequiredService<FilesMessageService>();
|
||||
var (fileMarker, globalFolder, daoFactory, settingsManager) = scopeClass;
|
||||
var entries = Enumerable.Empty<FileEntry<T>>();
|
||||
var entries = Enumerable.Empty<FileEntry<T>>();
|
||||
if (Folders.Count > 0)
|
||||
{
|
||||
entries = entries.Concat(await FolderDao.GetFoldersAsync(Folders).ToListAsync());
|
||||
entries = entries.Concat(await FolderDao.GetFoldersAsync(Folders).ToListAsync());
|
||||
}
|
||||
if (Files.Count > 0)
|
||||
{
|
||||
entries = entries.Concat(await FileDao.GetFilesAsync(Files).ToListAsync());
|
||||
entries = entries.Concat(await FileDao.GetFilesAsync(Files).ToListAsync());
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@ -95,7 +96,7 @@ class FileMarkAsReadOperation<T> : FileOperation<FileMarkAsReadOperationData<T>,
|
||||
}
|
||||
|
||||
ProgressStep();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var rootIds = new List<int>
|
||||
@ -155,4 +156,4 @@ public class FileMarkAsReadOperationScope
|
||||
daoFactory = _daoFactory;
|
||||
settingsManager = _settingsManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +46,9 @@ internal class FileMoveCopyOperationData<T> : FileOperationData<T>
|
||||
public FileConflictResolveType ResolveType { get; }
|
||||
public IDictionary<string, StringValues> Headers { get; }
|
||||
|
||||
public FileMoveCopyOperationData(IEnumerable<T> folders, IEnumerable<T> files, Tenant tenant, JsonElement toFolderId, bool copy, FileConflictResolveType resolveType, bool holdResult = true, IDictionary<string, StringValues> headers = null)
|
||||
: base(folders, files, tenant, holdResult)
|
||||
public FileMoveCopyOperationData(IEnumerable<T> folders, IEnumerable<T> files, Tenant tenant, JsonElement toFolderId, bool copy, FileConflictResolveType resolveType,
|
||||
ExternalShareData externalShareData, bool holdResult = true, IDictionary<string, StringValues> headers = null)
|
||||
: base(folders, files, tenant, externalShareData, holdResult)
|
||||
{
|
||||
if (toFolderId.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
|
@ -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 IAccount = ASC.Common.Security.Authentication.IAccount;
|
||||
|
||||
namespace ASC.Web.Files.Services.WCFService.FileOperations;
|
||||
|
||||
public abstract class FileOperation : DistributedTaskProgress
|
||||
@ -49,7 +51,16 @@ public abstract class FileOperation : DistributedTaskProgress
|
||||
_principal = serviceProvider.GetService<IHttpContextAccessor>()?.HttpContext?.User ?? CustomSynchronizationContext.CurrentContext.CurrentPrincipal;
|
||||
_culture = CultureInfo.CurrentCulture.Name;
|
||||
|
||||
this[Owner] = ((IAccount)(_principal ?? CustomSynchronizationContext.CurrentContext.CurrentPrincipal).Identity).ID.ToString();
|
||||
if ((_principal ?? CustomSynchronizationContext.CurrentContext.CurrentPrincipal)?.Identity is IAccount account)
|
||||
{
|
||||
this[Owner] = account.ID.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
var externalShare = serviceProvider.GetRequiredService<ExternalShare>();
|
||||
this[Owner] = externalShare.GetSessionId().ToString();
|
||||
}
|
||||
|
||||
this[Src] = _props.ContainsValue(Src) ? this[Src] : "";
|
||||
this[Progress] = 0;
|
||||
this[Res] = "";
|
||||
@ -180,13 +191,15 @@ abstract class FileOperationData<T>
|
||||
public List<T> Folders { get; private set; }
|
||||
public List<T> Files { get; private set; }
|
||||
public Tenant Tenant { get; }
|
||||
public ExternalShareData ExternalShareData { get; }
|
||||
public bool HoldResult { get; set; }
|
||||
|
||||
protected FileOperationData(IEnumerable<T> folders, IEnumerable<T> files, Tenant tenant, bool holdResult = true)
|
||||
protected FileOperationData(IEnumerable<T> folders, IEnumerable<T> files, Tenant tenant, ExternalShareData externalShareData, bool holdResult = true)
|
||||
{
|
||||
Folders = folders?.ToList() ?? new List<T>();
|
||||
Files = files?.ToList() ?? new List<T>();
|
||||
Tenant = tenant;
|
||||
ExternalShareData = externalShareData;
|
||||
HoldResult = holdResult;
|
||||
}
|
||||
}
|
||||
@ -204,6 +217,7 @@ abstract class FileOperation<T, TId> : FileOperation where T : FileOperationData
|
||||
protected CancellationToken CancellationToken { get; private set; }
|
||||
protected internal List<TId> Folders { get; private set; }
|
||||
protected internal List<TId> Files { get; private set; }
|
||||
protected ExternalShareData CurrentShareData { get; private set; }
|
||||
|
||||
protected IServiceProvider _serviceProvider;
|
||||
|
||||
@ -214,11 +228,15 @@ abstract class FileOperation<T, TId> : FileOperation where T : FileOperationData
|
||||
Folders = fileOperationData.Folders;
|
||||
this[Hold] = fileOperationData.HoldResult;
|
||||
CurrentTenant = fileOperationData.Tenant;
|
||||
CurrentShareData = fileOperationData.ExternalShareData;
|
||||
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var tenantManager = scope.ServiceProvider.GetService<TenantManager>();
|
||||
tenantManager.SetCurrentTenant(CurrentTenant);
|
||||
|
||||
var externalShare = scope.ServiceProvider.GetRequiredService<ExternalShare>();
|
||||
externalShare.SetCurrentShareData(CurrentShareData);
|
||||
|
||||
var daoFactory = scope.ServiceProvider.GetService<IDaoFactory>();
|
||||
FolderDao = daoFactory.GetFolderDao<TId>();
|
||||
|
||||
@ -237,6 +255,9 @@ abstract class FileOperation<T, TId> : FileOperation where T : FileOperationData
|
||||
var (tenantManager, daoFactory, fileSecurity, logger) = scopeClass;
|
||||
tenantManager.SetCurrentTenant(CurrentTenant);
|
||||
|
||||
var externalShare = scope.ServiceProvider.GetRequiredService<ExternalShare>();
|
||||
externalShare.SetCurrentShareData(CurrentShareData);
|
||||
|
||||
CustomSynchronizationContext.CurrentContext.CurrentPrincipal = _principal;
|
||||
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(_culture);
|
||||
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(_culture);
|
||||
@ -281,6 +302,8 @@ abstract class FileOperation<T, TId> : FileOperation where T : FileOperationData
|
||||
var scope = _serviceProvider.CreateAsyncScope();
|
||||
var tenantManager = scope.ServiceProvider.GetService<TenantManager>();
|
||||
tenantManager.SetCurrentTenant(CurrentTenant);
|
||||
var externalShare = scope.ServiceProvider.GetRequiredService<ExternalShare>();
|
||||
externalShare.SetCurrentShareData(CurrentShareData);
|
||||
|
||||
return scope;
|
||||
}
|
||||
@ -359,4 +382,4 @@ public class FileOperationScope
|
||||
fileSecurity = _fileSecurity;
|
||||
optionsMonitor = _options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class FileOperationsManager
|
||||
TempStream tempStream,
|
||||
IDistributedTaskQueueFactory queueFactory,
|
||||
IServiceProvider serviceProvider,
|
||||
ThumbnailSettings thumbnailSettings)
|
||||
ThumbnailSettings thumbnailSettings)
|
||||
{
|
||||
_tasks = queueFactory.CreateQueue(CUSTOM_DISTRIBUTED_TASK_QUEUE_NAME);
|
||||
_tempStream = tempStream;
|
||||
@ -49,6 +49,8 @@ public class FileOperationsManager
|
||||
|
||||
public List<FileOperationResult> GetOperationResults(Guid userId)
|
||||
{
|
||||
userId = ProcessUserId(userId);
|
||||
|
||||
var operations = _tasks.GetAllTasks();
|
||||
var processlist = Process.GetProcesses();
|
||||
|
||||
@ -102,22 +104,24 @@ public class FileOperationsManager
|
||||
}
|
||||
|
||||
|
||||
public List<FileOperationResult> MarkAsRead(Guid userId, Tenant tenant, List<JsonElement> folderIds, List<JsonElement> fileIds, IDictionary<string, StringValues> headers)
|
||||
public List<FileOperationResult> MarkAsRead(Guid userId, Tenant tenant, List<JsonElement> folderIds, List<JsonElement> fileIds, IDictionary<string, StringValues> headers,
|
||||
ExternalShareData externalShareData)
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = GetIds(folderIds);
|
||||
var (fileIntIds, fileStringIds) = GetIds(fileIds);
|
||||
|
||||
var op1 = new FileMarkAsReadOperation<int>(_serviceProvider, new FileMarkAsReadOperationData<int>(folderIntIds, fileIntIds, tenant, headers));
|
||||
var op2 = new FileMarkAsReadOperation<string>(_serviceProvider, new FileMarkAsReadOperationData<string>(folderStringIds, fileStringIds, tenant, headers));
|
||||
var op1 = new FileMarkAsReadOperation<int>(_serviceProvider, new FileMarkAsReadOperationData<int>(folderIntIds, fileIntIds, tenant, headers, externalShareData));
|
||||
var op2 = new FileMarkAsReadOperation<string>(_serviceProvider, new FileMarkAsReadOperationData<string>(folderStringIds, fileStringIds, tenant, headers, externalShareData));
|
||||
var op = new FileMarkAsReadOperation(_serviceProvider, op2, op1);
|
||||
|
||||
return QueueTask(userId, op);
|
||||
}
|
||||
|
||||
public List<FileOperationResult> Download(Guid userId, Tenant tenant, Dictionary<JsonElement, string> folders, Dictionary<JsonElement, string> files, IDictionary<string, StringValues> headers)
|
||||
public List<FileOperationResult> Download(Guid userId, Tenant tenant, Dictionary<JsonElement, string> folders, Dictionary<JsonElement, string> files, IDictionary<string, StringValues> headers,
|
||||
ExternalShareData externalShareData)
|
||||
{
|
||||
var operations = _tasks.GetAllTasks()
|
||||
.Where(t => new Guid(t[FileOperation.Owner]) == userId)
|
||||
.Where(t => new Guid(t[FileOperation.Owner]) == ProcessUserId(userId))
|
||||
.Where(t => (FileOperationType)t[FileOperation.OpType] == FileOperationType.Download);
|
||||
|
||||
if (operations.Any(o => o.Status <= DistributedTaskStatus.Running))
|
||||
@ -128,39 +132,42 @@ public class FileOperationsManager
|
||||
var (folderIntIds, folderStringIds) = GetIds(folders);
|
||||
var (fileIntIds, fileStringIds) = GetIds(files);
|
||||
|
||||
var op1 = new FileDownloadOperation<int>(_serviceProvider, new FileDownloadOperationData<int>(folderIntIds, fileIntIds, tenant, headers));
|
||||
var op2 = new FileDownloadOperation<string>(_serviceProvider, new FileDownloadOperationData<string>(folderStringIds, fileStringIds, tenant, headers));
|
||||
var op1 = new FileDownloadOperation<int>(_serviceProvider, new FileDownloadOperationData<int>(folderIntIds, fileIntIds, tenant, headers, externalShareData));
|
||||
var op2 = new FileDownloadOperation<string>(_serviceProvider, new FileDownloadOperationData<string>(folderStringIds, fileStringIds, tenant, headers, externalShareData));
|
||||
var op = new FileDownloadOperation(_serviceProvider, _tempStream, op2, op1);
|
||||
|
||||
return QueueTask(userId, op);
|
||||
}
|
||||
|
||||
public List<FileOperationResult> MoveOrCopy(Guid userId, Tenant tenant, List<JsonElement> folders, List<JsonElement> files, JsonElement destFolderId, bool copy, FileConflictResolveType resolveType, bool holdResult, IDictionary<string, StringValues> headers)
|
||||
public List<FileOperationResult> MoveOrCopy(Guid userId, Tenant tenant, List<JsonElement> folders, List<JsonElement> files, JsonElement destFolderId, bool copy, FileConflictResolveType resolveType, bool holdResult, IDictionary<string, StringValues> headers,
|
||||
ExternalShareData externalShareData)
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = GetIds(folders);
|
||||
var (fileIntIds, fileStringIds) = GetIds(files);
|
||||
|
||||
var op1 = new FileMoveCopyOperation<int>(_serviceProvider, new FileMoveCopyOperationData<int>(folderIntIds, fileIntIds, tenant, destFolderId, copy, resolveType, holdResult, headers), _thumbnailSettings);
|
||||
var op2 = new FileMoveCopyOperation<string>(_serviceProvider, new FileMoveCopyOperationData<string>(folderStringIds, fileStringIds, tenant, destFolderId, copy, resolveType, holdResult, headers), _thumbnailSettings);
|
||||
var op = new FileMoveCopyOperation(_serviceProvider, op2, op1);
|
||||
var op1 = new FileMoveCopyOperation<int>(_serviceProvider, new FileMoveCopyOperationData<int>(folderIntIds, fileIntIds, tenant, destFolderId, copy, resolveType, externalShareData, holdResult, headers), _thumbnailSettings);
|
||||
var op2 = new FileMoveCopyOperation<string>(_serviceProvider, new FileMoveCopyOperationData<string>(folderStringIds, fileStringIds, tenant, destFolderId, copy, resolveType, externalShareData, holdResult, headers), _thumbnailSettings);
|
||||
var op = new FileMoveCopyOperation(_serviceProvider, op2, op1);
|
||||
|
||||
return QueueTask(userId, op);
|
||||
}
|
||||
|
||||
public List<FileOperationResult> Delete<T>(Guid userId, Tenant tenant, IEnumerable<T> folders, IEnumerable<T> files, bool ignoreException, bool holdResult, bool immediately, IDictionary<string, StringValues> headers, bool isEmptyTrash = false)
|
||||
public List<FileOperationResult> Delete<T>(Guid userId, Tenant tenant, IEnumerable<T> folders, IEnumerable<T> files, bool ignoreException, bool holdResult, bool immediately, IDictionary<string, StringValues> headers, ExternalShareData externalShareData,
|
||||
bool isEmptyTrash = false)
|
||||
{
|
||||
var op = new FileDeleteOperation<T>(_serviceProvider, new FileDeleteOperationData<T>(folders, files, tenant, holdResult, ignoreException, immediately, headers, isEmptyTrash), _thumbnailSettings);
|
||||
var op = new FileDeleteOperation<T>(_serviceProvider, new FileDeleteOperationData<T>(folders, files, tenant, externalShareData, holdResult, ignoreException, immediately, headers, isEmptyTrash), _thumbnailSettings);
|
||||
return QueueTask(userId, op);
|
||||
}
|
||||
|
||||
public List<FileOperationResult> Delete(Guid userId, Tenant tenant, List<JsonElement> folders, List<JsonElement> files, bool ignoreException, bool holdResult, bool immediately, IDictionary<string, StringValues> headers, bool isEmptyTrash = false)
|
||||
public List<FileOperationResult> Delete(Guid userId, Tenant tenant, List<JsonElement> folders, List<JsonElement> files, bool ignoreException, bool holdResult, bool immediately, IDictionary<string, StringValues> headers,
|
||||
ExternalShareData externalShareData, bool isEmptyTrash = false)
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = GetIds(folders);
|
||||
var (fileIntIds, fileStringIds) = GetIds(files);
|
||||
|
||||
var op1 = new FileDeleteOperation<int>(_serviceProvider, new FileDeleteOperationData<int>(folderIntIds, fileIntIds, tenant, holdResult, ignoreException, immediately, headers, isEmptyTrash), _thumbnailSettings);
|
||||
var op2 = new FileDeleteOperation<string>(_serviceProvider, new FileDeleteOperationData<string>(folderStringIds, fileStringIds, tenant, holdResult, ignoreException, immediately, headers, isEmptyTrash), _thumbnailSettings);
|
||||
var op = new FileDeleteOperation(_serviceProvider, op2, op1);
|
||||
var op1 = new FileDeleteOperation<int>(_serviceProvider, new FileDeleteOperationData<int>(folderIntIds, fileIntIds, tenant, externalShareData, holdResult, ignoreException, immediately, headers, isEmptyTrash), _thumbnailSettings);
|
||||
var op2 = new FileDeleteOperation<string>(_serviceProvider, new FileDeleteOperationData<string>(folderStringIds, fileStringIds, tenant, externalShareData, holdResult, ignoreException, immediately, headers, isEmptyTrash), _thumbnailSettings);
|
||||
var op = new FileDeleteOperation(_serviceProvider, op2, op1);
|
||||
|
||||
return QueueTask(userId, op);
|
||||
}
|
||||
@ -241,6 +248,20 @@ public class FileOperationsManager
|
||||
|
||||
return (resultInt, resultString);
|
||||
}
|
||||
|
||||
private Guid ProcessUserId(Guid userId)
|
||||
{
|
||||
var securityContext = _serviceProvider.GetRequiredService<SecurityContext>();
|
||||
|
||||
if (securityContext.IsAuthenticated)
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
|
||||
var externalShare = _serviceProvider.GetRequiredService<ExternalShare>();
|
||||
|
||||
return externalShare.GetSessionId();
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileOperationsManagerHelperExtention
|
||||
@ -254,4 +275,4 @@ public static class FileOperationsManagerHelperExtention
|
||||
services.TryAdd<FileDownloadOperationScope>();
|
||||
services.TryAdd<CompressToArchive>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public class AceWrapper : IMapFrom<RoomInvitation>
|
||||
public SubjectType SubjectType { get; set; }
|
||||
public FileShareOptions FileShareOptions { get; set; }
|
||||
public bool CanEditAccess { get; set; }
|
||||
public bool IsTemplate { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string SubjectName { get; set; }
|
||||
@ -61,6 +62,9 @@ public class AceWrapper : IMapFrom<RoomInvitation>
|
||||
|
||||
[JsonPropertyName("disable_remove")]
|
||||
public bool DisableRemove { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsLink => (SubjectType is SubjectType.InvitationLink or SubjectType.ExternalLink) || !string.IsNullOrEmpty(Link);
|
||||
}
|
||||
|
||||
public class AceShortWrapper
|
||||
|
@ -47,7 +47,8 @@ public class FileConverterQueue
|
||||
IAccount account,
|
||||
bool deleteAfter,
|
||||
string url,
|
||||
string serverRootPath)
|
||||
string serverRootPath,
|
||||
ExternalShareData externalShareData = null)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
@ -79,7 +80,8 @@ public class FileConverterQueue
|
||||
StartDateTime = DateTime.UtcNow,
|
||||
Url = url,
|
||||
Password = password,
|
||||
ServerRootPath = serverRootPath
|
||||
ServerRootPath = serverRootPath,
|
||||
ExternalShareData = externalShareData != null ? JsonSerializer.Serialize(externalShareData) : null
|
||||
};
|
||||
|
||||
Enqueue(queueResult, cacheKey);
|
||||
@ -284,6 +286,7 @@ public class FileConverter
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
private readonly SocketManager _socketManager;
|
||||
private readonly FileConverterQueue _fileConverterQueue;
|
||||
private readonly ExternalShare _externalShare;
|
||||
|
||||
public FileConverter(
|
||||
FileUtility fileUtility,
|
||||
@ -308,7 +311,8 @@ public class FileConverter
|
||||
IServiceProvider serviceProvider,
|
||||
IHttpClientFactory clientFactory,
|
||||
SocketManager socketManager,
|
||||
FileConverterQueue fileConverterQueue)
|
||||
FileConverterQueue fileConverterQueue,
|
||||
ExternalShare externalShare)
|
||||
{
|
||||
_fileUtility = fileUtility;
|
||||
_filesLinkUtility = filesLinkUtility;
|
||||
@ -333,6 +337,7 @@ public class FileConverter
|
||||
_clientFactory = clientFactory;
|
||||
_socketManager = socketManager;
|
||||
_fileConverterQueue = fileConverterQueue;
|
||||
_externalShare = externalShare;
|
||||
}
|
||||
|
||||
public FileConverter(
|
||||
@ -359,11 +364,11 @@ public class FileConverter
|
||||
IHttpContextAccessor httpContextAccesor,
|
||||
IHttpClientFactory clientFactory,
|
||||
SocketManager socketManager,
|
||||
FileConverterQueue fileConverterQueue)
|
||||
FileConverterQueue fileConverterQueue, ExternalShare externalShare)
|
||||
: this(fileUtility, filesLinkUtility, daoFactory, setupInfo, pathProvider, fileSecurity,
|
||||
fileMarker, tenantManager, authContext, entryManager, filesSettingsHelper,
|
||||
globalFolderHelper, filesMessageService, fileShareLink, documentServiceHelper, documentServiceConnector, fileTracker,
|
||||
baseCommonLinkUtility, entryStatusManager, serviceProvider, clientFactory, socketManager, fileConverterQueue)
|
||||
baseCommonLinkUtility, entryStatusManager, serviceProvider, clientFactory, socketManager, fileConverterQueue, externalShare)
|
||||
{
|
||||
_httpContextAccesor = httpContextAccesor;
|
||||
}
|
||||
@ -486,9 +491,10 @@ public class FileConverter
|
||||
Account = _authContext.CurrentAccount.ID,
|
||||
Delete = false,
|
||||
StartDateTime = DateTime.UtcNow,
|
||||
Url = _httpContextAccesor?.HttpContext != null ? _httpContextAccesor.HttpContext.Request.GetDisplayUrl() : null,
|
||||
Url = _httpContextAccesor?.HttpContext?.Request.GetDisplayUrl(),
|
||||
Password = null,
|
||||
ServerRootPath = _baseCommonLinkUtility.ServerRootPath
|
||||
ServerRootPath = _baseCommonLinkUtility.ServerRootPath,
|
||||
ExternalShareData = await _externalShare.GetLinkIdAsync() != default ? JsonSerializer.Serialize(_externalShare.GetCurrentShareDataAsync()) : null
|
||||
};
|
||||
|
||||
var operationResultError = string.Empty;
|
||||
@ -527,7 +533,9 @@ public class FileConverter
|
||||
}
|
||||
|
||||
await _fileMarker.RemoveMarkAsNewAsync(file);
|
||||
_fileConverterQueue.Add(file, password, _tenantManager.GetCurrentTenant().Id, _authContext.CurrentAccount, deleteAfter, _httpContextAccesor?.HttpContext != null ? _httpContextAccesor.HttpContext.Request.GetDisplayUrl() : null, _baseCommonLinkUtility.ServerRootPath);
|
||||
|
||||
_fileConverterQueue.Add(file, password, (await _tenantManager.GetCurrentTenantAsync()).Id, _authContext.CurrentAccount, deleteAfter, _httpContextAccesor?.HttpContext?.Request.GetDisplayUrl(),
|
||||
_baseCommonLinkUtility.ServerRootPath, await _externalShare.GetLinkIdAsync() != default ? await _externalShare.GetCurrentShareDataAsync() : null);
|
||||
}
|
||||
|
||||
public bool IsConverting<T>(File<T> file)
|
||||
|
@ -53,4 +53,7 @@ public class FileConverterOperationResult : FileOperationResult
|
||||
[ProtoMember(8)]
|
||||
//hack for download
|
||||
public string ServerRootPath { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
public string ExternalShareData { get; set; }
|
||||
}
|
||||
|
@ -114,8 +114,6 @@ public class FileSharingAceHelper
|
||||
var changed = false;
|
||||
string warning = null;
|
||||
var shares = (await _fileSecurity.GetSharesAsync(entry)).ToList();
|
||||
var usersInRoomCount = shares.Count(r => !r.IsLink);
|
||||
var i = 1;
|
||||
|
||||
foreach (var w in aceWrappers.OrderByDescending(ace => ace.SubjectGroup))
|
||||
{
|
||||
@ -125,32 +123,35 @@ public class FileSharingAceHelper
|
||||
var existedShare = shares.FirstOrDefault(r => r.Subject == w.Id);
|
||||
var rightIsAvailable = FileSecurity.AvailableUserRights.TryGetValue(currentUserType, out var userAccesses)
|
||||
&& userAccesses.Contains(w.Access);
|
||||
|
||||
|
||||
if (room != null &&
|
||||
FileSecurity.AvailableRoomRights.TryGetValue(room.FolderType, out var roomAccesses) &&
|
||||
!roomAccesses.Contains(w.Access))
|
||||
|
||||
if (room != null)
|
||||
{
|
||||
continue;
|
||||
if (FileSecurity.AvailableRoomRights.TryGetValue(room.FolderType, out var roomAccesses) && !roomAccesses.Contains(w.Access))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (room.FolderType is not (FolderType.PublicRoom or FolderType.CustomRoom) && w.SubjectType == SubjectType.ExternalLink)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (room.FolderType == FolderType.PublicRoom && w.Access == FileShare.Read && w.SubjectType != SubjectType.ExternalLink)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (room != null && existedShare is not { IsLink: true })
|
||||
|
||||
if (room != null && existedShare is not { IsLink: true } && !w.IsLink)
|
||||
{
|
||||
if (currentUserType == EmployeeType.DocSpaceAdmin && !rightIsAvailable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existedShare is { IsLink: false })
|
||||
if (existedShare != null && !rightIsAvailable)
|
||||
{
|
||||
if (!rightIsAvailable)
|
||||
{
|
||||
throw new InvalidOperationException(FilesCommonResource.ErrorMessage_RoleNotAvailable);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _usersInRoomChecker.CheckAddAsync(usersInRoomCount + (i++));
|
||||
throw new InvalidOperationException(FilesCommonResource.ErrorMessage_RoleNotAvailable);
|
||||
}
|
||||
|
||||
try
|
||||
@ -172,6 +173,11 @@ public class FileSharingAceHelper
|
||||
{
|
||||
warning ??= e.Message;
|
||||
w.Access = FileSecurity.GetHighFreeRole(room.FolderType);
|
||||
|
||||
if (w.Access == FileShare.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -432,6 +438,7 @@ public class FileSharing
|
||||
private readonly FileSharingHelper _fileSharingHelper;
|
||||
private readonly FilesSettingsHelper _filesSettingsHelper;
|
||||
private readonly InvitationLinkService _invitationLinkService;
|
||||
private readonly ExternalShare _externalShare;
|
||||
|
||||
public FileSharing(
|
||||
Global global,
|
||||
@ -444,7 +451,8 @@ public class FileSharing
|
||||
IDaoFactory daoFactory,
|
||||
FileSharingHelper fileSharingHelper,
|
||||
FilesSettingsHelper filesSettingsHelper,
|
||||
InvitationLinkService invitationLinkService)
|
||||
InvitationLinkService invitationLinkService,
|
||||
ExternalShare externalShare)
|
||||
{
|
||||
_global = global;
|
||||
_fileSecurity = fileSecurity;
|
||||
@ -457,6 +465,7 @@ public class FileSharing
|
||||
_filesSettingsHelper = filesSettingsHelper;
|
||||
_logger = logger;
|
||||
_invitationLinkService = invitationLinkService;
|
||||
_externalShare = externalShare;
|
||||
}
|
||||
|
||||
public async Task<bool> CanSetAccessAsync<T>(FileEntry<T> entry)
|
||||
@ -464,7 +473,7 @@ public class FileSharing
|
||||
return await _fileSharingHelper.CanSetAccessAsync(entry);
|
||||
}
|
||||
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(FileEntry<T> entry)
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(FileEntry<T> entry, IEnumerable<SubjectType> subjectsTypes = null, bool withoutTemplates = false)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
@ -493,6 +502,11 @@ public class FileSharing
|
||||
|
||||
foreach (var r in records)
|
||||
{
|
||||
if (subjectsTypes != null && !subjectsTypes.Contains(r.SubjectType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r.Subject == FileConstant.ShareLinkId)
|
||||
{
|
||||
linkAccess = r.Share;
|
||||
@ -538,16 +552,20 @@ public class FileSharing
|
||||
Id = r.Subject,
|
||||
SubjectGroup = isgroup,
|
||||
Access = share,
|
||||
FileShareOptions = r.FileShareOptions,
|
||||
FileShareOptions = r.Options,
|
||||
};
|
||||
|
||||
w.CanEditAccess = _authContext.CurrentAccount.ID != w.Id && w.SubjectType == SubjectType.UserOrGroup && canEditAccess;
|
||||
|
||||
if (isRoom && r.IsLink)
|
||||
{
|
||||
w.Link = _invitationLinkService.GetInvitationLink(r.Subject, _authContext.CurrentAccount.ID);
|
||||
w.Link = r.SubjectType == SubjectType.InvitationLink ?
|
||||
_invitationLinkService.GetInvitationLink(r.Subject, _authContext.CurrentAccount.ID) :
|
||||
await _externalShare.GetLinkAsync(r.Subject);
|
||||
w.SubjectGroup = true;
|
||||
w.CanEditAccess = false;
|
||||
w.FileShareOptions.Password = await _externalShare.GetPasswordAsync(w.FileShareOptions.Password);
|
||||
w.SubjectType = r.SubjectType;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -561,20 +579,36 @@ public class FileSharing
|
||||
result.Add(w);
|
||||
}
|
||||
|
||||
if (isRoom)
|
||||
if (isRoom && !withoutTemplates)
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
var invitationId = Guid.NewGuid();
|
||||
|
||||
var w = new AceWrapper
|
||||
var invitationAceTemplate = new AceWrapper
|
||||
{
|
||||
Id = id,
|
||||
Link = _invitationLinkService.GetInvitationLink(id, _authContext.CurrentAccount.ID),
|
||||
Id = invitationId,
|
||||
Link = _invitationLinkService.GetInvitationLink(invitationId, _authContext.CurrentAccount.ID),
|
||||
SubjectGroup = true,
|
||||
Access = FileShare.Read,
|
||||
Owner = false
|
||||
Access = ((Folder<T>)entry).FolderType == FolderType.PublicRoom ? FileShare.RoomAdmin : FileShare.Read,
|
||||
Owner = false,
|
||||
IsTemplate = true,
|
||||
SubjectType = SubjectType.InvitationLink
|
||||
};
|
||||
|
||||
result.Add(w);
|
||||
var externalId = Guid.NewGuid();
|
||||
|
||||
var externalAceTemplate = new AceWrapper
|
||||
{
|
||||
Id = externalId,
|
||||
Link = await _externalShare.GetLinkAsync(externalId),
|
||||
SubjectGroup = true,
|
||||
Access = FileShare.Read,
|
||||
Owner = false,
|
||||
IsTemplate = true,
|
||||
SubjectType = SubjectType.ExternalLink
|
||||
};
|
||||
|
||||
result.Add(invitationAceTemplate);
|
||||
result.Add(externalAceTemplate);
|
||||
}
|
||||
|
||||
if (entry.FileEntryType == FileEntryType.File && result.All(w => w.Id != FileConstant.ShareLinkId)
|
||||
@ -593,7 +627,7 @@ public class FileSharing
|
||||
result.Add(w);
|
||||
}
|
||||
|
||||
if (!result.Any(w => w.Owner))
|
||||
if (!result.Any(w => w.Owner) && (subjectsTypes == null || subjectsTypes.Contains(SubjectType.UserOrGroup)))
|
||||
{
|
||||
var ownerId = entry.RootFolderType == FolderType.USER ? entry.RootCreateBy : entry.CreateBy;
|
||||
var w = new AceWrapper
|
||||
@ -655,7 +689,8 @@ public class FileSharing
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(IEnumerable<T> fileIds, IEnumerable<T> folderIds)
|
||||
public async Task<List<AceWrapper>> GetSharedInfoAsync<T>(IEnumerable<T> fileIds, IEnumerable<T> folderIds, IEnumerable<SubjectType> subjectTypes = null,
|
||||
bool withoutTemplates = false)
|
||||
{
|
||||
if (!_authContext.IsAuthenticated)
|
||||
{
|
||||
@ -677,7 +712,7 @@ public class FileSharing
|
||||
IEnumerable<AceWrapper> acesForObject;
|
||||
try
|
||||
{
|
||||
acesForObject = await GetSharedInfoAsync(entry);
|
||||
acesForObject = await GetSharedInfoAsync(entry, subjectTypes, withoutTemplates);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -232,6 +232,7 @@ public abstract class FilesController<T> : ApiControllerBase
|
||||
/// <short>File information</short>
|
||||
/// <category>Files</category>
|
||||
/// <returns>File info</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("file/{fileId}")]
|
||||
public async Task<FileDto<T>> GetFileInfoAsync(T fileId, int version = -1)
|
||||
{
|
||||
@ -427,6 +428,7 @@ public class FilesControllerCommon : ApiControllerBase
|
||||
return await _filesControllerHelperInternal.CreateTextFileAsync(await _globalFolderHelper.FolderMyAsync, inDto.Title, inDto.Content);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("thumbnails")]
|
||||
public async Task<IEnumerable<JsonElement>> CreateThumbnailsAsync(BaseBatchRequestDto inDto)
|
||||
{
|
||||
|
@ -121,6 +121,7 @@ public abstract class FoldersController<T> : ApiControllerBase
|
||||
/// <param name="userIdOrGroupId" optional="true">User or group ID</param>
|
||||
/// <param name="filterType" optional="true" remark="Allowed values: None (0), FilesOnly (1), FoldersOnly (2), DocumentsOnly (3), PresentationsOnly (4), SpreadsheetsOnly (5) or ImagesOnly (7)">Filter type</param>
|
||||
/// <returns>Folder contents</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("{folderId}", Order = 1)]
|
||||
public async Task<FolderContentDto<T>> GetFolderAsync(T folderId, Guid? userIdOrGroupId, FilterType? filterType, T roomId, bool? searchInContent, bool? withsubfolders, bool? excludeSubject,
|
||||
ApplyFilterOption? applyFilterOption)
|
||||
@ -136,6 +137,7 @@ public abstract class FoldersController<T> : ApiControllerBase
|
||||
/// <short>Folder information</short>
|
||||
/// <category>Folders</category>
|
||||
/// <returns>Folder info</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("folder/{folderId}")]
|
||||
public async Task<FolderDto<T>> GetFolderInfoAsync(T folderId)
|
||||
{
|
||||
|
@ -50,6 +50,7 @@ public class OperationController : ApiControllerBase
|
||||
/// <param name="folderIds">Folder ID list</param>
|
||||
/// <category>File operations</category>
|
||||
/// <returns>Operation result</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPut("fileops/bulkdownload")]
|
||||
public async IAsyncEnumerable<FileOperationDto> BulkDownload(DownloadRequestDto inDto)
|
||||
{
|
||||
@ -141,6 +142,7 @@ public class OperationController : ApiControllerBase
|
||||
/// <short>Get file operations list</short>
|
||||
/// <category>File operations</category>
|
||||
/// <returns>Operation result</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("fileops")]
|
||||
public async IAsyncEnumerable<FileOperationDto> GetOperationStatuses()
|
||||
{
|
||||
@ -218,12 +220,14 @@ public class OperationController : ApiControllerBase
|
||||
yield return await GetFileEntryWrapperAsync(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes all the active file operations
|
||||
/// </summary>
|
||||
/// <short>Finish all</short>
|
||||
/// <category>File operations</category>
|
||||
/// <returns>Operation result</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPut("fileops/terminate")]
|
||||
public async IAsyncEnumerable<FileOperationDto> TerminateTasks()
|
||||
{
|
||||
|
@ -1,223 +1,240 @@
|
||||
// (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.Api;
|
||||
|
||||
// (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.MessagingSystem;
|
||||
using ASC.Web.Core;
|
||||
|
||||
namespace ASC.Files.Api;
|
||||
|
||||
[ConstraintRoute("int")]
|
||||
public class SecutiryControllerInternal : SecutiryController<int>
|
||||
{
|
||||
public SecutiryControllerInternal(
|
||||
FileStorageService fileStorageService,
|
||||
SecurityControllerHelper securityControllerHelper,
|
||||
FolderDtoHelper folderDtoHelper,
|
||||
FileDtoHelper fileDtoHelper)
|
||||
: base(fileStorageService, securityControllerHelper, folderDtoHelper, fileDtoHelper)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class SecutiryControllerThirdparty : SecutiryController<string>
|
||||
{
|
||||
public SecutiryControllerThirdparty(
|
||||
FileStorageService fileStorageService,
|
||||
SecurityControllerHelper securityControllerHelper,
|
||||
FolderDtoHelper folderDtoHelper,
|
||||
FileDtoHelper fileDtoHelper)
|
||||
: base(fileStorageService, securityControllerHelper, folderDtoHelper, fileDtoHelper)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SecutiryController<T> : ApiControllerBase
|
||||
public class SecurityControllerInternal : SecurityController<int>
|
||||
{
|
||||
private readonly FileStorageService _fileStorageService;
|
||||
private readonly SecurityControllerHelper _securityControllerHelper;
|
||||
|
||||
public SecutiryController(FileStorageService fileStorageService, SecurityControllerHelper securityControllerHelper,
|
||||
FolderDtoHelper folderDtoHelper,
|
||||
FileDtoHelper fileDtoHelper) : base(folderDtoHelper, fileDtoHelper)
|
||||
{
|
||||
_fileStorageService = fileStorageService;
|
||||
_securityControllerHelper = securityControllerHelper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the external link to the shared file with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// File external link
|
||||
/// </summary>
|
||||
/// <param name="fileId">File ID</param>
|
||||
/// <param name="share">Access right</param>
|
||||
/// <category>Files</category>
|
||||
/// <returns>Shared file link</returns>
|
||||
[HttpPut("{fileId}/sharedlinkAsync")]
|
||||
public async Task<object> GenerateSharedLinkAsync(T fileId, GenerateSharedLinkRequestDto inDto)
|
||||
{
|
||||
return await _securityControllerHelper.GenerateSharedLinkAsync(fileId, inDto.Share);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the detailed information about shared file with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <short>File sharing</short>
|
||||
/// <category>Sharing</category>
|
||||
/// <param name="fileId">File ID</param>
|
||||
/// <returns>Shared file information</returns>
|
||||
[HttpGet("file/{fileId}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> GetFileSecurityInfoAsync(T fileId)
|
||||
{
|
||||
await foreach (var s in _securityControllerHelper.GetFileSecurityInfoAsync(fileId))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the detailed information about shared folder with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <short>Folder sharing</short>
|
||||
/// <param name="folderId">Folder ID</param>
|
||||
/// <category>Sharing</category>
|
||||
/// <returns>Shared folder information</returns>
|
||||
[HttpGet("folder/{folderId}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> GetFolderSecurityInfoAsync(T folderId)
|
||||
{
|
||||
await foreach (var s in _securityControllerHelper.GetFolderSecurityInfoAsync(folderId))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{fileId}/setacelink")]
|
||||
public async Task<bool> SetAceLinkAsync(T fileId, [FromBody] GenerateSharedLinkRequestDto inDto)
|
||||
{
|
||||
return await _fileStorageService.SetAceLinkAsync(fileId, inDto.Share);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets sharing settings for the file with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <param name="fileId">File ID</param>
|
||||
/// <param name="share">Collection of sharing rights</param>
|
||||
/// <param name="notify">Should notify people</param>
|
||||
/// <param name="sharingMessage">Sharing message to send when notifying</param>
|
||||
/// <short>Share file</short>
|
||||
/// <category>Sharing</category>
|
||||
/// <remarks>
|
||||
/// Each of the FileShareParams must contain two parameters: 'ShareTo' - ID of the user with whom we want to share and 'Access' - access type which we want to grant to the user (Read, ReadWrite, etc)
|
||||
/// </remarks>
|
||||
/// <returns>Shared file information</returns>
|
||||
[HttpPut("file/{fileId}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> SetFileSecurityInfoAsync(T fileId, SecurityInfoRequestDto inDto)
|
||||
{
|
||||
await foreach (var s in _securityControllerHelper.SetSecurityInfoAsync(new List<T> { fileId }, new List<T>(), inDto.Share, inDto.Notify, inDto.SharingMessage))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets sharing settings for the folder with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <short>Share folder</short>
|
||||
/// <param name="folderId">Folder ID</param>
|
||||
/// <param name="share">Collection of sharing rights</param>
|
||||
/// <param name="notify">Should notify people</param>
|
||||
/// <param name="sharingMessage">Sharing message to send when notifying</param>
|
||||
/// <remarks>
|
||||
/// Each of the FileShareParams must contain two parameters: 'ShareTo' - ID of the user with whom we want to share and 'Access' - access type which we want to grant to the user (Read, ReadWrite, etc)
|
||||
/// </remarks>
|
||||
/// <category>Sharing</category>
|
||||
/// <returns>Shared folder information</returns>
|
||||
[HttpPut("folder/{folderId}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> SetFolderSecurityInfoAsync(T folderId, SecurityInfoRequestDto inDto)
|
||||
{
|
||||
await foreach (var s in _securityControllerHelper.SetSecurityInfoAsync(new List<T>(), new List<T> { folderId }, inDto.Share, inDto.Notify, inDto.SharingMessage))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("file/{fileId}/publickeys")]
|
||||
public async Task<List<EncryptionKeyPairDto>> GetEncryptionAccess(T fileId)
|
||||
{
|
||||
return await _fileStorageService.GetEncryptionAccessAsync(fileId);
|
||||
}
|
||||
|
||||
[HttpPost("file/{fileId}/sendeditornotify")]
|
||||
public async Task<List<AceShortWrapper>> SendEditorNotify(T fileId, MentionMessageWrapper mentionMessage)
|
||||
{
|
||||
return await _fileStorageService.SendEditorNotifyAsync(fileId, mentionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public class SecutiryControllerCommon : ApiControllerBase
|
||||
{
|
||||
private readonly FileStorageService _fileStorageService;
|
||||
private readonly SecurityControllerHelper _securityControllerHelper;
|
||||
|
||||
public SecutiryControllerCommon(
|
||||
public SecurityControllerInternal(
|
||||
FileStorageService fileStorageService,
|
||||
SecurityControllerHelper securityControllerHelper,
|
||||
FolderDtoHelper folderDtoHelper,
|
||||
SecurityControllerHelper securityControllerHelper,
|
||||
FolderDtoHelper folderDtoHelper,
|
||||
FileDtoHelper fileDtoHelper)
|
||||
: base(fileStorageService, securityControllerHelper, folderDtoHelper, fileDtoHelper)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class SecurityControllerThirdparty : SecurityController<string>
|
||||
{
|
||||
public SecurityControllerThirdparty(
|
||||
FileStorageService fileStorageService,
|
||||
SecurityControllerHelper securityControllerHelper,
|
||||
FolderDtoHelper folderDtoHelper,
|
||||
FileDtoHelper fileDtoHelper)
|
||||
: base(fileStorageService, securityControllerHelper, folderDtoHelper, fileDtoHelper)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SecurityController<T> : ApiControllerBase
|
||||
{
|
||||
private readonly FileStorageService _fileStorageService;
|
||||
private readonly SecurityControllerHelper _securityControllerHelper;
|
||||
|
||||
public SecurityController(
|
||||
FileStorageService fileStorageService,
|
||||
SecurityControllerHelper securityControllerHelper,
|
||||
FolderDtoHelper folderDtoHelper,
|
||||
FileDtoHelper fileDtoHelper) : base(folderDtoHelper, fileDtoHelper)
|
||||
{
|
||||
_fileStorageService = fileStorageService;
|
||||
_securityControllerHelper = securityControllerHelper;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the external link to the shared file with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// File external link
|
||||
/// </summary>
|
||||
/// <param name="fileId">File ID</param>
|
||||
/// <param name="share">Access right</param>
|
||||
/// <category>Files</category>
|
||||
/// <returns>Shared file link</returns>
|
||||
[HttpPut("{fileId}/sharedlinkAsync")]
|
||||
public async Task<object> GenerateSharedLinkAsync(T fileId, GenerateSharedLinkRequestDto inDto)
|
||||
{
|
||||
return await _securityControllerHelper.GenerateSharedLinkAsync(fileId, inDto.Share);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the detailed information about shared file with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <short>File sharing</short>
|
||||
/// <category>Sharing</category>
|
||||
/// <param name="fileId">File ID</param>
|
||||
/// <returns>Shared file information</returns>
|
||||
[HttpGet("file/{fileId}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> GetFileSecurityInfoAsync(T fileId)
|
||||
{
|
||||
await foreach (var s in _securityControllerHelper.GetFileSecurityInfoAsync(fileId))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the detailed information about shared folder with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <short>Folder sharing</short>
|
||||
/// <param name="folderId">Folder ID</param>
|
||||
/// <category>Sharing</category>
|
||||
/// <returns>Shared folder information</returns>
|
||||
[HttpGet("folder/{folderId}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> GetFolderSecurityInfoAsync(T folderId)
|
||||
{
|
||||
await foreach (var s in _securityControllerHelper.GetFolderSecurityInfoAsync(folderId))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{fileId}/setacelink")]
|
||||
public async Task<bool> SetAceLinkAsync(T fileId, [FromBody] GenerateSharedLinkRequestDto inDto)
|
||||
{
|
||||
return await _fileStorageService.SetAceLinkAsync(fileId, inDto.Share);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets sharing settings for the file with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <param name="fileId">File ID</param>
|
||||
/// <param name="share">Collection of sharing rights</param>
|
||||
/// <param name="notify">Should notify people</param>
|
||||
/// <param name="sharingMessage">Sharing message to send when notifying</param>
|
||||
/// <short>Share file</short>
|
||||
/// <category>Sharing</category>
|
||||
/// <remarks>
|
||||
/// Each of the FileShareParams must contain two parameters: 'ShareTo' - ID of the user with whom we want to share and 'Access' - access type which we want to grant to the user (Read, ReadWrite, etc)
|
||||
/// </remarks>
|
||||
/// <returns>Shared file information</returns>
|
||||
[HttpPut("file/{fileId}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> SetFileSecurityInfoAsync(T fileId, SecurityInfoRequestDto inDto)
|
||||
{
|
||||
await foreach (var s in _securityControllerHelper.SetSecurityInfoAsync(new List<T> { fileId }, new List<T>(), inDto.Share, inDto.Notify, inDto.SharingMessage))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets sharing settings for the folder with the ID specified in the request
|
||||
/// </summary>
|
||||
/// <short>Share folder</short>
|
||||
/// <param name="folderId">Folder ID</param>
|
||||
/// <param name="share">Collection of sharing rights</param>
|
||||
/// <param name="notify">Should notify people</param>
|
||||
/// <param name="sharingMessage">Sharing message to send when notifying</param>
|
||||
/// <remarks>
|
||||
/// Each of the FileShareParams must contain two parameters: 'ShareTo' - ID of the user with whom we want to share and 'Access' - access type which we want to grant to the user (Read, ReadWrite, etc)
|
||||
/// </remarks>
|
||||
/// <category>Sharing</category>
|
||||
/// <returns>Shared folder information</returns>
|
||||
[HttpPut("folder/{folderId}/share")]
|
||||
public async IAsyncEnumerable<FileShareDto> SetFolderSecurityInfoAsync(T folderId, SecurityInfoRequestDto inDto)
|
||||
{
|
||||
await foreach (var s in _securityControllerHelper.SetSecurityInfoAsync(new List<T>(), new List<T> { folderId }, inDto.Share, inDto.Notify, inDto.SharingMessage))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("file/{fileId}/publickeys")]
|
||||
public async Task<List<EncryptionKeyPairDto>> GetEncryptionAccess(T fileId)
|
||||
{
|
||||
return await _fileStorageService.GetEncryptionAccessAsync(fileId);
|
||||
}
|
||||
|
||||
[HttpPost("file/{fileId}/sendeditornotify")]
|
||||
public async Task<List<AceShortWrapper>> SendEditorNotify(T fileId, MentionMessageWrapper mentionMessage)
|
||||
{
|
||||
return await _fileStorageService.SendEditorNotifyAsync(fileId, mentionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public class SecurityControllerCommon : ApiControllerBase
|
||||
{
|
||||
private readonly FileStorageService _fileStorageService;
|
||||
private readonly SecurityControllerHelper _securityControllerHelper;
|
||||
private readonly BruteForceLoginManager _bruteForceLoginManager;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ExternalLinkHelper _externalLinkHelper;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public SecurityControllerCommon(
|
||||
FileStorageService fileStorageService,
|
||||
SecurityControllerHelper securityControllerHelper,
|
||||
FolderDtoHelper folderDtoHelper,
|
||||
FileDtoHelper fileDtoHelper,
|
||||
BruteForceLoginManager bruteForceLoginManager,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ExternalLinkHelper externalLinkHelper,
|
||||
IMapper mapper) : base(folderDtoHelper, fileDtoHelper)
|
||||
{
|
||||
_fileStorageService = fileStorageService;
|
||||
_securityControllerHelper = securityControllerHelper;
|
||||
_bruteForceLoginManager = bruteForceLoginManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_externalLinkHelper = externalLinkHelper;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpPost("owner")]
|
||||
public async IAsyncEnumerable<FileEntryDto> ChangeOwnerAsync(ChangeOwnerRequestDto inDto)
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = FileOperationsManager.GetIds(inDto.FolderIds);
|
||||
var (fileIntIds, fileStringIds) = FileOperationsManager.GetIds(inDto.FileIds);
|
||||
|
||||
var data = AsyncEnumerable.Empty<FileEntry>();
|
||||
data = data.Concat(_fileStorageService.ChangeOwnerAsync(folderIntIds, fileIntIds, inDto.UserId));
|
||||
data = data.Concat(_fileStorageService.ChangeOwnerAsync(folderStringIds, fileStringIds, inDto.UserId));
|
||||
|
||||
await foreach (var e in data)
|
||||
{
|
||||
yield return await GetFileEntryWrapperAsync(e);
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = FileOperationsManager.GetIds(inDto.FolderIds);
|
||||
var (fileIntIds, fileStringIds) = FileOperationsManager.GetIds(inDto.FileIds);
|
||||
|
||||
var data = AsyncEnumerable.Empty<FileEntry>();
|
||||
data = data.Concat(_fileStorageService.ChangeOwnerAsync(folderIntIds, fileIntIds, inDto.UserId));
|
||||
data = data.Concat(_fileStorageService.ChangeOwnerAsync(folderStringIds, fileStringIds, inDto.UserId));
|
||||
|
||||
await foreach (var e in data)
|
||||
{
|
||||
yield return await GetFileEntryWrapperAsync(e);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("share")]
|
||||
public async IAsyncEnumerable<FileShareDto> GetSecurityInfoAsync(BaseBatchRequestDto inDto)
|
||||
{
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = FileOperationsManager.GetIds(inDto.FolderIds);
|
||||
var (fileIntIds, fileStringIds) = FileOperationsManager.GetIds(inDto.FileIds);
|
||||
|
||||
var internalIds = _securityControllerHelper.GetSecurityInfoAsync(fileIntIds, folderIntIds);
|
||||
var thirdpartyIds = _securityControllerHelper.GetSecurityInfoAsync(fileStringIds, folderStringIds);
|
||||
|
||||
await foreach (var r in internalIds.Concat(thirdpartyIds))
|
||||
{
|
||||
yield return r;
|
||||
|
||||
var internalIds = _securityControllerHelper.GetSecurityInfoAsync(fileIntIds, folderIntIds);
|
||||
var thirdpartyIds = _securityControllerHelper.GetSecurityInfoAsync(fileStringIds, folderStringIds);
|
||||
|
||||
await foreach (var r in internalIds.Concat(thirdpartyIds))
|
||||
{
|
||||
yield return r;
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +248,7 @@ public class SecutiryControllerCommon : ApiControllerBase
|
||||
/// <returns>Shared file information</returns>
|
||||
[HttpDelete("share")]
|
||||
public async Task<bool> RemoveSecurityInfoAsync(BaseBatchRequestDto inDto)
|
||||
{
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = FileOperationsManager.GetIds(inDto.FolderIds);
|
||||
var (fileIntIds, fileStringIds) = FileOperationsManager.GetIds(inDto.FileIds);
|
||||
|
||||
@ -239,21 +256,48 @@ public class SecutiryControllerCommon : ApiControllerBase
|
||||
await _securityControllerHelper.RemoveSecurityInfoAsync(fileStringIds, folderStringIds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[HttpPut("share")]
|
||||
public async IAsyncEnumerable<FileShareDto> SetSecurityInfoAsync(SecurityInfoRequestDto inDto)
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = FileOperationsManager.GetIds(inDto.FolderIds);
|
||||
var (fileIntIds, fileStringIds) = FileOperationsManager.GetIds(inDto.FileIds);
|
||||
|
||||
var internalIds = _securityControllerHelper.SetSecurityInfoAsync(fileIntIds, folderIntIds, inDto.Share, inDto.Notify, inDto.SharingMessage);
|
||||
var thirdpartyIds = _securityControllerHelper.SetSecurityInfoAsync(fileStringIds, folderStringIds, inDto.Share, inDto.Notify, inDto.SharingMessage);
|
||||
|
||||
await foreach (var s in internalIds.Concat(thirdpartyIds))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("share")]
|
||||
public async IAsyncEnumerable<FileShareDto> SetSecurityInfoAsync(SecurityInfoRequestDto inDto)
|
||||
{
|
||||
var (folderIntIds, folderStringIds) = FileOperationsManager.GetIds(inDto.FolderIds);
|
||||
var (fileIntIds, fileStringIds) = FileOperationsManager.GetIds(inDto.FileIds);
|
||||
|
||||
var internalIds = _securityControllerHelper.SetSecurityInfoAsync(fileIntIds, folderIntIds, inDto.Share, inDto.Notify, inDto.SharingMessage);
|
||||
var thirdpartyIds = _securityControllerHelper.SetSecurityInfoAsync(fileStringIds, folderStringIds, inDto.Share, inDto.Notify, inDto.SharingMessage);
|
||||
|
||||
await foreach (var s in internalIds.Concat(thirdpartyIds))
|
||||
{
|
||||
yield return s;
|
||||
}
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("share/{key}")]
|
||||
public async Task<ExternalShareDto> GetExternalShareDataAsync(string key)
|
||||
{
|
||||
var validationInfo = await _externalLinkHelper.ValidateAsync(key);
|
||||
|
||||
return _mapper.Map<ValidationInfo, ExternalShareDto>(validationInfo);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("share/{key}/password")]
|
||||
public async Task<ExternalShareDto> ApplyExternalSharePasswordAsync(string key, ExternalShareRequestDto inDto)
|
||||
{
|
||||
var ip = MessageSettings.GetIP(_httpContextAccessor.HttpContext?.Request);
|
||||
|
||||
await _bruteForceLoginManager.IncrementAsync(key, ip, true, FilesCommonResource.ErrorMessage_SharePasswordManyAttempts);
|
||||
|
||||
var validationInfo = await _externalLinkHelper.ValidateAsync(key, inDto.Password);
|
||||
|
||||
if (validationInfo.Status != Status.InvalidPassword)
|
||||
{
|
||||
await _bruteForceLoginManager.DecrementAsync(key, ip);
|
||||
}
|
||||
|
||||
return _mapper.Map<ValidationInfo, ExternalShareDto>(validationInfo);
|
||||
}
|
||||
}
|
@ -155,6 +155,7 @@ public class SettingsController : ApiControllerBase
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("settings")]
|
||||
public FilesSettingsHelper GetFilesSettings()
|
||||
{
|
||||
|
@ -183,6 +183,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
/// <returns>
|
||||
/// Room info
|
||||
/// </returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("rooms/{id}")]
|
||||
public async Task<FolderDto<T>> GetRoomInfoAsync(T id)
|
||||
{
|
||||
@ -369,7 +370,7 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setting an external invite link
|
||||
/// Setting an room link
|
||||
/// </summary>
|
||||
/// <param name="id">
|
||||
/// Room ID
|
||||
@ -378,16 +379,67 @@ public abstract class VirtualRoomsController<T> : ApiControllerBase
|
||||
/// Link ID
|
||||
/// </param>
|
||||
/// <param name="title">
|
||||
/// External link name
|
||||
/// Link name
|
||||
/// </param>
|
||||
/// /// <param name="access">
|
||||
/// <param name="access">
|
||||
/// Access level
|
||||
/// </param>
|
||||
/// <param name="expirationDate">
|
||||
/// Link expiration date
|
||||
/// </param>
|
||||
/// <param name="linkType">
|
||||
/// Link type
|
||||
/// </param>
|
||||
/// <param name="password">
|
||||
/// Link password
|
||||
/// </param>
|
||||
/// <param name="disabled">
|
||||
/// Link status
|
||||
/// </param>
|
||||
/// <param name="denyDownload">
|
||||
/// Download restriction
|
||||
/// </param>
|
||||
/// <returns>Room security info</returns>
|
||||
[HttpPut("rooms/{id}/links")]
|
||||
public async IAsyncEnumerable<FileShareDto> SetInvintationLinkAsync(T id, InvintationLinkRequestDto inDto)
|
||||
public async IAsyncEnumerable<FileShareDto> SetLinkAsync(T id, LinkRequestDto inDto)
|
||||
{
|
||||
var fileShares = await _fileStorageService.SetInvitationLink(id, inDto.LinkId, inDto.Title, inDto.Access);
|
||||
var fileShares = inDto.LinkType switch
|
||||
{
|
||||
LinkType.Invitation => await _fileStorageService.SetInvitationLinkAsync(id, inDto.LinkId, inDto.Title, inDto.Access),
|
||||
LinkType.External => await _fileStorageService.SetExternalLinkAsync(id, FileEntryType.Folder, inDto.LinkId, inDto.Title,
|
||||
inDto.Access is not (FileShare.Read or FileShare.None) ? FileShare.Read : inDto.Access , inDto.ExpirationDate ?? default, inDto.Password, inDto.Disabled, inDto.DenyDownload),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
|
||||
foreach (var fileShareDto in fileShares)
|
||||
{
|
||||
yield return await _fileShareDtoHelper.Get(fileShareDto);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getting room links
|
||||
/// </summary>
|
||||
/// <param name="id">
|
||||
/// Room ID
|
||||
/// </param>
|
||||
/// <param name="type">
|
||||
/// Link type
|
||||
/// </param>
|
||||
/// <returns>Room security info</returns>
|
||||
[HttpGet("rooms/{id}/links")]
|
||||
public async IAsyncEnumerable<FileShareDto> GetLinksAsync(T id, LinkType? type)
|
||||
{
|
||||
var subjectTypes = type.HasValue
|
||||
? type.Value switch
|
||||
{
|
||||
LinkType.Invitation => new[] { SubjectType.InvitationLink },
|
||||
LinkType.External => new[] { SubjectType.ExternalLink },
|
||||
_ => new[] { SubjectType.InvitationLink, SubjectType.ExternalLink }
|
||||
}
|
||||
: new[] { SubjectType.InvitationLink, SubjectType.ExternalLink };
|
||||
|
||||
var fileShares = await _fileStorageService.GetSharedInfoAsync(Array.Empty<T>(), new[] { id }, subjectTypes, true);
|
||||
|
||||
foreach (var fileShareDto in fileShares)
|
||||
{
|
||||
@ -636,7 +688,7 @@ public class VirtualRoomsCommonController : ApiControllerBase
|
||||
/// <param name="filterValue">
|
||||
/// Filter by name
|
||||
/// </param>
|
||||
/// <param name="types">
|
||||
/// <param name="type">
|
||||
/// Filter by room type
|
||||
/// </param>
|
||||
/// <param name="subjectId">
|
||||
@ -660,11 +712,17 @@ public class VirtualRoomsCommonController : ApiControllerBase
|
||||
/// <param name="excludeSubject">
|
||||
/// Exclude subject from search
|
||||
/// </param>
|
||||
/// <param name="provider">
|
||||
/// Filter by ThirdParty provider type
|
||||
/// </param>
|
||||
/// <param name="subjectFilter">
|
||||
/// Filter by subject
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Virtual Rooms content
|
||||
/// </returns>
|
||||
[HttpGet("rooms")]
|
||||
public async Task<FolderContentDto<int>> GetRoomsFolderAsync(RoomFilterType? type, string subjectId, bool? searchInContent, bool? withSubfolders, SearchArea? searchArea, bool? withoutTags, string tags, bool? excludeSubject,
|
||||
public async Task<FolderContentDto<int>> GetRoomsFolderAsync(RoomType? type, string subjectId, bool? searchInContent, bool? withSubfolders, SearchArea? searchArea, bool? withoutTags, string tags, bool? excludeSubject,
|
||||
ProviderFilter? provider, SubjectFilter? subjectFilter)
|
||||
{
|
||||
ErrorIfNotDocSpace();
|
||||
@ -674,12 +732,12 @@ public class VirtualRoomsCommonController : ApiControllerBase
|
||||
|
||||
var filter = type switch
|
||||
{
|
||||
RoomFilterType.FillingFormsRoomOnly => FilterType.FillingFormsRooms,
|
||||
RoomFilterType.ReadOnlyRoomOnly => FilterType.ReadOnlyRooms,
|
||||
RoomFilterType.EditingRoomOnly => FilterType.EditingRooms,
|
||||
RoomFilterType.ReviewRoomOnly => FilterType.ReviewRooms,
|
||||
RoomFilterType.CustomRoomOnly => FilterType.CustomRooms,
|
||||
RoomFilterType.FoldersOnly => FilterType.FoldersOnly,
|
||||
RoomType.FillingFormsRoom => FilterType.FillingFormsRooms,
|
||||
RoomType.ReadOnlyRoom => FilterType.ReadOnlyRooms,
|
||||
RoomType.EditingRoom => FilterType.EditingRooms,
|
||||
RoomType.ReviewRoom => FilterType.ReviewRooms,
|
||||
RoomType.CustomRoom => FilterType.CustomRooms,
|
||||
RoomType.PublicRoom => FilterType.PublicRooms,
|
||||
_ => FilterType.None
|
||||
};
|
||||
|
||||
|
@ -127,6 +127,12 @@ internal class FileConverterService<T> : BackgroundService
|
||||
|
||||
try
|
||||
{
|
||||
var externalShare = scope.ServiceProvider.GetRequiredService<ExternalShare>();
|
||||
|
||||
if (!string.IsNullOrEmpty(converter.ExternalShareData))
|
||||
{
|
||||
externalShare.SetCurrentShareData(JsonSerializer.Deserialize<ExternalShareData>(converter.ExternalShareData));
|
||||
}
|
||||
|
||||
var user = await userManager.GetUsersAsync(converter.Account);
|
||||
|
||||
|
@ -61,6 +61,7 @@ public class SettingsController : BaseSettingsController
|
||||
private readonly QuotaUsageManager _quotaUsageManager;
|
||||
private readonly TenantDomainValidator _tenantDomainValidator;
|
||||
private readonly QuotaSyncOperation _quotaSyncOperation;
|
||||
private readonly ExternalShare _externalShare;
|
||||
|
||||
public SettingsController(
|
||||
ILoggerProvider option,
|
||||
@ -96,7 +97,8 @@ public class SettingsController : BaseSettingsController
|
||||
CustomColorThemesSettingsHelper customColorThemesSettingsHelper,
|
||||
QuotaSyncOperation quotaSyncOperation,
|
||||
QuotaUsageManager quotaUsageManager,
|
||||
TenantDomainValidator tenantDomainValidator
|
||||
TenantDomainValidator tenantDomainValidator,
|
||||
ExternalShare externalShare
|
||||
) : base(apiContext, memoryCache, webItemManager, httpContextAccessor)
|
||||
{
|
||||
_log = option.CreateLogger("ASC.Api");
|
||||
@ -129,6 +131,7 @@ public class SettingsController : BaseSettingsController
|
||||
_customColorThemesSettingsHelper = customColorThemesSettingsHelper;
|
||||
_quotaUsageManager = quotaUsageManager;
|
||||
_tenantDomainValidator = tenantDomainValidator;
|
||||
_externalShare = externalShare;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
@ -149,9 +152,14 @@ public class SettingsController : BaseSettingsController
|
||||
TenantStatus = (await _tenantManager.GetCurrentTenantAsync()).Status,
|
||||
TenantAlias = Tenant.Alias,
|
||||
EnableAdmMess = studioAdminMessageSettings.Enable || await _tenantExtra.IsNotPaidAsync(),
|
||||
LegalTerms = _setupInfo.LegalTerms
|
||||
LegalTerms = _setupInfo.LegalTerms,
|
||||
};
|
||||
|
||||
if (!_authContext.IsAuthenticated && (await _externalShare.GetSessionIdAsync() != default || await _externalShare.GetLinkIdAsync() != default))
|
||||
{
|
||||
settings.SocketUrl = _configuration["web:hub:url"] ?? "";
|
||||
}
|
||||
|
||||
if (_authContext.IsAuthenticated)
|
||||
{
|
||||
settings.TrustedDomains = Tenant.TrustedDomains;
|
||||
@ -162,11 +170,11 @@ public class SettingsController : BaseSettingsController
|
||||
settings.UtcHoursOffset = settings.UtcOffset.TotalHours;
|
||||
settings.OwnerId = Tenant.OwnerId;
|
||||
settings.NameSchemaId = _customNamingPeople.Current.Id;
|
||||
settings.SocketUrl = _configuration["web:hub:url"] ?? "";
|
||||
settings.DomainValidator = _tenantDomainValidator;
|
||||
settings.ZendeskKey = _setupInfo.ZendeskKey;
|
||||
settings.BookTrainingEmail = _setupInfo.BookTrainingEmail;
|
||||
settings.DocumentationEmail = _setupInfo.DocumentationEmail;
|
||||
settings.SocketUrl = _configuration["web:hub:url"] ?? "";
|
||||
|
||||
settings.Firebase = new FirebaseDto
|
||||
{
|
||||
|
@ -1,29 +1,29 @@
|
||||
// (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
|
||||
|
||||
// (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
|
||||
|
||||
global using System.Collections.Specialized;
|
||||
global using System.Globalization;
|
||||
global using System.Net;
|
||||
@ -31,53 +31,53 @@ global using System.Net.Mail;
|
||||
global using System.Net.Sockets;
|
||||
global using System.Security;
|
||||
global using System.ServiceModel.Security;
|
||||
global using System.Text;
|
||||
global using System.Text;
|
||||
global using System.Text.Json;
|
||||
global using System.Text.Json.Serialization;
|
||||
global using System.Text.RegularExpressions;
|
||||
global using System.Web;
|
||||
|
||||
global using ASC.ActiveDirectory.Base;
|
||||
global using ASC.ActiveDirectory.Base.Settings;
|
||||
global using ASC.ActiveDirectory.ComplexOperations;
|
||||
global using System.Web;
|
||||
|
||||
global using ASC.ActiveDirectory.Base;
|
||||
global using ASC.ActiveDirectory.Base.Settings;
|
||||
global using ASC.ActiveDirectory.ComplexOperations;
|
||||
global using ASC.Api.Collections;
|
||||
global using ASC.Api.Core;
|
||||
global using ASC.Api.Core.Convention;
|
||||
global using ASC.Api.Core.Convention;
|
||||
global using ASC.Api.Core.Extensions;
|
||||
global using ASC.Api.Core.Security;
|
||||
global using ASC.Api.Settings;
|
||||
global using ASC.Api.Settings.Smtp;
|
||||
global using ASC.Api.Settings.Smtp;
|
||||
global using ASC.Api.Utils;
|
||||
global using ASC.AuditTrail;
|
||||
global using ASC.AuditTrail.Mappers;
|
||||
global using ASC.AuditTrail.Mappers;
|
||||
global using ASC.AuditTrail.Models;
|
||||
global using ASC.AuditTrail.Repositories;
|
||||
global using ASC.AuditTrail.Types;
|
||||
global using ASC.AuditTrail.Types;
|
||||
global using ASC.Common;
|
||||
global using ASC.Common.Caching;
|
||||
global using ASC.Common.Log;
|
||||
global using ASC.Web.Core.RemovePortal;
|
||||
global using ASC.Common.Mapping;
|
||||
global using ASC.Common.Log;
|
||||
global using ASC.Web.Core.RemovePortal;
|
||||
global using ASC.Common.Mapping;
|
||||
global using ASC.Common.Radicale;
|
||||
global using ASC.Common.Radicale.Core;
|
||||
global using ASC.Common.Radicale.Core;
|
||||
global using ASC.Common.Security.Authorizing;
|
||||
global using ASC.Common.Threading;
|
||||
global using ASC.Common.Utils;
|
||||
global using ASC.Common.Web;
|
||||
global using ASC.Common.Web;
|
||||
global using ASC.Core;
|
||||
global using ASC.Core.Billing;
|
||||
global using ASC.Core.Common.Configuration;
|
||||
global using ASC.Core.Common.EF;
|
||||
global using ASC.Core.Common.EF;
|
||||
global using ASC.Core.Common.Notify;
|
||||
global using ASC.Core.Common.Notify.Push;
|
||||
global using ASC.Core.Common.Quota;
|
||||
global using ASC.Core.Common.Quota.Features;
|
||||
global using ASC.Core.Common.Quota;
|
||||
global using ASC.Core.Common.Quota.Features;
|
||||
global using ASC.Core.Common.Security;
|
||||
global using ASC.Core.Common.Settings;
|
||||
global using ASC.Core.Configuration;
|
||||
global using ASC.Core.Data;
|
||||
global using ASC.Core.Data;
|
||||
global using ASC.Core.Encryption;
|
||||
global using ASC.Core.Security.Authentication;
|
||||
global using ASC.Core.Security.Authentication;
|
||||
global using ASC.Core.Tenants;
|
||||
global using ASC.Core.Users;
|
||||
global using ASC.Data.Backup;
|
||||
@ -85,21 +85,22 @@ global using ASC.Data.Backup.Contracts;
|
||||
global using ASC.Data.Backup.EF.Context;
|
||||
global using ASC.Data.Storage.Configuration;
|
||||
global using ASC.Data.Storage.Encryption;
|
||||
global using ASC.Data.Storage.Migration;
|
||||
global using ASC.EventBus.Abstractions;
|
||||
global using ASC.Data.Storage.Migration;
|
||||
global using ASC.EventBus.Abstractions;
|
||||
global using ASC.FederatedLogin;
|
||||
global using ASC.FederatedLogin.Helpers;
|
||||
global using ASC.FederatedLogin.LoginProviders;
|
||||
global using ASC.FederatedLogin.Profile;
|
||||
global using ASC.Feed;
|
||||
global using ASC.Feed;
|
||||
global using ASC.Feed.Data;
|
||||
global using ASC.Files.Core.Core;
|
||||
global using ASC.Files.Core.EF;
|
||||
global using ASC.Files.Core.Core;
|
||||
global using ASC.Files.Core.EF;
|
||||
global using ASC.Files.Core.Helpers;
|
||||
global using ASC.Files.Core.VirtualRooms;
|
||||
global using ASC.Geolocation;
|
||||
global using ASC.Files.Core.VirtualRooms;
|
||||
global using ASC.Files.Core.Security;
|
||||
global using ASC.Geolocation;
|
||||
global using ASC.IPSecurity;
|
||||
global using ASC.MessagingSystem;
|
||||
global using ASC.MessagingSystem;
|
||||
global using ASC.MessagingSystem.Core;
|
||||
global using ASC.MessagingSystem.EF.Model;
|
||||
global using ASC.Notify.Cron;
|
||||
@ -107,19 +108,19 @@ global using ASC.Security.Cryptography;
|
||||
global using ASC.Web.Api;
|
||||
global using ASC.Web.Api.ApiModel.RequestsDto;
|
||||
global using ASC.Web.Api.ApiModel.ResponseDto;
|
||||
global using ASC.Web.Api.ApiModels.RequestsDto;
|
||||
global using ASC.Web.Api.ApiModels.ResponseDto;
|
||||
global using ASC.Web.Api.ApiModels.RequestsDto;
|
||||
global using ASC.Web.Api.ApiModels.ResponseDto;
|
||||
global using ASC.Web.Api.Core;
|
||||
global using ASC.Web.Api.Log;
|
||||
global using ASC.Web.Api.Mapping;
|
||||
global using ASC.Web.Api.Log;
|
||||
global using ASC.Web.Api.Mapping;
|
||||
global using ASC.Web.Api.Models;
|
||||
global using ASC.Web.Api.Routing;
|
||||
global using ASC.Web.Core;
|
||||
global using ASC.Web.Core.Helpers;
|
||||
global using ASC.Web.Core.Mobile;
|
||||
global using ASC.Web.Core.Notify;
|
||||
global using ASC.Web.Core.Notify;
|
||||
global using ASC.Web.Core.PublicResources;
|
||||
global using ASC.Web.Core.Quota;
|
||||
global using ASC.Web.Core.Quota;
|
||||
global using ASC.Web.Core.Sms;
|
||||
global using ASC.Web.Core.Users;
|
||||
global using ASC.Web.Core.Utility;
|
||||
@ -128,37 +129,37 @@ global using ASC.Web.Core.WebZones;
|
||||
global using ASC.Web.Core.WhiteLabel;
|
||||
global using ASC.Web.Files.Services.DocumentService;
|
||||
global using ASC.Web.Studio.Core;
|
||||
global using ASC.Web.Studio.Core.Notify;
|
||||
global using ASC.Web.Studio.Core.Notify;
|
||||
global using ASC.Web.Studio.Core.Quota;
|
||||
global using ASC.Web.Studio.Core.SMS;
|
||||
global using ASC.Web.Studio.Core.Statistic;
|
||||
global using ASC.Web.Studio.Core.TFA;
|
||||
global using ASC.Web.Studio.Core.TFA;
|
||||
global using ASC.Web.Studio.UserControls.CustomNavigation;
|
||||
global using ASC.Web.Studio.UserControls.FirstTime;
|
||||
global using ASC.Web.Studio.UserControls.Management;
|
||||
global using ASC.Web.Studio.UserControls.Management.SingleSignOnSettings;
|
||||
global using ASC.Web.Studio.Utility;
|
||||
global using ASC.Webhooks.Core;
|
||||
global using ASC.Webhooks.Core.EF.Model;
|
||||
|
||||
global using ASC.Webhooks.Core.EF.Model;
|
||||
|
||||
global using Autofac;
|
||||
|
||||
global using AutoMapper;
|
||||
|
||||
|
||||
global using AutoMapper;
|
||||
|
||||
global using Google.Authenticator;
|
||||
|
||||
|
||||
global using MailKit.Security;
|
||||
|
||||
|
||||
global using Microsoft.AspNetCore.Authorization;
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Microsoft.Extensions.Caching.Memory;
|
||||
global using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
global using Microsoft.Extensions.Primitives;
|
||||
|
||||
|
||||
global using MimeKit;
|
||||
|
||||
global using static ASC.ActiveDirectory.Base.Settings.LdapSettings;
|
||||
global using static ASC.Security.Cryptography.EmailValidationKeyProvider;
|
||||
|
||||
global using SecurityContext = ASC.Core.SecurityContext;
|
||||
|
||||
global using static ASC.ActiveDirectory.Base.Settings.LdapSettings;
|
||||
global using static ASC.Security.Cryptography.EmailValidationKeyProvider;
|
||||
|
||||
global using SecurityContext = ASC.Core.SecurityContext;
|
||||
|
@ -24,101 +24,182 @@
|
||||
// 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.Web.Core;
|
||||
namespace ASC.Web.Core;
|
||||
|
||||
[Scope]
|
||||
public class BruteForceLoginManager
|
||||
{
|
||||
private readonly SettingsManager _settingsManager;
|
||||
private readonly UserManager _userManager;
|
||||
private readonly TenantManager _tenantManager;
|
||||
private readonly SettingsManager _settingsManager;
|
||||
private readonly UserManager _userManager;
|
||||
private readonly TenantManager _tenantManager;
|
||||
private readonly IDistributedCache _distributedCache;
|
||||
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
|
||||
|
||||
public BruteForceLoginManager(SettingsManager settingsManager, UserManager userManager, TenantManager tenantManager, IDistributedCache distributedCache)
|
||||
{
|
||||
_settingsManager = settingsManager;
|
||||
_userManager = userManager;
|
||||
_tenantManager = tenantManager;
|
||||
_settingsManager = settingsManager;
|
||||
_userManager = userManager;
|
||||
_tenantManager = tenantManager;
|
||||
_distributedCache = distributedCache;
|
||||
}
|
||||
|
||||
public async Task<(bool, UserInfo)> AttemptAsync(string login, string passwordHash, string requestIp)
|
||||
{
|
||||
UserInfo user = null;
|
||||
public async Task<(bool, bool)> IncrementAsync(string key, string requestIp, bool throwException, string exceptionMessage = null)
|
||||
{
|
||||
var blockCacheKey = GetBlockCacheKey(key, requestIp);
|
||||
|
||||
if (GetFromCache<string>(blockCacheKey) != null)
|
||||
{
|
||||
if (throwException)
|
||||
{
|
||||
throw new BruteForceCredentialException(exceptionMessage);
|
||||
}
|
||||
|
||||
var showRecaptcha = true;
|
||||
|
||||
var blockCacheKey = GetBlockCacheKey(login, requestIp);
|
||||
|
||||
if (GetFromCache<string>(blockCacheKey) != null)
|
||||
{
|
||||
throw new BruteForceCredentialException();
|
||||
}
|
||||
return (false, true);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
|
||||
if (GetFromCache<string>(blockCacheKey) != null)
|
||||
{
|
||||
throw new BruteForceCredentialException(exceptionMessage);
|
||||
}
|
||||
|
||||
var historyCacheKey = GetHistoryCacheKey(key, requestIp);
|
||||
var settings = new LoginSettingsWrapper(_settingsManager.Load<LoginSettings>());
|
||||
var history = GetFromCache<List<DateTime>>(historyCacheKey) ?? new List<DateTime>();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var checkTime = now.Subtract(settings.CheckPeriod);
|
||||
|
||||
history = history.Where(item => item > checkTime).ToList();
|
||||
history.Add(now);
|
||||
|
||||
var showRecaptcha = history.Count > settings.AttemptCount - 1;
|
||||
|
||||
if (history.Count > settings.AttemptCount)
|
||||
{
|
||||
SetToCache(blockCacheKey, "block", now.Add(settings.BlockTime));
|
||||
await _distributedCache.RemoveAsync(historyCacheKey);
|
||||
|
||||
if (throwException)
|
||||
{
|
||||
throw new BruteForceCredentialException(exceptionMessage);
|
||||
}
|
||||
|
||||
return (false, showRecaptcha);
|
||||
}
|
||||
|
||||
SetToCache(historyCacheKey, history, now.Add(settings.CheckPeriod));
|
||||
|
||||
return (true, showRecaptcha);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DecrementAsync(string key, string requestIp)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
|
||||
var settings = new LoginSettingsWrapper(_settingsManager.Load<LoginSettings>());
|
||||
var historyCacheKey = GetHistoryCacheKey(key, requestIp);
|
||||
var history = GetFromCache<List<DateTime>>(historyCacheKey) ?? new List<DateTime>();
|
||||
|
||||
if (history.Count > 0)
|
||||
{
|
||||
history.RemoveAt(history.Count - 1);
|
||||
}
|
||||
|
||||
SetToCache(historyCacheKey, history, DateTime.UtcNow.Add(settings.CheckPeriod));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool, UserInfo)> AttemptAsync(string login, string passwordHash, string requestIp)
|
||||
{
|
||||
UserInfo user = null;
|
||||
|
||||
var showRecaptcha = true;
|
||||
|
||||
var blockCacheKey = GetBlockCacheKey(login, requestIp);
|
||||
|
||||
if (GetFromCache<string>(blockCacheKey) != null)
|
||||
{
|
||||
throw new BruteForceCredentialException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
if (GetFromCache<string>(blockCacheKey) != null)
|
||||
{
|
||||
throw new BruteForceCredentialException();
|
||||
}
|
||||
|
||||
string historyCacheKey = null;
|
||||
var now = DateTime.UtcNow;
|
||||
LoginSettingsWrapper settings = null;
|
||||
List<DateTime> history = null;
|
||||
var secretEmail = SetupInfo.IsSecretEmail(login);
|
||||
|
||||
if (!secretEmail)
|
||||
{
|
||||
historyCacheKey = GetHistoryCacheKey(login, requestIp);
|
||||
|
||||
settings = new LoginSettingsWrapper(await _settingsManager.LoadAsync<LoginSettings>());
|
||||
var checkTime = now.Subtract(settings.CheckPeriod);
|
||||
|
||||
history = GetFromCache<List<DateTime>>(historyCacheKey) ?? new List<DateTime>();
|
||||
history = history.Where(item => item > checkTime).ToList();
|
||||
history.Add(now);
|
||||
|
||||
showRecaptcha = history.Count > settings.AttemptCount - 1;
|
||||
|
||||
if (history.Count > settings.AttemptCount)
|
||||
{
|
||||
SetToCache(blockCacheKey, "block", now.Add(settings.BlockTime));
|
||||
_distributedCache.Remove(historyCacheKey);
|
||||
throw new BruteForceCredentialException();
|
||||
}
|
||||
|
||||
SetToCache(historyCacheKey, history, now.Add(settings.CheckPeriod));
|
||||
}
|
||||
|
||||
user = await _userManager.GetUsersByPasswordHashAsync(
|
||||
await _tenantManager.GetCurrentTenantIdAsync(),
|
||||
login,
|
||||
passwordHash);
|
||||
|
||||
if (user == null || !_userManager.UserExists(user))
|
||||
{
|
||||
throw new Exception("user not found");
|
||||
}
|
||||
|
||||
if (!secretEmail)
|
||||
{
|
||||
history.RemoveAt(history.Count - 1);
|
||||
|
||||
SetToCache(historyCacheKey, history, now.Add(settings.CheckPeriod));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
|
||||
string historyCacheKey = null;
|
||||
var now = DateTime.UtcNow;
|
||||
LoginSettingsWrapper settings = null;
|
||||
List<DateTime> history = null;
|
||||
var secretEmail = SetupInfo.IsSecretEmail(login);
|
||||
|
||||
if (!secretEmail)
|
||||
{
|
||||
historyCacheKey = GetHistoryCacheKey(login, requestIp);
|
||||
|
||||
settings = new LoginSettingsWrapper(await _settingsManager.LoadAsync<LoginSettings>());
|
||||
var checkTime = now.Subtract(settings.CheckPeriod);
|
||||
|
||||
history = GetFromCache<List<DateTime>>(historyCacheKey) ?? new List<DateTime>();
|
||||
history = history.Where(item => item > checkTime).ToList();
|
||||
history.Add(now);
|
||||
|
||||
showRecaptcha = history.Count > settings.AttemptCount - 1;
|
||||
|
||||
if (history.Count > settings.AttemptCount)
|
||||
{
|
||||
SetToCache(blockCacheKey, "block", now.Add(settings.BlockTime));
|
||||
_distributedCache.Remove(historyCacheKey);
|
||||
throw new BruteForceCredentialException();
|
||||
}
|
||||
|
||||
SetToCache(historyCacheKey, history, now.Add(settings.CheckPeriod));
|
||||
}
|
||||
|
||||
user = await _userManager.GetUsersByPasswordHashAsync(
|
||||
await _tenantManager.GetCurrentTenantIdAsync(),
|
||||
login,
|
||||
passwordHash);
|
||||
|
||||
if (user == null || !_userManager.UserExists(user))
|
||||
{
|
||||
throw new Exception("user not found");
|
||||
}
|
||||
|
||||
if (!secretEmail)
|
||||
{
|
||||
history.RemoveAt(history.Count - 1);
|
||||
|
||||
SetToCache(historyCacheKey, history, now.Add(settings.CheckPeriod));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
return (showRecaptcha, user);
|
||||
}
|
||||
|
||||
@ -151,4 +232,4 @@ public class BruteForceLoginManager
|
||||
private static string GetBlockCacheKey(string login, string requestIp) => $"loginblock/{login}/{requestIp}";
|
||||
|
||||
private static string GetHistoryCacheKey(string login, string requestIp) => $"loginsec/{login}/{requestIp}";
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,9 @@ namespace ASC.Web.Core;
|
||||
public enum CookiesType
|
||||
{
|
||||
AuthKey,
|
||||
SocketIO
|
||||
SocketIO,
|
||||
ShareLink,
|
||||
AnonymousSessionKey
|
||||
}
|
||||
|
||||
[Scope]
|
||||
@ -43,6 +45,8 @@ public class CookiesManager
|
||||
{
|
||||
private const string AuthCookiesName = "asc_auth_key";
|
||||
private const string SocketIOCookiesName = "socketio.sid";
|
||||
private const string ShareLinkCookiesName = "sharelink";
|
||||
private const string AnonymousSessionKeyCookiesName = "anonymous_session_key";
|
||||
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly UserManager _userManager;
|
||||
@ -73,7 +77,7 @@ public class CookiesManager
|
||||
_messageService = messageService;
|
||||
}
|
||||
|
||||
public async Task SetCookiesAsync(CookiesType type, string value, bool session = false)
|
||||
public async Task SetCookiesAsync(CookiesType type, string value, bool session = false, string itemId = null)
|
||||
{
|
||||
if (_httpContextAccessor?.HttpContext == null)
|
||||
{
|
||||
@ -101,7 +105,14 @@ public class CookiesManager
|
||||
}
|
||||
}
|
||||
|
||||
_httpContextAccessor.HttpContext.Response.Cookies.Append(GetCookiesName(type), value, options);
|
||||
var cookieName = GetCookiesName(type);
|
||||
|
||||
if (!string.IsNullOrEmpty(itemId))
|
||||
{
|
||||
cookieName += itemId;
|
||||
}
|
||||
|
||||
_httpContextAccessor.HttpContext.Response.Cookies.Append(cookieName, value, options);
|
||||
}
|
||||
|
||||
public string GetCookies(CookiesType type)
|
||||
@ -118,16 +129,50 @@ public class CookiesManager
|
||||
return "";
|
||||
}
|
||||
|
||||
public void ClearCookies(CookiesType type)
|
||||
public string GetCookies(CookiesType type, string itemId, bool allowHeader = false)
|
||||
{
|
||||
if (_httpContextAccessor?.HttpContext == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var cookieName = GetCookiesName(type);
|
||||
|
||||
if (!string.IsNullOrEmpty(itemId))
|
||||
{
|
||||
cookieName += itemId;
|
||||
}
|
||||
|
||||
if (_httpContextAccessor.HttpContext.Request.Cookies.ContainsKey(cookieName))
|
||||
{
|
||||
return _httpContextAccessor.HttpContext.Request.Cookies[cookieName] ?? string.Empty;
|
||||
}
|
||||
|
||||
if (allowHeader)
|
||||
{
|
||||
return _httpContextAccessor.HttpContext.Request.Headers[cookieName].FirstOrDefault() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public void ClearCookies(CookiesType type, string itemId = null)
|
||||
{
|
||||
if (_httpContextAccessor?.HttpContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_httpContextAccessor.HttpContext.Request.Cookies.ContainsKey(GetCookiesName(type)))
|
||||
var cookieName = GetCookiesName(type);
|
||||
|
||||
if (!string.IsNullOrEmpty(itemId))
|
||||
{
|
||||
_httpContextAccessor.HttpContext.Response.Cookies.Delete(GetCookiesName(type), new CookieOptions() { Expires = DateTime.Now.AddDays(-3) });
|
||||
cookieName += itemId;
|
||||
}
|
||||
|
||||
if (_httpContextAccessor.HttpContext.Request.Cookies.ContainsKey(cookieName))
|
||||
{
|
||||
_httpContextAccessor.HttpContext.Response.Cookies.Delete(cookieName, new CookieOptions() { Expires = DateTime.Now.AddDays(-3) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,7 +328,8 @@ public class CookiesManager
|
||||
{
|
||||
CookiesType.AuthKey => AuthCookiesName,
|
||||
CookiesType.SocketIO => SocketIOCookiesName,
|
||||
|
||||
CookiesType.ShareLink => ShareLinkCookiesName,
|
||||
CookiesType.AnonymousSessionKey => AnonymousSessionKeyCookiesName,
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user