Merge branch 'develop' into feature/rtl-interface-direction

This commit is contained in:
Aleksandr Lushkin 2023-07-17 09:13:00 +02:00
commit 80bf83f2e2
65 changed files with 3264 additions and 1906 deletions

View File

@ -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;

View File

@ -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

View File

@ -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";
}

View File

@ -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));

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -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);
});
};
})
}
};

View File

@ -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`);

View File

@ -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
}
},
{

View File

@ -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

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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)));
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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>();

View File

@ -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)

View 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,
};
}

View File

@ -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");
});
}
}
}

View File

@ -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}";
}
}

View File

@ -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>

View File

@ -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
}

View 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
}

View File

@ -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,
}
}

View File

@ -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;
}

View File

@ -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>
{

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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)

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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];

View File

@ -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);
}
}

View File

@ -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

View File

@ -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>

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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)
{

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using 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;
}
}
}

View File

@ -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>();
}
}
}

View File

@ -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

View File

@ -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)

View 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; }
}

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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()
{

View File

@ -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);
}
}

View File

@ -155,6 +155,7 @@ public class SettingsController : ApiControllerBase
///
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("settings")]
public FilesSettingsHelper GetFilesSettings()
{

View File

@ -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
};

View File

@ -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);

View File

@ -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
{

View File

@ -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;

View File

@ -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}";
}
}

View File

@ -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,
};