Merge branch 'feature/files' into feature/media-viewer

This commit is contained in:
NikolayRechkin 2020-05-21 10:10:08 +03:00
commit e8a0bbcd73
78 changed files with 4768 additions and 698 deletions

View File

@ -52,6 +52,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppLimit.CloudComputing.Sha
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Files.Service", "products\ASC.Files\Service\ASC.Files.Service.csproj", "{5D41FFFF-816C-40B2-95CD-E2DDDCB83784}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASC.ApiSystem", "common\services\ASC.ApiSystem\ASC.ApiSystem.csproj", "{C2BB03A0-C35B-433F-96D4-3A06CBC06AD7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -150,6 +152,10 @@ Global
{5D41FFFF-816C-40B2-95CD-E2DDDCB83784}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D41FFFF-816C-40B2-95CD-E2DDDCB83784}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D41FFFF-816C-40B2-95CD-E2DDDCB83784}.Release|Any CPU.Build.0 = Release|Any CPU
{C2BB03A0-C35B-433F-96D4-3A06CBC06AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2BB03A0-C35B-433F-96D4-3A06CBC06AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2BB03A0-C35B-433F-96D4-3A06CBC06AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2BB03A0-C35B-433F-96D4-3A06CBC06AD7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1,2 @@
echo "RUN ASC.Notify"
call dotnet run --project ..\..\common\services\ASC.ApiSystem\ASC.ApiSystem.csproj --no-build --$STORAGE_ROOT=..\..\..\Data --log__dir=..\..\..\Logs --log__name=apisystem

View File

@ -265,7 +265,7 @@ namespace ASC.Api.Core
{
public static bool Check(this ApiContext context, string field)
{
return context == null || context.Fields == null || (context.Fields != null && context.Fields.Contains(field));
return context?.Fields == null || (context.Fields != null && context.Fields.Contains(field, StringComparer.InvariantCultureIgnoreCase));
}
}

View File

@ -28,17 +28,16 @@ using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
using ASC.Common;
using ASC.Common.Utils;
using ASC.Core;
using Newtonsoft.Json;
namespace ASC.Api.Core
{
[DataContract(Name = "date", Namespace = "")]
[JsonConverter(typeof(ApiDateTimeConverter))]
[TypeConverter(typeof(ApiDateTimeTypeConverter))]
public class ApiDateTime : IComparable<ApiDateTime>, IComparable
{
@ -324,43 +323,30 @@ namespace ASC.Api.Core
}
}
public class ApiDateTimeConverter : JsonConverter
public class ApiDateTimeConverter : JsonConverter<ApiDateTime>
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override ApiDateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (value is ApiDateTime)
if (reader.TryGetDateTime(out var result))
{
writer.WriteValue(value.ToString());
return new ApiDateTime(result, TimeSpan.Zero);
}
else
{
if (DateTime.TryParseExact(reader.GetString(), ApiDateTime.Formats, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTime))
{
return new ApiDateTime(dateTime, TimeSpan.Zero);
}
else
{
return new ApiDateTime();
}
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
public override void Write(Utf8JsonWriter writer, ApiDateTime value, JsonSerializerOptions options)
{
if (reader.ValueType != null)
{
if (reader.ValueType.Name == "String")
{
if (DateTime.TryParseExact(reader.Value?.ToString(), ApiDateTime.Formats, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result))
{
return new ApiDateTime(result, TimeSpan.Zero);
}
else
{
return new ApiDateTime();
}
}
else if (reader.ValueType.Name == "DateTime")
{
return new ApiDateTime((DateTime)reader.Value, TimeSpan.Zero);
}
}
return DateTime.MinValue;
}
public override bool CanConvert(Type objectType)
{
return typeof(ApiDateTime).IsAssignableFrom(objectType);
writer.WriteStringValue(value.ToString());
}
}

View File

@ -1,19 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace ASC.Api.Core.Core
{
public class CustomJsonOptionsWrapper : IConfigureOptions<MvcNewtonsoftJsonOptions>
{
readonly IHttpContextAccessor ServiceProvider;
public CustomJsonOptionsWrapper(IHttpContextAccessor serviceProvider)
{
ServiceProvider = serviceProvider;
}
public void Configure(MvcNewtonsoftJsonOptions options)
{
options.SerializerSettings.ContractResolver = new ResponseContractResolver(ServiceProvider);
}
}
}

View File

@ -23,20 +23,14 @@
*
*/
using System.Runtime.Serialization;
namespace ASC.Api.Collections
{
[DataContract]
public class ItemKeyValuePair<TKey, TValue>
{
[DataMember]
public TKey Key { get; set; }
[DataMember]
public TValue Value { get; set; }
}

View File

@ -1,118 +0,0 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace ASC.Api.Core
{
public class ResponseContractResolver : DefaultContractResolver
{
public IHttpContextAccessor Services { get; }
public ResponseContractResolver(IHttpContextAccessor services)
{
Services = services;
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true
};
}
protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyName == "response")
{
property.ItemConverter = new JsonStringConverter(Services);
}
return property;
}
}
public class ResponseDataContractResolver : DefaultContractResolver
{
public List<string> Props { get; }
public ResponseDataContractResolver(List<string> props)
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true
};
Props = props;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var retval = base.CreateProperties(type, memberSerialization);
retval = retval.Where(p => Props.Contains(p.PropertyName.ToLower())).ToList();
return retval;
}
}
public class JsonStringConverter : JsonConverter
{
public IHttpContextAccessor HttpContextAccessor { get; }
public JsonStringConverter(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor;
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var fields = HttpContextAccessor.HttpContext.Request.Query.GetRequestArray("fields");
if (fields != null)
{
var props = fields.Select(r => r.ToLower()).ToList();
var jsonSerializer = JsonSerializer.CreateDefault();
jsonSerializer.DateParseHandling = DateParseHandling.None;
jsonSerializer.ContractResolver = new ResponseDataContractResolver(props);
jsonSerializer.Serialize(writer, value);
return;
}
serializer.Serialize(writer, value);
}
}
}

View File

@ -1,19 +1,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
namespace ASC.Api.Core.Middleware
{
[DataContract]
public abstract class CommonApiResponse
{
[DataMember(Order = 1)]
public int Status { get; set; }
[DataMember(Order = 2)]
public HttpStatusCode StatusCode { get; set; }
protected CommonApiResponse(HttpStatusCode statusCode)
@ -32,10 +27,8 @@ namespace ASC.Api.Core.Middleware
}
}
[DataContract]
public class ErrorApiResponse : CommonApiResponse
{
[DataMember(EmitDefaultValue = false, Order = 3)]
public CommonApiError Error { get; set; }
protected internal ErrorApiResponse(HttpStatusCode statusCode, Exception error) : base(statusCode)
@ -45,16 +38,12 @@ namespace ASC.Api.Core.Middleware
}
}
[DataContract]
public class SuccessApiResponse : CommonApiResponse
{
[DataMember(EmitDefaultValue = false, Order = 0)]
public int? Count { get; set; }
[DataMember(EmitDefaultValue = false, Order = 1)]
public long? Total { get; set; }
[DataMember(EmitDefaultValue = false, Order = 3)]
public object Response { get; set; }
protected internal SuccessApiResponse(HttpStatusCode statusCode, object response, long? total = null, int? count = null) : base(statusCode)
@ -89,33 +78,24 @@ namespace ASC.Api.Core.Middleware
}
}
[DataContract]
public class CommonApiError
{
[DataMember]
public string Message { get; set; }
[DataMember]
public Type Type { get; set; }
public string Type { get; set; }
[DataMember]
public string Stack { get; set; }
[DataMember]
public int Hresult { get; set; }
[DataMember]
public IDictionary Data { get; set; }
public static CommonApiError FromException(Exception exception)
{
return new CommonApiError()
{
Message = exception.Message,
Type = exception.GetType(),
Type = exception.GetType().ToString(),
Stack = exception.StackTrace,
Hresult = exception.HResult,
Data = exception.Data
Hresult = exception.HResult
};
}
}

View File

@ -23,18 +23,12 @@
*
*/
using System.Runtime.Serialization;
namespace ASC.Web.Api.Models
{
[DataContract(Name = "contact", Namespace = "")]
public class Contact
{
[DataMember(Order = 1)]
public string Type { get; set; }
[DataMember(Order = 2)]
public string Value { get; set; }
public Contact()

View File

@ -25,7 +25,7 @@
using System;
using System.Runtime.Serialization;
using ASC.Api.Core;
using ASC.Common;
@ -36,22 +36,16 @@ using ASC.Web.Studio.Utility;
namespace ASC.Web.Api.Models
{
[DataContract(Name = "person", Namespace = "")]
public class EmployeeWraper
{
[DataMember(Order = 1)]
public Guid Id { get; set; }
[DataMember(Order = 10)]
public string DisplayName { get; set; }
[DataMember(Order = 11, EmitDefaultValue = false)]
public string Title { get; set; }
[DataMember(Order = 20)]
public string AvatarSmall { get; set; }
[DataMember(Order = 30)]
public string ProfileUrl { get; set; }
public static EmployeeWraper GetSample()

View File

@ -27,11 +27,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Linq.Expressions;
using ASC.Api.Core;
using ASC.Common;
using ASC.Core;
using ASC.Core.Common.EF;
using ASC.Core.Users;
using ASC.Web.Core;
using ASC.Web.Core.Users;
@ -39,89 +41,61 @@ using ASC.Web.Studio.Utility;
namespace ASC.Web.Api.Models
{
[DataContract(Name = "person", Namespace = "")]
public class EmployeeWraperFull : EmployeeWraper
{
[DataMember(Order = 10)]
public string FirstName { get; set; }
[DataMember(Order = 10)]
public string LastName { get; set; }
[DataMember(Order = 2)]
public string UserName { get; set; }
[DataMember(Order = 10)]
public string Email { get; set; }
[DataMember(Order = 12, EmitDefaultValue = false)]
public List<Contact> Contacts { get; set; }
[DataMember(Order = 10, EmitDefaultValue = false)]
public ApiDateTime Birthday { get; set; }
[DataMember(Order = 10, EmitDefaultValue = false)]
public string Sex { get; set; }
[DataMember(Order = 10)]
public EmployeeStatus Status { get; set; }
[DataMember(Order = 10)]
public EmployeeActivationStatus ActivationStatus { get; set; }
[DataMember(Order = 10)]
public ApiDateTime Terminated { get; set; }
[DataMember(Order = 10, EmitDefaultValue = false)]
public string Department { get; set; }
[DataMember(Order = 10, EmitDefaultValue = false)]
public ApiDateTime WorkFrom { get; set; }
[DataMember(Order = 20, EmitDefaultValue = false)]
public List<GroupWrapperSummary> Groups { get; set; }
[DataMember(Order = 10, EmitDefaultValue = false)]
public string Location { get; set; }
[DataMember(Order = 10, EmitDefaultValue = false)]
public string Notes { get; set; }
[DataMember(Order = 20)]
public string AvatarMax { get; set; }
[DataMember(Order = 20)]
public string AvatarMedium { get; set; }
[DataMember(Order = 20)]
public string Avatar { get; set; }
[DataMember(Order = 20)]
public bool IsAdmin { get; set; }
[DataMember(Order = 20)]
public bool IsLDAP { get; set; }
[DataMember(Order = 20, EmitDefaultValue = false)]
public List<string> ListAdminModules { get; set; }
[DataMember(Order = 20)]
public bool IsOwner { get; set; }
[DataMember(Order = 2)]
public bool IsVisitor { get; set; }
[DataMember(Order = 20, EmitDefaultValue = false)]
public string CultureName { get; set; }
[DataMember(Order = 11, EmitDefaultValue = false)]
public string MobilePhone { get; set; }
[DataMember(Order = 11, EmitDefaultValue = false)]
public MobilePhoneActivationStatus MobilePhoneActivationStatus { get; set; }
[DataMember(Order = 20)]
public bool IsSSO { get; set; }
public new static EmployeeWraperFull GetSample()
@ -178,6 +152,26 @@ namespace ASC.Web.Api.Models
ApiDateTimeHelper = apiDateTimeHelper;
}
public static Expression<Func<User, UserInfo>> GetExpression(ApiContext apiContext)
{
if (apiContext?.Fields == null) return null;
var newExpr = Expression.New(typeof(UserInfo));
//i => new UserInfo { ID = i.id }
var parameter = Expression.Parameter(typeof(User), "i");
var bindExprs = new List<MemberAssignment>();
if (apiContext.Check("Id"))
{
bindExprs.Add(Expression.Bind(typeof(UserInfo).GetProperty("ID"), Expression.Property(parameter, typeof(User).GetProperty("Id"))));
}
var body = Expression.MemberInit(newExpr, bindExprs);
var lambda = Expression.Lambda<Func<User, UserInfo>>(body, parameter);
return lambda;
}
public EmployeeWraperFull GetFull(UserInfo userInfo)
{
var result = new EmployeeWraperFull

View File

@ -26,6 +26,7 @@
using System;
using System.Runtime.Serialization;
using ASC.Core;
using ASC.Core.Users;
@ -46,13 +47,10 @@ namespace ASC.Web.Api.Models
}
[DataMember(Order = 2)]
public string Name { get; set; }
[DataMember(Order = 1)]
public Guid Id { get; set; }
[DataMember(Order = 9, EmitDefaultValue = true)]
public string Manager { get; set; }
public static GroupWrapperSummary GetSample()

View File

@ -27,17 +27,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Linq.Expressions;
using System.Threading;
using ASC.Common;
using ASC.Common.Caching;
using ASC.Common.Logging;
using ASC.Core.Common.EF;
using ASC.Common.Logging;
using ASC.Core.Common.EF;
using ASC.Core.Data;
using ASC.Core.Tenants;
using ASC.Core.Users;
using ASC.Core.Users;
using Microsoft.Extensions.Options;
namespace ASC.Core.Caching
{
public class UserServiceCache
@ -133,14 +134,14 @@ namespace ASC.Core.Caching
{
return tenant.ToString() + USERS + userId;
}
}
}
class ConfigureCachedUserService : IConfigureNamedOptions<CachedUserService>
{
{
public IOptionsSnapshot<EFUserService> Service { get; }
public UserServiceCache UserServiceCache { get; }
public CoreBaseSettings CoreBaseSettings { get; }
public ConfigureCachedUserService(
IOptionsSnapshot<EFUserService> service,
UserServiceCache userServiceCache,
@ -196,22 +197,22 @@ namespace ASC.Core.Caching
PhotoExpiration = TimeSpan.FromMinutes(10);
}
public CachedUserService(
EFUserService service,
CoreBaseSettings coreBaseSettings,
UserServiceCache userServiceCache
public CachedUserService(
EFUserService service,
CoreBaseSettings coreBaseSettings,
UserServiceCache userServiceCache
) : this()
{
{
Service = service ?? throw new ArgumentNullException("service");
CoreBaseSettings = coreBaseSettings;
UserServiceCache = userServiceCache;
CoreBaseSettings = coreBaseSettings;
UserServiceCache = userServiceCache;
Cache = userServiceCache.Cache;
CacheUserInfoItem = userServiceCache.CacheUserInfoItem;
CacheUserPhotoItem = userServiceCache.CacheUserPhotoItem;
CacheGroupCacheItem = userServiceCache.CacheGroupCacheItem;
CacheUserGroupRefItem = userServiceCache.CacheUserGroupRefItem;
CacheUserInfoItem = userServiceCache.CacheUserInfoItem;
CacheUserPhotoItem = userServiceCache.CacheUserPhotoItem;
CacheGroupCacheItem = userServiceCache.CacheGroupCacheItem;
CacheUserGroupRefItem = userServiceCache.CacheUserGroupRefItem;
TrustInterval = userServiceCache.TrustInterval;
}
}
public IDictionary<Guid, UserInfo> GetUsers(int tenant, DateTime from)
{
@ -222,19 +223,19 @@ namespace ASC.Core.Caching
}
}
public IQueryable<UserInfo> GetUsers(
int tenant,
bool isAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
EmployeeActivationStatus? activationStatus,
string text,
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
public IQueryable<UserInfo> GetUsers(
int tenant,
bool isAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
EmployeeActivationStatus? activationStatus,
string text,
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
out int count)
{
return Service.GetUsers(tenant, isAdmin, employeeStatus, includeGroups, excludeGroups, activationStatus, text, sortBy, sortOrderAsc, limit, offset, out total, out count);
@ -503,8 +504,13 @@ namespace ASC.Core.Caching
public void InvalidateCache()
{
UserServiceCache.InvalidateCache();
}
}
public UserInfo GetUser(int tenant, Guid id, Expression<Func<User, UserInfo>> exp)
{
return Service.GetUser(tenant, id, exp);
}
[Serializable]
class UserPhoto
{
@ -514,21 +520,21 @@ namespace ASC.Core.Caching
public static class UserConfigExtension
{
public static DIHelper AddUserService(this DIHelper services)
{
services.TryAddSingleton(typeof(ICacheNotify<>), typeof(KafkaCache<>));
services
.AddCoreSettingsService()
.AddLoggerService()
.AddUserDbContextService();
services.TryAddScoped<EFUserService>();
services.TryAddScoped<IUserService, CachedUserService>();
{
services.TryAddSingleton(typeof(ICacheNotify<>), typeof(KafkaCache<>));
services
.AddCoreSettingsService()
.AddLoggerService()
.AddUserDbContextService();
services.TryAddScoped<EFUserService>();
services.TryAddScoped<IUserService, CachedUserService>();
services.TryAddScoped<IConfigureOptions<EFUserService>, ConfigureEFUserService>();
services.TryAddScoped<IConfigureOptions<CachedUserService>, ConfigureCachedUserService>();
services.TryAddSingleton<UserServiceCache>();
services.TryAddSingleton<UserServiceCache>();
return services;
}
}

View File

@ -26,16 +26,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Linq.Expressions;
using ASC.Collections;
using ASC.Common;
using ASC.Core.Caching;
using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Core.Users;
using ASC.Core.Users;
using Microsoft.AspNetCore.Http;
namespace ASC.Core
{
public class UserManagerConstants
@ -124,18 +126,18 @@ namespace ASC.Core
return users.ToArray();
}
public IQueryable<UserInfo> GetUsers(
bool isAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
EmployeeActivationStatus? activationStatus,
string text,
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
public IQueryable<UserInfo> GetUsers(
bool isAdmin,
EmployeeStatus? employeeStatus,
List<List<Guid>> includeGroups,
List<Guid> excludeGroups,
EmployeeActivationStatus? activationStatus,
string text,
string sortBy,
bool sortOrderAsc,
long limit,
long offset,
out int total,
out int count)
{
return UserService.GetUsers(Tenant.TenantId, isAdmin, employeeStatus, includeGroups, excludeGroups, activationStatus, text, sortBy, sortOrderAsc, limit, offset, out total, out count);
@ -187,6 +189,12 @@ namespace ASC.Core
var u = UserService.GetUser(Tenant.TenantId, id);
return u != null && !u.Removed ? u : Constants.LostUser;
}
public UserInfo GetUser(Guid id, Expression<Func<User, UserInfo>> exp)
{
if (IsSystemUser(id)) return SystemUsers[id];
var u = UserService.GetUser(Tenant.TenantId, id, exp);
return u != null && !u.Removed ? u : Constants.LostUser;
}
public UserInfo GetUsers(int tenant, string login, string passwordHash)
{
@ -270,12 +278,12 @@ namespace ASC.Core
{
throw new TenantQuotaException(string.Format("Exceeds the maximum active users ({0})", q.ActiveUsers));
}
}
if (u.Status == EmployeeStatus.Terminated && u.ID == TenantManager.GetCurrentTenant().OwnerId)
{
throw new InvalidOperationException("Can not disable tenant owner.");
}
}
if (u.Status == EmployeeStatus.Terminated && u.ID == TenantManager.GetCurrentTenant().OwnerId)
{
throw new InvalidOperationException("Can not disable tenant owner.");
}
var newUser = UserService.SaveUser(Tenant.TenantId, u);
@ -639,9 +647,9 @@ namespace ASC.Core
public static class UserManagerConfigExtension
{
public static DIHelper AddUserManagerService(this DIHelper services)
{
{
services.TryAddSingleton<UserManagerConstants>();
services.TryAddScoped<UserManager>();
services.TryAddScoped<UserManager>();
return services
.AddUserService()

View File

@ -27,6 +27,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ASC.Core.Common.EF;
using ASC.Core.Users;
namespace ASC.Core
@ -48,7 +51,9 @@ namespace ASC.Core
out int total,
out int count);
UserInfo GetUser(int tenant, Guid id);
UserInfo GetUser(int tenant, Guid id);
UserInfo GetUser(int tenant, Guid id, Expression<Func<User, UserInfo>> exp);
UserInfo GetUser(int tenant, string login, string passwordHash);

View File

@ -685,5 +685,13 @@ namespace ASC.Core.Data
result.Add(id);
return result.Distinct().ToList();
}
public UserInfo GetUser(int tenant, Guid id, Expression<Func<User, UserInfo>> exp)
{
return GetUserQuery(UserDbContext, tenant, default)
.Where(r => r.Id == id)
.Select(exp ?? FromUserToUserInfo)
.FirstOrDefault();
}
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyTitle>ASC.ApiSystem</AssemblyTitle>
<Company>Ascensio System SIA</Company>
<Product>ASC.ApiSystem</Product>
<Copyright>(c) Ascensio System SIA. All rights reserved</Copyright>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Classes\EnableCorsAppSettingsAttribute .cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\web\ASC.Web.Core\ASC.Web.Core.csproj" />
<ProjectReference Include="..\..\ASC.Common\ASC.Common.csproj" />
<ProjectReference Include="..\..\ASC.Core.Common\ASC.Core.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,45 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System.Web.Http.Filters;
namespace ASC.ApiSystem.Classes
{
public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response != null)
{
actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
//actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Headers", "*");
//actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
}
base.OnActionExecuted(actionExecutedContext);
}
}
}

View File

@ -0,0 +1,172 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Web.Core.Helpers;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace ASC.ApiSystem.Classes
{
public class AuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public AuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) :
base(options, logger, encoder, clock)
{
}
public AuthHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IConfiguration configuration,
IOptionsMonitor<ILog> option,
ApiSystemHelper apiSystemHelper) :
base(options, logger, encoder, clock)
{
Configuration = configuration;
Log = option.Get("ASC.ApiSystem");
ApiSystemHelper = apiSystemHelper;
}
private ILog Log { get; }
private IConfiguration Configuration { get; }
private ApiSystemHelper ApiSystemHelper { get; }
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (Convert.ToBoolean(Configuration[Scheme.Name] ?? "false"))
{
Log.DebugFormat("Auth for {0} skipped", Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)));
}
try
{
Context.Request.Headers.TryGetValue("Authorization", out var headers);
var header = headers.FirstOrDefault();
if (string.IsNullOrEmpty(header))
{
Log.Debug("Auth header is NULL");
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Unauthorized.ToString())));
}
var substring = "ASC";
if (!header.StartsWith(substring, StringComparison.InvariantCultureIgnoreCase))
{
var splitted = header.Substring(substring.Length).Trim().Split(':', StringSplitOptions.RemoveEmptyEntries);
if (splitted.Length < 3)
{
Log.DebugFormat("Auth failed: invalid token {0}.", header);
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Unauthorized.ToString())));
}
var pkey = splitted[0];
var date = splitted[1];
var orighash = splitted[2];
Log.Debug("Variant of correct auth:" + ApiSystemHelper.CreateAuthToken(pkey));
if (!string.IsNullOrWhiteSpace(date))
{
var timestamp = DateTime.ParseExact(date, "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
var trustInterval = TimeSpan.FromMinutes(Convert.ToDouble(Configuration["auth:trust-interval"] ?? "5"));
if (DateTime.UtcNow > timestamp.Add(trustInterval))
{
Log.DebugFormat("Auth failed: invalid timesatmp {0}, now {1}.", timestamp, DateTime.UtcNow);
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Forbidden.ToString())));
}
}
var skey = Configuration["core:machinekey"];
using var hasher = new HMACSHA1(Encoding.UTF8.GetBytes(skey));
var data = string.Join("\n", date, pkey);
var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(data));
if (WebEncoders.Base64UrlEncode(hash) != orighash && Convert.ToBase64String(hash) != orighash)
{
Log.DebugFormat("Auth failed: invalid token {0}, expect {1} or {2}.", orighash, WebEncoders.Base64UrlEncode(hash), Convert.ToBase64String(hash));
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Forbidden.ToString())));
}
}
else
{
Log.DebugFormat("Auth failed: invalid auth header. Sheme: {0}, parameter: {1}.", Scheme.Name, header);
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Forbidden.ToString())));
}
}
catch (Exception ex)
{
Log.Error(ex);
return Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.InternalServerError.ToString())));
}
Log.InfoFormat("Auth success {0}", Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)));
}
}
public static class AuthAllowskipHandlerExtension
{
public static DIHelper AddAuthAllowskipHandler(this DIHelper services)
{
return services.AddApiSystemHelper();
}
}
}

View File

@ -0,0 +1,97 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using ASC.Common;
using Microsoft.Extensions.Configuration;
namespace ASC.ApiSystem.Classes
{
public class CommonConstants
{
public CommonConstants(IConfiguration configuration)
{
DefaultCulture = new CultureInfo(DefaultLanguage);
RecaptchaRequired = Convert.ToBoolean(configuration["recaptcha:required"] ?? "true");
var appKeys = configuration["web:app:keys"];
if (!string.IsNullOrEmpty(appKeys))
{
AppSecretKeys = appKeys.Split(',', ';')
.Select(x => x.Trim().ToLower())
.ToList();
}
else
{
AppSecretKeys = new List<string>();
}
AutotestSecretEmails = (configuration["web:autotest:secret-email"] ?? "").Trim();
MaxAttemptsCount = Convert.ToInt32(configuration["max-attempts-count"] ?? "10");
MaxAttemptsTimeInterval = TimeSpan.Parse(Convert.ToString(configuration["max-attempts-interval"] ?? "00:05:00"));
WebApiBaseUrl = Convert.ToString(configuration["api:url"] ?? "/api/2.0/");
}
public const string BaseDbConnKeyString = "core";
public const string DefaultLanguage = "en-US";
public CultureInfo DefaultCulture { get; }
public bool RecaptchaRequired { get; }
public List<string> AppSecretKeys { get; }
public string AutotestSecretEmails { get; }
public int MaxAttemptsCount { get; }
public TimeSpan MaxAttemptsTimeInterval { get; }
public string WebApiBaseUrl { get; }
}
public static class CommonControllerExtention
{
public static DIHelper AddCommonConstants(this DIHelper services)
{
services.TryAddSingleton<CommonConstants>();
return services;
}
}
}

View File

@ -0,0 +1,349 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using ASC.ApiSystem.Classes;
using ASC.ApiSystem.Interfaces;
using ASC.ApiSystem.Models;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Common.Utils;
using ASC.Core;
using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
using ASC.Core.Users;
using ASC.Security.Cryptography;
using ASC.Web.Core.Helpers;
using ASC.Web.Core.Users;
using ASC.Web.Studio.Utility;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
namespace ASC.ApiSystem.Controllers
{
public class CommonMethods
{
public IHttpContextAccessor HttpContextAccessor { get; }
private IConfiguration Configuration { get; }
private ILog Log { get; }
private CoreSettings CoreSettings { get; }
private CommonLinkUtility CommonLinkUtility { get; }
private EmailValidationKeyProvider EmailValidationKeyProvider { get; }
private TimeZoneConverter TimeZoneConverter { get; }
private CommonConstants CommonConstants { get; }
private HostedSolution HostedSolution { get; }
private IMemoryCache MemoryCache { get; }
public CommonMethods(
IHttpContextAccessor httpContextAccessor,
IConfiguration configuration,
IOptionsMonitor<ILog> option,
CoreSettings coreSettings,
CommonLinkUtility commonLinkUtility,
EmailValidationKeyProvider emailValidationKeyProvider,
TimeZoneConverter timeZoneConverter, CommonConstants commonConstants,
IMemoryCache memoryCache,
IOptionsSnapshot<HostedSolution> hostedSolutionOptions)
{
HttpContextAccessor = httpContextAccessor;
Configuration = configuration;
Log = option.Get("ASC.ApiSystem");
CoreSettings = coreSettings;
CommonLinkUtility = commonLinkUtility;
EmailValidationKeyProvider = emailValidationKeyProvider;
TimeZoneConverter = timeZoneConverter;
CommonConstants = commonConstants;
MemoryCache = memoryCache;
HostedSolution = hostedSolutionOptions.Get(CommonConstants.BaseDbConnKeyString);
}
public object ToTenantWrapper(Tenant t)
{
return new
{
created = t.CreatedDateTime,
domain = t.GetTenantDomain(CoreSettings),
hostedRegion = t.HostedRegion,
industry = t.Industry,
language = t.Language,
name = t.Name,
ownerId = t.OwnerId,
partnerId = t.PartnerId,
paymentId = t.PaymentId,
portalName = t.TenantAlias,
status = t.Status.ToString(),
tenantId = t.TenantId,
timeZoneName = TimeZoneConverter.GetTimeZone(t.TimeZone).DisplayName,
};
}
public string CreateReference(string requestUriScheme, string tenantDomain, string email, bool first = false, string module = "", bool sms = false)
{
return string.Format("{0}{1}{2}/{3}{4}{5}{6}",
requestUriScheme,
Uri.SchemeDelimiter,
tenantDomain,
CommonLinkUtility.GetConfirmationUrlRelative(email, ConfirmType.Auth, (first ? "true" : "") + module + (sms ? "true" : "")),
first ? "&first=true" : "",
string.IsNullOrEmpty(module) ? "" : "&module=" + module,
sms ? "&sms=true" : ""
);
}
public bool SendCongratulations(string requestUriScheme, Tenant tenant, bool skipWelcome, out string url)
{
var validationKey = EmailValidationKeyProvider.GetEmailKey(tenant.TenantId, tenant.OwnerId.ToString() + ConfirmType.Auth);
url = string.Format("{0}{1}{2}{3}{4}?userid={5}&key={6}",
requestUriScheme,
Uri.SchemeDelimiter,
tenant.GetTenantDomain(CoreSettings),
CommonConstants.WebApiBaseUrl,
"portal/sendcongratulations",
tenant.OwnerId,
validationKey);
if (skipWelcome)
{
Log.DebugFormat("congratulations skiped");
return false;
}
var webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Method = WebRequestMethods.Http.Post;
webRequest.Accept = "application/x-www-form-urlencoded";
webRequest.ContentLength = 0;
try
{
using var response = webRequest.GetResponse();
using var stream = response.GetResponseStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
var result = reader.ReadToEnd();
Log.DebugFormat("congratulations result = {0}", result);
var resObj = JObject.Parse(result);
if (resObj["errors"] != null && resObj["errors"].HasValues)
{
throw new Exception(result);
}
}
catch (Exception ex)
{
Log.Error("SendCongratulations error", ex);
return false;
}
url = null;
return true;
}
public bool GetTenant(IModel model, out Tenant tenant)
{
if (model != null && model.TenantId.HasValue)
{
tenant = HostedSolution.GetTenant(model.TenantId.Value);
return true;
}
if (model != null && !string.IsNullOrEmpty((model.PortalName ?? "").Trim()))
{
tenant = HostedSolution.GetTenant((model.PortalName ?? "").Trim());
return true;
}
tenant = null;
return false;
}
public bool IsTestEmail(string email)
{
var regex = new Regex(CommonConstants.AutotestSecretEmails, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
return regex.IsMatch((email ?? "").ToLower());
}
public bool CheckMuchRegistration(TenantModel model, string clientIP, Stopwatch sw)
{
if (IsTestEmail(model.Email)) return false;
Log.DebugFormat("clientIP = {0}", clientIP);
var cacheKey = "ip_" + clientIP;
if (MemoryCache.TryGetValue(cacheKey, out int ipAttemptsCount))
{
MemoryCache.Remove(cacheKey);
}
ipAttemptsCount++;
MemoryCache.Set(
// String that represents the name of the cache item,
// could be any string
cacheKey,
// Something to store in the cache
ipAttemptsCount,
new MemoryCacheEntryOptions
{
// Will not use absolute cache expiration
AbsoluteExpiration = DateTime.MaxValue,
// Cache will expire after one hour
// You can change this time interval according
// to your requriements
SlidingExpiration = CommonConstants.MaxAttemptsTimeInterval,
// Cache will not be removed before expired
Priority = CacheItemPriority.NeverRemove
});
if (ipAttemptsCount <= CommonConstants.MaxAttemptsCount) return false;
Log.DebugFormat("PortalName = {0}; Too much reqests from ip: {1}", model.PortalName, clientIP);
sw.Stop();
return true;
}
public string GetClientIp()
{
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
//TODO: check old version
//if (request.Properties.ContainsKey("MS_HttpContext"))
//{
// return ((HttpContextWrapper)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
//}
//if (request.Properties.ContainsKey(RemoteEndpointMessageProperty.Name))
//{
// var prop = (RemoteEndpointMessageProperty)request.Properties[RemoteEndpointMessageProperty.Name];
// return prop.Address;
//}
//return null;
}
public bool ValidateRecaptcha(string response, string ip)
{
try
{
var data = string.Format("secret={0}&remoteip={1}&response={2}", Configuration["recaptcha:private-key"], ip, response);
var url = Configuration["recaptcha:verify-url"] ?? "https://www.google.com/recaptcha/api/siteverify";
var webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Method = WebRequestMethods.Http.Post;
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.ContentLength = data.Length;
using (var writer = new StreamWriter(webRequest.GetRequestStream()))
{
writer.Write(data);
}
using var webResponse = webRequest.GetResponse();
using var reader = new StreamReader(webResponse.GetResponseStream());
var resp = reader.ReadToEnd();
var resObj = JObject.Parse(resp);
if (resObj["success"] != null && resObj.Value<bool>("success"))
{
return true;
}
else
{
Log.DebugFormat("Recaptcha error: {0}", resp);
}
if (resObj["error-codes"] != null && resObj["error-codes"].HasValues)
{
Log.DebugFormat("Recaptcha api returns errors: {0}", resp);
}
}
catch (Exception ex)
{
Log.Error(ex);
}
return false;
}
}
public static class CommonMethodsExtention
{
public static DIHelper AddCommonMethods(this DIHelper services)
{
services.TryAddScoped<CommonMethods>();
return services
.AddCoreSettingsService()
.AddCommonLinkUtilityService()
.AddEmailValidationKeyProviderService()
.AddApiSystemHelper()
.AddTenantManagerService()
.AddUserFormatter()
.AddUserManagerWrapperService()
.AddSettingsManagerService()
.AddSecurityContextService()
.AddHostedSolutionService();
}
}
}

View File

@ -0,0 +1,45 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.ComponentModel.DataAnnotations;
namespace ASC.ApiSystem.Classes
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class EmailAttribute : DataTypeAttribute
{
public EmailAttribute() : base(DataType.EmailAddress)
{
ErrorMessage = "emailIncorrect";
}
public override bool IsValid(object value)
{
return value != null && (value as string).TestEmailRegex();
}
}
}

View File

@ -0,0 +1,154 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using ASC.Common;
using ASC.Common.Logging;
using Microsoft.Extensions.Options;
namespace ASC.ApiSystem.Classes
{
public class TimeZonesProvider
{
public ILog Log { get; }
public CommonConstants CommonConstants { get; }
public TimeZonesProvider(IOptionsMonitor<ILog> option, CommonConstants commonConstants)
{
Log = option.Get("ASC.ApiSystem");
CommonConstants = commonConstants;
}
#region Private
private static readonly Dictionary<string, KeyValuePair<string, string>> TimeZones = new Dictionary<string, KeyValuePair<string, string>>
{
{ "", new KeyValuePair<string, string>("Europe/London", "GMT Standard Time") },
{ "fr", new KeyValuePair<string, string>("Europe/Paris", "Romance Standard Time") },
{ "es", new KeyValuePair<string, string>("Europe/Madrid", "Romance Standard Time")},
{ "de", new KeyValuePair<string, string>("Europe/Berlin", "W. Europe Standard Time") },
{ "ru", new KeyValuePair<string, string>("Europe/Moscow", "Russian Standard Time") },
{ "lv", new KeyValuePair<string, string>("Europe/Riga", "FLE Standard Time") },
{ "pt", new KeyValuePair<string, string>("America/Cuiaba", "Central Brazilian Standard Time") },
{ "it", new KeyValuePair<string, string>("Europe/Rome", "Central European Standard Time") },
{ "tr", new KeyValuePair<string, string>("Europe/Istanbul", "GTB Standard Time") },
{ "id", new KeyValuePair<string, string>("Europe/London", "GMT Standard Time") },
{ "zh", new KeyValuePair<string, string>("Asia/Shanghai", "China Standard Time") },
{ "ja", new KeyValuePair<string, string>("Asia/Tokyo", "Tokyo Standard Time") },
{ "ko", new KeyValuePair<string, string>("Asia/Seoul", "Korea Standard Time") },
{ "az", new KeyValuePair<string, string>("Asia/Baku", "Azerbaijan Standard Time") },
{ "cs", new KeyValuePair<string, string>("Europe/Warsaw", "Central European Standard Time") },
{ "el", new KeyValuePair<string, string>("Europe/Warsaw", "Central European Standard Time") },
{ "fi", new KeyValuePair<string, string>("Europe/Warsaw", "Central European Standard Time") },
{ "pl", new KeyValuePair<string, string>("Europe/Warsaw", "Central European Standard Time") },
{ "uk", new KeyValuePair<string, string>("Europe/Kiev", "FLE Standard Time") },
{ "vi", new KeyValuePair<string, string>("Asia/Shanghai", "China Standard Time") }
};
private static readonly Dictionary<string, CultureInfo> CultureUiMap = new Dictionary<string, CultureInfo>
{
{ "", CultureInfo.GetCultureInfo("en-US") },
{ "fr", CultureInfo.GetCultureInfo("fr-FR") },
{ "es", CultureInfo.GetCultureInfo("es-ES") },
{ "de", CultureInfo.GetCultureInfo("de-DE") },
{ "ru", CultureInfo.GetCultureInfo("ru-RU") },
{ "lv", CultureInfo.GetCultureInfo("lv-LV") },
{ "pt", CultureInfo.GetCultureInfo("pt-BR") },
{ "it", CultureInfo.GetCultureInfo("it-IT") },
{ "tr", CultureInfo.GetCultureInfo("tr-TR") },
{ "id", CultureInfo.GetCultureInfo("id-ID") },
{ "zh", CultureInfo.GetCultureInfo("zh-CN") },
{ "ja", CultureInfo.GetCultureInfo("ja-JP") },
{ "ko", CultureInfo.GetCultureInfo("ko-KR") },
{ "az", CultureInfo.GetCultureInfo("az-Latn-AZ") },
{ "cs", CultureInfo.GetCultureInfo("cs-CZ") },
{ "el", CultureInfo.GetCultureInfo("el-GR") },
{ "fi", CultureInfo.GetCultureInfo("fi-FI") },
{ "pl", CultureInfo.GetCultureInfo("pl-PL") },
{ "uk", CultureInfo.GetCultureInfo("uk-UA") },
{ "vi", CultureInfo.GetCultureInfo("vi-VN") }
};
#endregion
#region Public
public TimeZoneInfo GetCurrentTimeZoneInfo(string languageKey)
{
var time = TimeZones.ContainsKey(languageKey) ? TimeZones[languageKey] : TimeZones[""];
try
{
try
{
return TimeZoneInfo.FindSystemTimeZoneById(time.Value);
}
catch (TimeZoneNotFoundException)
{
return TimeZoneInfo.FindSystemTimeZoneById(time.Key);
}
}
catch (Exception e)
{
Log.Error(e);
return TimeZoneInfo.Utc;
}
}
public CultureInfo GetCurrentCulture(string languageKey)
{
if (string.IsNullOrEmpty(languageKey))
{
return CommonConstants.DefaultCulture;
}
var culture = CultureUiMap.ContainsKey(languageKey) ? CultureUiMap[languageKey] : null;
return culture ?? CommonConstants.DefaultCulture;
}
#endregion
}
public static class TimeZonesProviderExtention
{
public static DIHelper AddTimeZonesProvider(this DIHelper services)
{
services.TryAddSingleton<TimeZonesProvider>();
return services;
}
}
}

View File

@ -0,0 +1,376 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using ASC.ApiSystem.Classes;
using ASC.ApiSystem.Models;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Tenants;
using ASC.Security.Cryptography;
using ASC.Web.Studio.Utility;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
namespace ASC.ApiSystem.Controllers
{
[ApiController]
[Route("[controller]")]
public class CalDavController : ControllerBase
{
private CommonMethods CommonMethods { get; }
private EmailValidationKeyProvider EmailValidationKeyProvider { get; }
private CoreSettings CoreSettings { get; }
private CommonConstants CommonConstants { get; }
private ILog Log { get; }
public CalDavController(
CommonMethods commonMethods,
EmailValidationKeyProvider emailValidationKeyProvider,
CoreSettings coreSettings,
CommonConstants commonConstants,
IOptionsMonitor<ILog> option)
{
CommonMethods = commonMethods;
EmailValidationKeyProvider = emailValidationKeyProvider;
CoreSettings = coreSettings;
CommonConstants = commonConstants;
Log = option.Get("ASC.ApiSystem");
}
#region For TEST api
[HttpGet("test")]
public IActionResult Check()
{
return Ok(new
{
value = "CalDav api works"
});
}
#endregion
#region API methods
[HttpGet("change_to_storage")]
public IActionResult СhangeOfCalendarStorage(string change)
{
if (!GetTenant(change, out Tenant tenant, out object error))
{
return BadRequest(error);
}
try
{
var validationKey = EmailValidationKeyProvider.GetEmailKey(tenant.TenantId, change + ConfirmType.Auth);
SendToApi(Request.Scheme, tenant, "calendar/change_to_storage", new Dictionary<string, string> { { "change", change }, { "key", validationKey } });
}
catch (Exception ex)
{
Log.Error("Error change_to_storage", ex);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
error = "apiError",
message = ex.Message
});
}
return Ok();
}
[HttpGet("caldav_delete_event")]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult CaldavDeleteEvent(string eventInfo)
{
if (!GetTenant(eventInfo, out Tenant tenant, out object error))
{
return BadRequest(error);
}
try
{
var validationKey = EmailValidationKeyProvider.GetEmailKey(tenant.TenantId, eventInfo + ConfirmType.Auth);
SendToApi(Request.Scheme, tenant, "calendar/caldav_delete_event", new Dictionary<string, string> { { "eventInfo", eventInfo }, { "key", validationKey } });
}
catch (Exception ex)
{
Log.Error("Error caldav_delete_event", ex);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
error = "apiError",
message = ex.Message
});
}
return Ok();
}
[HttpPost("is_caldav_authenticated")]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult IsCaldavAuthenticated(JObject data)
{
if (data == null)
{
Log.Error("CalDav authenticated data is null");
return BadRequest(new
{
value = "false",
error = "portalNameEmpty",
message = "Argument is required"
});
}
var username = data.Value<string>("User");
var password = data.Value<string>("Password");
if (!GetUserData(username, out string email, out Tenant tenant, out object error))
{
return BadRequest(error);
}
try
{
Log.Info(string.Format("Caldav auth user: {0}, tenant: {1}", email, tenant.TenantId));
if (email == "admin@ascsystem" && Core.Configuration.Constants.CoreSystem.ID.ToString() == password)
{
return Ok(new
{
value = "true"
});
}
var validationKey = EmailValidationKeyProvider.GetEmailKey(tenant.TenantId, email + password + ConfirmType.Auth);
var authData = string.Format("userName={0}&password={1}&key={2}",
HttpUtility.UrlEncode(email),
HttpUtility.UrlEncode(password),
HttpUtility.UrlEncode(validationKey));
SendToApi(Request.Scheme, tenant, "authentication/login", null, WebRequestMethods.Http.Post, authData);
return Ok(new
{
value = "true"
});
}
catch (Exception ex)
{
Log.Error("Caldav authenticated", ex);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
value = "false",
message = ex.Message
});
}
}
#endregion
#region private methods
private bool GetTenant(string calendarParam, out Tenant tenant, out object error)
{
tenant = null;
if (string.IsNullOrEmpty(calendarParam))
{
Log.Error("calendarParam is empty");
error = new
{
value = "false",
error = "portalNameEmpty",
message = "Argument is required"
};
return false;
}
Log.Info(string.Format("CalDav calendarParam: {0}", calendarParam));
var userParam = calendarParam.Split('/')[0];
return GetUserData(userParam, out string email, out tenant, out error);
}
private bool GetUserData(string userParam, out string email, out Tenant tenant, out object error)
{
email = null;
tenant = null;
error = null;
if (string.IsNullOrEmpty(userParam))
{
Log.Error("userParam is empty");
error = new
{
value = "false",
error = "portalNameEmpty",
message = "Argument is required"
};
return false;
}
var userData = userParam.Split('@');
if (userData.Length < 3)
{
Log.Error(string.Format("Error Caldav username: {0}", userParam));
error = new
{
value = "false",
error = "portalNameEmpty",
message = "PortalName is required"
};
return false;
}
email = string.Join("@", userData[0], userData[1]);
var tenantName = userData[2];
var baseUrl = CoreSettings.BaseDomain;
if (!string.IsNullOrEmpty(baseUrl) && tenantName.EndsWith("." + baseUrl, StringComparison.InvariantCultureIgnoreCase))
{
tenantName = tenantName.Replace("." + baseUrl, "");
}
Log.Info(string.Format("CalDav: user:{0} tenantName:{1}", userParam, tenantName));
var tenantModel = new TenantModel { PortalName = tenantName };
if (!CommonMethods.GetTenant(tenantModel, out tenant))
{
Log.Error("Model without tenant");
error = new
{
value = "false",
error = "portalNameEmpty",
message = "PortalName is required"
};
return false;
}
if (tenant == null)
{
Log.Error("Tenant not found " + tenantName);
error = new
{
value = "false",
error = "portalNameNotFound",
message = "Portal not found"
};
return false;
}
return true;
}
private void SendToApi(string requestUriScheme,
Tenant tenant,
string path,
IEnumerable<KeyValuePair<string, string>> args = null,
string httpMethod = WebRequestMethods.Http.Get,
string data = null)
{
var query = args == null
? null
: string.Join("&", args.Select(arg => HttpUtility.UrlEncode(arg.Key) + "=" + HttpUtility.UrlEncode(arg.Value)).ToArray());
var url = string.Format("{0}{1}{2}{3}{4}{5}",
requestUriScheme,
Uri.SchemeDelimiter,
tenant.GetTenantDomain(CoreSettings),
CommonConstants.WebApiBaseUrl,
path,
string.IsNullOrEmpty(query) ? "" : "?" + query);
Log.Info(string.Format("CalDav: SendToApi: {0}", url));
var webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Method = httpMethod;
webRequest.Accept = "application/json";
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.ContentLength = 0;
if (data != null)
{
webRequest.ContentLength = data.Length;
using var writer = new StreamWriter(webRequest.GetRequestStream());
writer.Write(data);
}
using (webRequest.GetResponse())
{
}
}
#endregion
}
public static class CalDavControllerExtention
{
public static DIHelper AddCalDavController(this DIHelper services)
{
return services
.AddCommonMethods()
.AddEmailValidationKeyProviderService()
.AddCommonConstants()
.AddCoreSettingsService();
}
}
}

View File

@ -0,0 +1,200 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using ASC.ApiSystem.Models;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace ASC.ApiSystem.Controllers
{
[Obsolete("CoreSettingsController is deprecated, please use SettingsController instead.")]
[ApiController]
[Route("[controller]")]
public class CoreSettingsController : ControllerBase
{
public CoreSettings CoreSettings { get; }
private ILog Log { get; }
public CoreSettingsController(
CoreSettings coreSettings,
IOptionsMonitor<ILog> option
)
{
CoreSettings = coreSettings;
Log = option.Get("ASC.ApiSystem");
}
#region For TEST api
[HttpGet("test")]
public IActionResult Check()
{
return Ok(new
{
value = "CoreSettings api works"
});
}
#endregion
#region API methods
[HttpGet("get")]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult GetSettings(int tenant, string key)
{
try
{
if (tenant < -1)
{
return BadRequest(new
{
error = "portalNameIncorrect",
message = "Tenant is incorrect"
});
}
if (string.IsNullOrEmpty(key))
{
return BadRequest(new
{
error = "params",
message = "Key is empty"
});
}
var settings = CoreSettings.GetSetting(key, tenant);
return Ok(new
{
settings
});
}
catch (ArgumentException ae)
{
Log.Error(ae);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
error = "error",
message = ae.Message
});
}
catch (Exception e)
{
Log.Error(e);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
error = "error",
message = e.Message
});
}
}
[HttpPost("save")]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult SaveSettings([FromBody] CoreSettingsModel model)
{
try
{
if (model == null || string.IsNullOrEmpty(model.Key))
{
return BadRequest(new
{
error = "params",
message = "Key is empty"
});
}
if (string.IsNullOrEmpty(model.Value))
{
return BadRequest(new
{
error = "params",
message = "Value is empty"
});
}
var tenant = model.Tenant;
if (tenant < -1)
{
tenant = -1;
}
CoreSettings.SaveSetting(model.Key, model.Value, tenant);
var settings = CoreSettings.GetSetting(model.Key, tenant);
return Ok(new
{
settings
});
}
catch (ArgumentException ae)
{
Log.Error(ae);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
error = "params",
message = ae.Message
});
}
catch (Exception e)
{
Log.Error(e);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
error = "error",
message = e.Message
});
}
}
#endregion
}
public static class ControllerExtention
{
public static DIHelper AddCoreSettingsController(this DIHelper services)
{
return services
.AddCoreSettingsService();
}
}
}

View File

@ -0,0 +1,685 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security;
using ASC.ApiSystem.Classes;
using ASC.ApiSystem.Models;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Common.Utils;
using ASC.Core;
using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
using ASC.Core.Users;
using ASC.Web.Core.Helpers;
using ASC.Web.Core.Users;
using ASC.Web.Core.Utility;
using ASC.Web.Core.Utility.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
namespace ASC.ApiSystem.Controllers
{
[ApiController]
[Route("[controller]")]
public class PortalController : ControllerBase
{
private IConfiguration Configuration { get; }
private Core.SecurityContext SecurityContext { get; }
private TenantManager TenantManager { get; }
private SettingsManager SettingsManager { get; }
private ApiSystemHelper ApiSystemHelper { get; }
private CommonMethods CommonMethods { get; }
private HostedSolution HostedSolution { get; }
private CoreSettings CoreSettings { get; }
private TenantDomainValidator TenantDomainValidator { get; }
private UserFormatter UserFormatter { get; }
private UserManagerWrapper UserManagerWrapper { get; }
private CommonConstants CommonConstants { get; }
private TimeZonesProvider TimeZonesProvider { get; }
private TimeZoneConverter TimeZoneConverter { get; }
private ILog Log { get; }
public PortalController(
IConfiguration configuration,
Core.SecurityContext securityContext,
TenantManager tenantManager,
SettingsManager settingsManager,
ApiSystemHelper apiSystemHelper,
CommonMethods commonMethods,
HostedSolution hostedSolution,
CoreSettings coreSettings,
TenantDomainValidator tenantDomainValidator,
UserFormatter userFormatter,
UserManagerWrapper userManagerWrapper,
CommonConstants commonConstants,
IOptionsMonitor<ILog> option,
TimeZonesProvider timeZonesProvider,
TimeZoneConverter timeZoneConverter)
{
Configuration = configuration;
SecurityContext = securityContext;
TenantManager = tenantManager;
SettingsManager = settingsManager;
ApiSystemHelper = apiSystemHelper;
CommonMethods = commonMethods;
HostedSolution = hostedSolution;
CoreSettings = coreSettings;
TenantDomainValidator = tenantDomainValidator;
UserFormatter = userFormatter;
UserManagerWrapper = userManagerWrapper;
CommonConstants = commonConstants;
TimeZonesProvider = timeZonesProvider;
TimeZoneConverter = timeZoneConverter;
Log = option.Get("ASC.ApiSystem");
}
#region For TEST api
[HttpGet("test")]
public IActionResult Check()
{
return Ok(new
{
value = "Portal api works"
});
}
#endregion
#region API methods
[HttpPost("register")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip.registerportal")]
public IActionResult Register(TenantModel model)
{
if (model == null)
{
return BadRequest(new
{
error = "portalNameEmpty",
message = "PortalName is required"
});
}
if (!ModelState.IsValid)
{
var message = new JArray();
foreach (var k in ModelState.Keys)
{
message.Add(ModelState[k].Errors.FirstOrDefault().ErrorMessage);
}
return BadRequest(new
{
error = "params",
message
});
}
var sw = Stopwatch.StartNew();
if (!CheckPasswordPolicy(model.Password, out object error))
{
sw.Stop();
return BadRequest(error);
}
model.FirstName = (model.FirstName ?? "").Trim();
model.LastName = (model.LastName ?? "").Trim();
if (!CheckValidName(model.FirstName + model.LastName, out error))
{
sw.Stop();
return BadRequest(error);
}
model.PortalName = (model.PortalName ?? "").Trim();
if (!CheckExistingNamePortal(model.PortalName, out error))
{
sw.Stop();
return BadRequest(error);
}
Log.DebugFormat("PortalName = {0}; Elapsed ms. CheckExistingNamePortal: {1}", model.PortalName, sw.ElapsedMilliseconds);
var clientIP = CommonMethods.GetClientIp();
if (CommonMethods.CheckMuchRegistration(model, clientIP, sw))
{
return BadRequest(new
{
error = "tooMuchAttempts",
message = "Too much attempts already"
});
}
if (!CheckRecaptcha(model, clientIP, sw, out error))
{
return BadRequest(error);
}
if (!CheckRegistrationPayment(out error))
{
return BadRequest(error);
}
var language = model.Language ?? string.Empty;
var tz = TimeZonesProvider.GetCurrentTimeZoneInfo(language);
Log.DebugFormat("PortalName = {0}; Elapsed ms. TimeZonesProvider.GetCurrentTimeZoneInfo: {1}", model.PortalName, sw.ElapsedMilliseconds);
if (!string.IsNullOrEmpty(model.TimeZoneName))
{
tz = TimeZoneConverter.GetTimeZone(model.TimeZoneName.Trim(), false) ?? tz;
Log.DebugFormat("PortalName = {0}; Elapsed ms. TimeZonesProvider.OlsonTimeZoneToTimeZoneInfo: {1}", model.PortalName, sw.ElapsedMilliseconds);
}
var lang = TimeZonesProvider.GetCurrentCulture(language);
Log.DebugFormat("PortalName = {0}; model.Language = {1}, resultLang.DisplayName = {2}", model.PortalName, language, lang.DisplayName);
var info = new TenantRegistrationInfo
{
Name = Configuration["web:portal-name"] ?? "Cloud Office Applications",
Address = model.PortalName,
Culture = lang,
FirstName = model.FirstName,
LastName = model.LastName,
Password = string.IsNullOrEmpty(model.Password) ? null : model.Password,
Email = (model.Email ?? "").Trim(),
TimeZoneInfo = tz,
MobilePhone = string.IsNullOrEmpty(model.Phone) ? null : model.Phone.Trim(),
Industry = (TenantIndustry)model.Industry,
Spam = model.Spam,
Calls = model.Calls,
Analytics = model.Analytics
};
if (!string.IsNullOrEmpty(model.PartnerId))
{
if (Guid.TryParse(model.PartnerId, out _))
{
// valid guid
info.PartnerId = model.PartnerId;
}
}
if (!string.IsNullOrEmpty(model.AffiliateId))
{
info.AffiliateId = model.AffiliateId;
}
if (!string.IsNullOrEmpty(model.Campaign))
{
info.Campaign = model.Campaign;
}
Tenant t;
try
{
/****REGISTRATION!!!*****/
if (!string.IsNullOrEmpty(ApiSystemHelper.ApiCacheUrl))
{
ApiSystemHelper.AddTenantToCache(info.Address, SecurityContext.CurrentAccount.ID);
Log.DebugFormat("PortalName = {0}; Elapsed ms. CacheController.AddTenantToCache: {1}", model.PortalName, sw.ElapsedMilliseconds);
}
HostedSolution.RegisterTenant(info, out t);
/*********/
Log.DebugFormat("PortalName = {0}; Elapsed ms. HostedSolution.RegisterTenant: {1}", model.PortalName, sw.ElapsedMilliseconds);
}
catch (Exception e)
{
sw.Stop();
Log.Error(e);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
error = "registerNewTenantError",
message = e.Message,
stacktrace = e.StackTrace
});
}
var isFirst = true;
string sendCongratulationsAddress = null;
if (!string.IsNullOrEmpty(model.Password))
{
isFirst = !CommonMethods.SendCongratulations(Request.Scheme, t, model.SkipWelcome, out sendCongratulationsAddress);
}
else if (Configuration["core:base-domain"] == "localhost")
{
try
{
/* set wizard not completed*/
TenantManager.SetCurrentTenant(t);
var settings = SettingsManager.Load<WizardSettings>();
settings.Completed = false;
SettingsManager.Save(settings);
}
catch (Exception e)
{
Log.Error(e);
}
}
var reference = CommonMethods.CreateReference(Request.Scheme, t.GetTenantDomain(CoreSettings), info.Email, isFirst);
Log.DebugFormat("PortalName = {0}; Elapsed ms. CreateReferenceByCookie...: {1}", model.PortalName, sw.ElapsedMilliseconds);
sw.Stop();
return Ok(new
{
reference,
tenant = CommonMethods.ToTenantWrapper(t),
referenceWelcome = sendCongratulationsAddress
});
}
[HttpDelete("remove")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult Remove([FromQuery] TenantModel model)
{
if (!CommonMethods.GetTenant(model, out Tenant tenant))
{
Log.Error("Model without tenant");
return BadRequest(new
{
error = "portalNameEmpty",
message = "PortalName is required"
});
}
if (tenant == null)
{
Log.Error("Tenant not found");
return BadRequest(new
{
error = "portalNameNotFound",
message = "Portal not found"
});
}
HostedSolution.RemoveTenant(tenant);
return Ok(new
{
tenant = CommonMethods.ToTenantWrapper(tenant)
});
}
[HttpPut("status")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult ChangeStatus(TenantModel model)
{
if (!CommonMethods.GetTenant(model, out Tenant tenant))
{
Log.Error("Model without tenant");
return BadRequest(new
{
error = "portalNameEmpty",
message = "PortalName is required"
});
}
if (tenant == null)
{
Log.Error("Tenant not found");
return BadRequest(new
{
error = "portalNameNotFound",
message = "Portal not found"
});
}
var active = model.Status;
if (active != TenantStatus.Active)
{
active = TenantStatus.Suspended;
}
tenant.SetStatus(active);
HostedSolution.SaveTenant(tenant);
return Ok(new
{
tenant = CommonMethods.ToTenantWrapper(tenant)
});
}
[HttpPost("validateportalname")]
[AllowCrossSiteJson]
public IActionResult CheckExistingNamePortal(TenantModel model)
{
if (model == null)
{
return BadRequest(new
{
error = "portalNameEmpty",
message = "PortalName is required"
});
}
if (!CheckExistingNamePortal((model.PortalName ?? "").Trim(), out object error))
{
return BadRequest(error);
}
return Ok(new
{
message = "portalNameReadyToRegister"
});
}
[HttpGet("get")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult GetPortals([FromQuery] TenantModel model)
{
try
{
var tenants = new List<Tenant>();
var empty = true;
if (!string.IsNullOrEmpty((model.Email ?? "").Trim()))
{
empty = false;
tenants.AddRange(HostedSolution.FindTenants((model.Email ?? "").Trim()));
}
if (!string.IsNullOrEmpty((model.PortalName ?? "").Trim()))
{
empty = false;
var tenant = HostedSolution.GetTenant((model.PortalName ?? "").Trim());
if (tenant != null)
{
tenants.Add(tenant);
}
}
if (model.TenantId.HasValue)
{
empty = false;
var tenant = HostedSolution.GetTenant(model.TenantId.Value);
if (tenant != null)
{
tenants.Add(tenant);
}
}
if (empty)
{
tenants.AddRange(HostedSolution.GetTenants(DateTime.MinValue).OrderBy(t => t.TenantId).ToList());
}
var tenantsWrapper = tenants
.Distinct()
.Where(t => t.Status == TenantStatus.Active)
.OrderBy(t => t.TenantId)
.Select(CommonMethods.ToTenantWrapper);
return Ok(new
{
tenants = tenantsWrapper
});
}
catch (Exception ex)
{
Log.Error(ex);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
error = "error",
message = ex.Message,
stacktrace = ex.StackTrace
});
}
}
#endregion
#region Validate Method
private void ValidateDomain(string domain)
{
// size
TenantDomainValidator.ValidateDomainLength(domain);
// characters
TenantDomainValidator.ValidateDomainCharacters(domain);
var sameAliasTenants = ApiSystemHelper.FindTenantsInCache(domain, SecurityContext.CurrentAccount.ID);
if (sameAliasTenants != null)
{
throw new TenantAlreadyExistsException("Address busy.", sameAliasTenants);
}
}
private bool CheckExistingNamePortal(string portalName, out object error)
{
error = null;
if (string.IsNullOrEmpty(portalName))
{
error = new { error = "portalNameEmpty", message = "PortalName is required" };
return false;
}
try
{
if (!string.IsNullOrEmpty(ApiSystemHelper.ApiCacheUrl))
{
ValidateDomain(portalName.Trim());
}
else
{
HostedSolution.CheckTenantAddress(portalName.Trim());
}
}
catch (TenantAlreadyExistsException ex)
{
error = new { error = "portalNameExist", message = "Portal already exists", variants = ex.ExistsTenants.ToArray() };
return false;
}
catch (TenantTooShortException)
{
error = new { error = "tooShortError", message = "Portal name is too short" };
return false;
}
catch (TenantIncorrectCharsException)
{
error = new { error = "portalNameIncorrect", message = "Unallowable symbols in portalName" };
return false;
}
catch (Exception ex)
{
Log.Error(ex);
error = new { error = "error", message = ex.Message, stacktrace = ex.StackTrace };
return false;
}
return true;
}
private bool CheckValidName(string name, out object error)
{
error = null;
if (string.IsNullOrEmpty(name = (name ?? "").Trim()))
{
error = new { error = "error", message = "name is required" };
return false;
}
if (!UserFormatter.IsValidUserName(name, string.Empty))
{
error = new { error = "error", message = "name is incorrect" };
return false;
}
return true;
}
private bool CheckPasswordPolicy(string pwd, out object error)
{
error = null;
//Validate Password match
if (string.IsNullOrEmpty(pwd))
{
return true;
}
var passwordSettings = (PasswordSettings)new PasswordSettings().GetDefault(Configuration);
if (!UserManagerWrapper.CheckPasswordRegex(passwordSettings, pwd))
{
error = new { error = "passPolicyError", message = "Password is incorrect" };
return false;
}
return true;
}
private bool CheckRegistrationPayment(out object error)
{
error = null;
if (Configuration["core:base-domain"] == "localhost")
{
var tenants = HostedSolution.GetTenants(DateTime.MinValue);
var firstTenant = tenants.FirstOrDefault();
if (firstTenant != null)
{
var activePortals = tenants.Count(r => r.Status != TenantStatus.Suspended && r.Status != TenantStatus.RemovePending);
var quota = HostedSolution.GetTenantQuota(firstTenant.TenantId);
if (quota.CountPortals > 0 && quota.CountPortals <= activePortals)
{
error = new { error = "portalsCountTooMuch", message = "Too much portals registered already", };
return false;
}
}
}
return true;
}
#region Recaptcha
private bool CheckRecaptcha(TenantModel model, string clientIP, Stopwatch sw, out object error)
{
error = null;
if (CommonConstants.RecaptchaRequired
&& !CommonMethods.IsTestEmail(model.Email))
{
if (!string.IsNullOrEmpty(model.AppKey) && CommonConstants.AppSecretKeys.Contains(model.AppKey))
{
Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha via app key: {1}. {2}", model.PortalName, model.AppKey, sw.ElapsedMilliseconds);
return true;
}
var data = string.Format("{0} {1} {2} {3} {4}", model.PortalName, model.FirstName, model.LastName, model.Email, model.Phone);
/*** validate recaptcha ***/
if (!CommonMethods.ValidateRecaptcha(model.RecaptchaResponse, clientIP))
{
Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha error: {1} {2}", model.PortalName, sw.ElapsedMilliseconds, data);
sw.Stop();
error = new { error = "recaptchaInvalid", message = "Recaptcha is invalid" };
return false;
}
Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha: {1} {2}", model.PortalName, sw.ElapsedMilliseconds, data);
}
return true;
}
#endregion
#endregion
}
public static class PortalControllerExtention
{
public static DIHelper AddPortalController(this DIHelper services)
{
return services
.AddCommonMethods()
.AddTimeZonesProvider()
.AddCommonConstants()
.AddUserManagerService()
.AddUserFormatter()
.AddCoreSettingsService()
.AddHostedSolutionService()
.AddApiSystemHelper()
.AddSettingsManagerService()
.AddTenantManagerService()
.AddSecurityContextService()
;
}
}
}

View File

@ -0,0 +1,801 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Diagnostics;
using System.Linq;
using ASC.ApiSystem.Classes;
using ASC.ApiSystem.Models;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Common.Utils;
using ASC.Core;
using ASC.Core.Billing;
using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
using ASC.Core.Users;
using ASC.Web.Core.Helpers;
using ASC.Web.Core.Users;
using ASC.Web.Core.Utility;
using ASC.Web.Core.Utility.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
namespace ASC.ApiSystem.Controllers
{
[Obsolete("Registration is deprecated, please use PortalController or TariffController instead.")]
[ApiController]
[Route("[controller]")]
public class RegistrationController : ControllerBase
{
private CommonMethods CommonMethods { get; }
private CommonConstants CommonConstants { get; }
private HostedSolution HostedSolution { get; }
private TimeZonesProvider TimeZonesProvider { get; }
private TimeZoneConverter TimeZoneConverter { get; }
private ApiSystemHelper ApiSystemHelper { get; }
private SecurityContext SecurityContext { get; }
private TenantManager TenantManager { get; }
private SettingsManager SettingsManager { get; }
private CoreSettings CoreSettings { get; }
private TenantDomainValidator TenantDomainValidator { get; }
private UserFormatter UserFormatter { get; }
private UserManagerWrapper UserManagerWrapper { get; }
private IConfiguration Configuration { get; }
private ILog Log { get; }
public RegistrationController(
CommonMethods commonMethods,
CommonConstants commonConstants,
IOptionsSnapshot<HostedSolution> hostedSolution,
TimeZonesProvider timeZonesProvider,
TimeZoneConverter timeZoneConverter,
ApiSystemHelper apiSystemHelper,
SecurityContext securityContext,
TenantManager tenantManager,
SettingsManager settingsManager,
CoreSettings coreSettings,
TenantDomainValidator tenantDomainValidator,
UserFormatter userFormatter,
UserManagerWrapper userManagerWrapper,
IConfiguration configuration,
IOptionsMonitor<ILog> option)
{
CommonMethods = commonMethods;
CommonConstants = commonConstants;
HostedSolution = hostedSolution.Value;
TimeZonesProvider = timeZonesProvider;
TimeZoneConverter = timeZoneConverter;
ApiSystemHelper = apiSystemHelper;
SecurityContext = securityContext;
TenantManager = tenantManager;
SettingsManager = settingsManager;
CoreSettings = coreSettings;
TenantDomainValidator = tenantDomainValidator;
UserFormatter = userFormatter;
UserManagerWrapper = userManagerWrapper;
Configuration = configuration;
Log = option.Get("ASC.ApiSystem");
}
#region For TEST api
[HttpGet("test")]
public IActionResult Check()
{
return Ok(new
{
value = "Registration api works"
});
}
#endregion
#region API methods
[HttpPost("registerportal")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip.registerportal")]
public IActionResult Register(TenantModel model)
{
if (model == null)
{
return BadRequest(new
{
errors = "Tenant data is required."
});
}
if (!ModelState.IsValid)
{
var errors = new JArray();
foreach (var k in ModelState.Keys)
{
errors.Add(ModelState[k].Errors.FirstOrDefault().ErrorMessage);
}
return Ok(new
{
errors
});
}
var sw = Stopwatch.StartNew();
object error;
if (!string.IsNullOrEmpty(model.Password))
{
if (!CheckPasswordPolicy(model.Password, out error))
{
sw.Stop();
return BadRequest(error);
}
}
if (!CheckValidName(model.FirstName.Trim() + model.LastName.Trim(), out error))
{
sw.Stop();
return BadRequest(error);
}
var checkTenantBusyPesp = CheckExistingNamePortal(model.PortalName.Trim());
if (checkTenantBusyPesp != null)
{
sw.Stop();
return checkTenantBusyPesp;
}
Log.DebugFormat("PortalName = {0}; Elapsed ms. CheckExistingNamePortal: {1}", model.PortalName, sw.ElapsedMilliseconds);
var clientIP = CommonMethods.GetClientIp();
Log.DebugFormat("clientIP = {0}", clientIP);
if (CommonMethods.CheckMuchRegistration(model, clientIP, sw))
{
return BadRequest(new
{
errors = new[] { "tooMuchAttempts" }
});
}
if (CommonConstants.RecaptchaRequired && !CommonMethods.IsTestEmail(model.Email))
{
/*** validate recaptcha ***/
if (!CommonMethods.ValidateRecaptcha(model.RecaptchaResponse, clientIP))
{
Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha: {1}", model.PortalName, sw.ElapsedMilliseconds);
sw.Stop();
return BadRequest(new
{
errors = new[] { "recaptchaInvalid" },
message = "Recaptcha is invalid"
});
}
Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha: {1}", model.PortalName, sw.ElapsedMilliseconds);
}
//check payment portal count
if (Configuration["core:base-domain"] == "localhost")
{
var tenants = HostedSolution.GetTenants(DateTime.MinValue);
var firstTenant = tenants.FirstOrDefault();
if (firstTenant != null)
{
var activePortals = tenants.Count(r => r.Status != TenantStatus.Suspended && r.Status != TenantStatus.RemovePending);
var quota = HostedSolution.GetTenantQuota(firstTenant.TenantId);
if (quota.CountPortals > 0 && quota.CountPortals <= activePortals)
{
return BadRequest(new
{
errors = new[] { "portalsCountTooMuch" },
message = "Too much portals registered already",
});
}
}
}
var language = model.Language ?? string.Empty;
var tz = TimeZonesProvider.GetCurrentTimeZoneInfo(language);
Log.DebugFormat("PortalName = {0}; Elapsed ms. TimeZonesProvider.GetCurrentTimeZoneInfo: {1}", model.PortalName, sw.ElapsedMilliseconds);
if (!string.IsNullOrEmpty(model.TimeZoneName))
{
tz = TimeZoneConverter.GetTimeZone(model.TimeZoneName.Trim(), false) ?? tz;
Log.DebugFormat("PortalName = {0}; Elapsed ms. TimeZonesProvider.OlsonTimeZoneToTimeZoneInfo: {1}", model.PortalName, sw.ElapsedMilliseconds);
}
var lang = TimeZonesProvider.GetCurrentCulture(language);
Log.DebugFormat("PortalName = {0}; model.Language = {1}, resultLang.DisplayName = {2}", model.PortalName, language, lang.DisplayName);
var info = new TenantRegistrationInfo
{
Name = Configuration["web:portal-name"] ?? "Cloud Office Applications",
Address = model.PortalName,
Culture = lang,
FirstName = model.FirstName.Trim(),
LastName = model.LastName.Trim(),
Password = string.IsNullOrEmpty(model.Password) ? null : model.Password,
Email = model.Email.Trim(),
TimeZoneInfo = tz,
MobilePhone = string.IsNullOrEmpty(model.Phone) ? null : model.Phone.Trim(),
Industry = (TenantIndustry)model.Industry,
Spam = model.Spam,
Calls = model.Calls,
Analytics = model.Analytics
};
if (!string.IsNullOrEmpty(model.PartnerId))
{
if (Guid.TryParse(model.PartnerId, out Guid guid))
{
// valid guid
info.PartnerId = model.PartnerId;
}
}
if (!string.IsNullOrEmpty(model.AffiliateId))
{
info.AffiliateId = model.AffiliateId;
}
Tenant t;
try
{
/****REGISTRATION!!!*****/
if (!string.IsNullOrEmpty(ApiSystemHelper.ApiCacheUrl))
{
ApiSystemHelper.AddTenantToCache(info.Address, SecurityContext.CurrentAccount.ID);
Log.DebugFormat("PortalName = {0}; Elapsed ms. CacheController.AddTenantToCache: {1}", model.PortalName, sw.ElapsedMilliseconds);
}
HostedSolution.RegisterTenant(info, out t);
/*********/
Log.DebugFormat("PortalName = {0}; Elapsed ms. HostedSolution.RegisterTenant: {1}", model.PortalName, sw.ElapsedMilliseconds);
}
catch (Exception e)
{
sw.Stop();
Log.Error(e);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
errors = new[] { "registerNewTenantError" },
message = e.Message,
stacktrace = e.StackTrace
});
}
var isFirst = true;
string sendCongratulationsAddress = null;
if (!string.IsNullOrEmpty(model.Password))
{
isFirst = !CommonMethods.SendCongratulations(Request.Scheme, t, model.SkipWelcome, out sendCongratulationsAddress);
}
else if (Configuration["core:base-domain"] == "localhost")
{
try
{
/* set wizard not completed*/
TenantManager.SetCurrentTenant(t);
var settings = SettingsManager.Load<WizardSettings>();
settings.Completed = false;
SettingsManager.Save(settings);
}
catch (Exception e)
{
Log.Error(e);
}
}
var reference = CommonMethods.CreateReference(Request.Scheme, t.GetTenantDomain(CoreSettings), info.Email, isFirst, model.Module);
Log.DebugFormat("PortalName = {0}; Elapsed ms. CreateReferenceByCookie...: {1}", model.PortalName, sw.ElapsedMilliseconds);
sw.Stop();
return Ok(new
{
errors = "",
reference,
tenant = ToTenantWrapper(t),
referenceWelcome = sendCongratulationsAddress,
});
}
[HttpDelete("removeportal")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult Remove(string portalName)
{
if (string.IsNullOrEmpty(portalName))
{
return BadRequest(new
{
errors = "portalName is required."
});
}
var tenant = HostedSolution.GetTenant(portalName.Trim());
if (tenant == null)
{
return BadRequest(new
{
errors = "Tenant not found."
});
}
HostedSolution.RemoveTenant(tenant);
return Ok(new
{
errors = "",
tenant = ToTenantWrapper(tenant)
});
}
[HttpPut("tariff")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult SetTariff(TariffModel model)
{
if (model == null)
{
return BadRequest(new
{
errors = "PortalName is required."
});
}
if (!ModelState.IsValid)
{
var errors = new JArray();
foreach (var k in ModelState.Keys)
{
errors.Add(ModelState[k].Errors.FirstOrDefault().ErrorMessage);
}
return BadRequest(new
{
errors
});
}
var tenant = HostedSolution.GetTenant((model.PortalName ?? "").Trim());
if (tenant == null)
{
return BadRequest(new
{
errors = "Tenant not found."
});
}
var quota = new TenantQuota(tenant.TenantId)
{
ActiveUsers = 10000,
Features = model.Features ?? "",
MaxFileSize = 1024 * 1024 * 1024,
MaxTotalSize = 1024L * 1024 * 1024 * 1024 - 1,
Name = "api",
};
if (model.ActiveUsers != default)
{
quota.ActiveUsers = model.ActiveUsers;
}
if (model.MaxTotalSize != default)
{
quota.MaxTotalSize = model.MaxTotalSize;
}
if (model.MaxFileSize != default)
{
quota.MaxFileSize = model.MaxFileSize;
}
HostedSolution.SaveTenantQuota(quota);
var tariff = new Tariff
{
QuotaId = quota.Id,
DueDate = model.DueDate != default ? model.DueDate : DateTime.MaxValue,
};
HostedSolution.SetTariff(tenant.TenantId, tariff);
tariff = HostedSolution.GetTariff(tenant.TenantId, false);
quota = HostedSolution.GetTenantQuota(tenant.TenantId);
return Ok(new
{
errors = "",
tenant = ToTenantWrapper(tenant),
tariff = ToTariffWrapper(tariff, quota)
});
}
[HttpGet("tariff")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult GetTariff(string portalName)
{
if (string.IsNullOrEmpty(portalName))
{
return BadRequest(new
{
errors = "portalName is required."
});
}
var tenant = HostedSolution.GetTenant(portalName.Trim());
if (tenant == null)
{
return BadRequest(new
{
errors = "Tenant not found."
});
}
var tariff = HostedSolution.GetTariff(tenant.TenantId, false);
var quota = HostedSolution.GetTenantQuota(tenant.TenantId);
return Ok(new
{
errors = "",
tenant = ToTenantWrapper(tenant),
tariff = ToTariffWrapper(tariff, quota)
});
}
[HttpPut("statusportal")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult ChangeStatus(TenantModel model, bool active)
{
if (model == null)
{
return BadRequest(new
{
errors = "PortalName is required."
});
}
var tenant = HostedSolution.GetTenant((model.PortalName ?? "").Trim());
if (tenant == null)
{
return BadRequest(new
{
errors = "Tenant not found."
});
}
tenant.SetStatus(active ? TenantStatus.Active : TenantStatus.Suspended);
HostedSolution.SaveTenant(tenant);
return Ok(new
{
errors = "",
tenant = ToTenantWrapper(tenant)
});
}
[HttpPost("validateportalname")]
[AllowCrossSiteJson]
public IActionResult CheckExistingNamePortal(TenantModel model)
{
if (model == null)
{
return BadRequest(new
{
errors = "PortalName is required."
});
}
var response = CheckExistingNamePortal((model.PortalName ?? "").Trim());
return response ?? Ok(new
{
errors = "",
message = "portalNameReadyToRegister"
});
}
[HttpGet("getportals")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult GetPortalsByEmail(string email)
{
try
{
var tenants = string.IsNullOrEmpty(email)
? HostedSolution.GetTenants(DateTime.MinValue).OrderBy(t => t.TenantId).ToList()
: HostedSolution.FindTenants(email.Trim()).OrderBy(t => t.TenantId).ToList();
var refers = tenants.Where(t => t.Status == TenantStatus.Active).ToList()
.ConvertAll(t => string.Format("{0}{1}{2}",
Request.Scheme,
Uri.SchemeDelimiter,
t.GetTenantDomain(CoreSettings)));
return Ok(new
{
errors = "",
message = "",
portals = refers
});
}
catch (Exception ex)
{
Log.Error(ex);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
errors = new[] { "error" },
message = ex.Message,
stacktrace = ex.StackTrace
});
}
}
#endregion
#region private methods
private object ToTenantWrapper(Tenant t)
{
return new
{
tenantId = t.TenantId,
tenantAlias = t.TenantAlias,
tenantDomain = t.GetTenantDomain(CoreSettings),
hostedRegion = t.HostedRegion,
name = t.Name,
ownerId = t.OwnerId,
status = t.Status,
partnerId = t.PartnerId,
paymentId = t.PaymentId,
language = t.Language,
industry = t.Industry,
timeZoneName = TimeZoneConverter.GetTimeZone(t.TimeZone).DisplayName,
createdDateTime = t.CreatedDateTime
};
}
private object ToTariffWrapper(Tariff t, TenantQuota q)
{
return new
{
t.DueDate,
t.State,
q.MaxTotalSize,
q.MaxFileSize,
q.ActiveUsers,
q.Features
};
}
private IActionResult CheckExistingNamePortal(string portalName)
{
if (string.IsNullOrEmpty(portalName))
{
return BadRequest(new
{
error = "portalNameEmpty"
});
}
try
{
if (!string.IsNullOrEmpty(ApiSystemHelper.ApiCacheUrl))
{
ValidateDomain(portalName.Trim());
}
else
{
HostedSolution.CheckTenantAddress(portalName.Trim());
}
}
catch (TenantAlreadyExistsException ex)
{
return BadRequest(new
{
errors = new[] { "portalNameExist" },
variants = ex.ExistsTenants.ToArray()
});
}
catch (TenantTooShortException)
{
return BadRequest(new
{
errors = new[] { "tooShortError" }
});
}
catch (TenantIncorrectCharsException)
{
return BadRequest(new
{
errors = new[] { "portalNameIncorrect" }
});
}
catch (Exception ex)
{
Log.Error(ex);
return StatusCode(StatusCodes.Status500InternalServerError, new
{
errors = new[] { "error" },
message = ex.Message,
stacktrace = ex.StackTrace
});
}
return null;
}
private void ValidateDomain(string domain)
{
// size
TenantDomainValidator.ValidateDomainLength(domain);
// characters
TenantDomainValidator.ValidateDomainCharacters(domain);
var sameAliasTenants = ApiSystemHelper.FindTenantsInCache(domain, SecurityContext.CurrentAccount.ID);
if (sameAliasTenants != null)
{
throw new TenantAlreadyExistsException("Address busy.", sameAliasTenants);
}
}
private bool CheckValidName(string name, out object error)
{
error = null;
if (string.IsNullOrEmpty(name = (name ?? "").Trim()))
{
error = new
{
error = new[] { "error" },
message = "name is required"
};
return false;
}
if (!UserFormatter.IsValidUserName(name, string.Empty))
{
error = new
{
error = new[] { "error" },
message = "name is incorrect"
};
return false;
}
return true;
}
public bool CheckPasswordPolicy(string pwd, out object error)
{
error = null;
//Validate Password match
if (string.IsNullOrEmpty(pwd))
{
error = new
{
errors = new[] { "passEmpty" },
message = "password is empty"
};
return false;
}
var passwordSettings = (PasswordSettings)new PasswordSettings().GetDefault(Configuration);
if (!UserManagerWrapper.CheckPasswordRegex(passwordSettings, pwd))
{
error = new
{
errors = new[] { "passPolicyError" },
message = "password is incorrect"
};
return false;
}
return true;
}
#endregion
}
public static class RegistrationControllerExtention
{
public static DIHelper AddRegistrationController(this DIHelper services)
{
return services
.AddCommonMethods()
.AddTimeZonesProvider()
.AddCommonConstants()
.AddUserManagerService()
.AddUserFormatter()
.AddCoreSettingsService()
.AddHostedSolutionService()
.AddApiSystemHelper()
.AddSettingsManagerService()
.AddTenantManagerService()
.AddSecurityContextService()
;
}
}
}

View File

@ -0,0 +1,207 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using ASC.ApiSystem.Models;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Tenants;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace ASC.ApiSystem.Controllers
{
[ApiController]
[Route("[controller]")]
public class SettingsController : ControllerBase
{
private CommonMethods CommonMethods { get; }
public CoreSettings CoreSettings { get; }
private ILog Log { get; }
public SettingsController(
CommonMethods commonMethods,
CoreSettings coreSettings,
IOptionsMonitor<ILog> option)
{
CommonMethods = commonMethods;
CoreSettings = coreSettings;
Log = option.Get("ASC.ApiSystem");
}
#region For TEST api
[HttpGet("test")]
public IActionResult Check()
{
return Ok(new
{
value = "Settings api works"
});
}
#endregion
#region API methods
[HttpGet("get")]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult GetSettings([FromQuery] SettingsModel model)
{
if (!GetTenant(model, out int tenantId, out object error))
{
return BadRequest(error);
}
if (string.IsNullOrEmpty(model.Key))
{
return BadRequest(new
{
error = "params",
message = "Key is required"
});
}
var settings = CoreSettings.GetSetting(model.Key, tenantId);
return Ok(new
{
settings
});
}
[HttpPost("save")]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult SaveSettings([FromBody] SettingsModel model)
{
if (!GetTenant(model, out int tenantId, out object error))
{
return BadRequest(error);
}
if (string.IsNullOrEmpty(model.Key))
{
return BadRequest(new
{
error = "params",
message = "Key is required"
});
}
if (string.IsNullOrEmpty(model.Value))
{
return BadRequest(new
{
error = "params",
message = "Value is empty"
});
}
Log.DebugFormat("Set {0} value {1} for {2}", model.Key, model.Value, tenantId);
CoreSettings.SaveSetting(model.Key, model.Value, tenantId);
var settings = CoreSettings.GetSetting(model.Key, tenantId);
return Ok(new
{
settings
});
}
#endregion
#region private methods
private bool GetTenant(SettingsModel model, out int tenantId, out object error)
{
tenantId = -1;
error = null;
if (model == null)
{
error = new
{
error = "portalNameEmpty",
message = "PortalName is required"
};
Log.Error("Model is null");
return false;
}
if (model.TenantId.HasValue && model.TenantId.Value == -1)
{
tenantId = model.TenantId.Value;
return true;
}
if (!CommonMethods.GetTenant(model, out Tenant tenant))
{
error = new
{
error = "portalNameEmpty",
message = "PortalName is required"
};
Log.Error("Model without tenant");
return false;
}
if (tenant == null)
{
error = new
{
error = "portalNameNotFound",
message = "Portal not found"
};
Log.Error("Tenant not found");
return false;
}
tenantId = tenant.TenantId;
return true;
}
#endregion
}
public static class SettingsControllerExtention
{
public static DIHelper AddSettingsController(this DIHelper services)
{
return services
.AddCommonMethods()
.AddCoreSettingsService();
}
}
}

View File

@ -0,0 +1,230 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Linq;
using ASC.ApiSystem.Classes;
using ASC.ApiSystem.Models;
using ASC.Common;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Billing;
using ASC.Core.Tenants;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace ASC.ApiSystem.Controllers
{
[ApiController]
[Route("[controller]")]
public class TariffController : ControllerBase
{
private CommonMethods CommonMethods { get; }
private HostedSolution HostedSolution { get; }
private ILog Log { get; }
public TariffController(
CommonMethods commonMethods,
HostedSolution hostedSolution,
IOptionsMonitor<ILog> option
)
{
CommonMethods = commonMethods;
HostedSolution = hostedSolution;
Log = option.Get("ASC.ApiSystem");
}
#region For TEST api
[HttpGet("test")]
public IActionResult Check()
{
return Ok(new
{
value = "Tariff api works"
});
}
#endregion
#region API methods
[HttpPut("set")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult SetTariff(TariffModel model)
{
if (!CommonMethods.GetTenant(model, out Tenant tenant))
{
Log.Error("Model without tenant");
return BadRequest(new
{
error = "portalNameEmpty",
message = "PortalName is required"
});
}
if (tenant == null)
{
Log.Error("Tenant not found");
return BadRequest(new
{
error = "portalNameNotFound",
message = "Portal not found"
});
}
var quota = new TenantQuota(tenant.TenantId)
{
ActiveUsers = 10000,
Features = model.Features ?? "",
MaxFileSize = 1024 * 1024 * 1024,
MaxTotalSize = 1024L * 1024 * 1024 * 1024 - 1,
Name = "api",
};
if (model.ActiveUsers != default)
{
quota.ActiveUsers = model.ActiveUsers;
}
if (model.MaxTotalSize != default)
{
quota.MaxTotalSize = model.MaxTotalSize;
}
if (model.MaxFileSize != default)
{
quota.MaxFileSize = model.MaxFileSize;
}
HostedSolution.SaveTenantQuota(quota);
var tariff = new Tariff
{
QuotaId = quota.Id,
DueDate = model.DueDate != default ? model.DueDate : DateTime.MaxValue,
};
HostedSolution.SetTariff(tenant.TenantId, tariff);
return GetTariff(tenant);
}
[HttpGet("get")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth.allowskip")]
public IActionResult GetTariff([FromQuery] TariffModel model)
{
if (!CommonMethods.GetTenant(model, out Tenant tenant))
{
Log.Error("Model without tenant");
return BadRequest(new
{
error = "portalNameEmpty",
message = "PortalName is required"
});
}
if (tenant == null)
{
Log.Error("Tenant not found");
return BadRequest(new
{
error = "portalNameNotFound",
message = "Portal not found"
});
}
return GetTariff(tenant);
}
[HttpGet("all")]
[AllowCrossSiteJson]
public IActionResult GetTariffs()
{
var tariffs = HostedSolution.GetTenantQuotas()
.Where(q => !q.Trial && !q.Free && !q.Open)
.OrderBy(q => q.ActiveUsers)
.ThenByDescending(q => q.Id)
.Select(q => ToTariffWrapper(null, q));
return Ok(new
{
tariffs
});
}
#endregion
#region private methods
private IActionResult GetTariff(Tenant tenant)
{
var tariff = HostedSolution.GetTariff(tenant.TenantId, false);
var quota = HostedSolution.GetTenantQuota(tenant.TenantId);
return Ok(new
{
tenant = CommonMethods.ToTenantWrapper(tenant),
tariff = ToTariffWrapper(tariff, quota),
});
}
private static object ToTariffWrapper(Tariff t, TenantQuota q)
{
return new
{
activeUsers = q.ActiveUsers,
dueDate = t == null ? DateTime.MaxValue : t.DueDate,
features = q.Features,
maxFileSize = q.MaxFileSize,
maxTotalSize = q.MaxTotalSize,
state = t == null ? TariffState.Paid : t.State,
};
}
#endregion
}
public static class TariffControllerExtention
{
public static DIHelper AddTariffController(this DIHelper services)
{
return services
.AddCommonMethods()
.AddHostedSolutionService()
.AddCoreSettingsService();
}
}
}

View File

@ -0,0 +1,34 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
namespace ASC.ApiSystem.Interfaces
{
public interface IModel
{
string PortalName { get; set; }
int? TenantId { get; set; }
}
}

View File

@ -0,0 +1,51 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace ASC.ApiSystem.Models
{
[DataContract]
public class CoreSettingsModel
{
[DataMember(IsRequired = false)]
public Int32 Tenant { get; set; }
[DataMember(IsRequired = true)]
[StringLength(255)]
public string Key { get; set; }
[DataMember(IsRequired = true)]
public string Value { get; set; }
public CoreSettingsModel()
{
Tenant = -1;
}
}
}

View File

@ -0,0 +1,51 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using ASC.ApiSystem.Interfaces;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace ASC.ApiSystem.Models
{
[DataContract]
public class SettingsModel : IModel
{
[DataMember(IsRequired = false)]
[StringLength(255)]
public string PortalName { get; set; }
[DataMember(IsRequired = false)]
public int? TenantId { get; set; }
[DataMember(IsRequired = true)]
[StringLength(255)]
public string Key { get; set; }
[DataMember(IsRequired = true)]
public string Value { get; set; }
}
}

View File

@ -0,0 +1,61 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using ASC.ApiSystem.Interfaces;
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace ASC.ApiSystem.Models
{
[DataContract]
public class TariffModel : IModel
{
[DataMember(IsRequired = true)]
[StringLength(255)]
public string PortalName { get; set; }
[DataMember(IsRequired = false)]
public int? TenantId { get; set; }
[DataMember]
public int ActiveUsers { get; set; }
[DataMember]
public DateTime DueDate { get; set; }
[DataMember]
[StringLength(255)]
public string Features { get; set; }
[DataMember]
public long MaxFileSize { get; set; }
[DataMember]
public long MaxTotalSize { get; set; }
}
}

View File

@ -0,0 +1,119 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using ASC.ApiSystem.Classes;
using ASC.ApiSystem.Interfaces;
using ASC.Core.Tenants;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace ASC.ApiSystem.Models
{
[DataContract]
public class TenantModel : IModel
{
[DataMember(IsRequired = true)]
[StringLength(255)]
public string PortalName { get; set; }
[DataMember(IsRequired = false)]
public int? TenantId { get; set; }
[DataMember(IsRequired = false)]
[StringLength(255)]
public string AffiliateId { get; set; }
[DataMember(IsRequired = false)]
public string Campaign { get; set; }
[DataMember(IsRequired = true)]
[StringLength(255)]
public string FirstName { get; set; }
[DataMember(IsRequired = true)]
[Email]
[StringLength(255)]
public string Email { get; set; }
[DataMember(IsRequired = false)]
public int Industry { get; set; }
[DataMember(IsRequired = false)]
[StringLength(7)]
public string Language { get; set; }
[DataMember(IsRequired = true)]
[StringLength(255)]
public string LastName { get; set; }
[DataMember(IsRequired = false)]
[StringLength(38)]
public string Module { get; set; }
[DataMember(IsRequired = false)]
[StringLength(Web.Core.Utility.PasswordSettings.MaxLength)]
public string Password { get; set; }
[DataMember(IsRequired = false)]
[StringLength(255)]
public string PartnerId { get; set; }
[DataMember(IsRequired = false)]
[StringLength(32)]
public string Phone { get; set; }
[DataMember(IsRequired = false)]
public string RecaptchaResponse { get; set; }
[DataMember(IsRequired = false)]
[StringLength(20)]
public string Region { get; set; }
[DataMember(IsRequired = false)]
public TenantStatus Status { get; set; }
[DataMember(IsRequired = false)]
public bool SkipWelcome { get; set; }
[DataMember(IsRequired = false)]
[StringLength(255)]
public string TimeZoneName { get; set; }
[DataMember(IsRequired = false)]
public bool Spam { get; set; }
[DataMember(IsRequired = false)]
public bool Calls { get; set; }
[DataMember(IsRequired = false)]
public bool Analytics { get; set; }
[DataMember(IsRequired = false)]
public string AppKey { get; set; }
}
}

View File

@ -0,0 +1,72 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace ASC.ApiSystem
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
var buided = config.Build();
var path = buided["pathToConf"];
if (!Path.IsPathRooted(path))
{
path = Path.GetFullPath(Path.Combine(hostingContext.HostingEnvironment.ContentRootPath, path));
}
config.SetBasePath(path);
config
.AddInMemoryCollection(new Dictionary<string, string>
{
{"pathToConf", path}
})
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true)
.AddJsonFile("storage.json")
.AddJsonFile("kafka.json")
.AddJsonFile($"kafka.{hostingContext.HostingEnvironment.EnvironmentName}.json", true)
.AddEnvironmentVariables();
});
}
}

View File

@ -0,0 +1,33 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5010",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"$STORAGE_ROOT": "../../../Data",
"log__name": "apisystem",
"log__dir": "../../../Logs"
}
},
"ASC.ApiSystem": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:5010",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"$STORAGE_ROOT": "../../../Data",
"log__name": "apisystem",
"log__dir": "../../../Logs"
}
}
}
}

View File

@ -0,0 +1,115 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using ASC.ApiSystem.Classes;
using ASC.ApiSystem.Controllers;
using ASC.Common;
using ASC.Common.DependencyInjection;
using ASC.Common.Logging;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ASC.ApiSystem
{
public class Startup
{
public IConfiguration Configuration { get; }
public IHostEnvironment HostEnvironment { get; }
public Startup(IConfiguration configuration, IHostEnvironment hostEnvironment)
{
Configuration = configuration;
HostEnvironment = hostEnvironment;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var diHelper = new DIHelper(services);
services.AddHttpContextAccessor();
services.AddControllers()
.AddNewtonsoftJson();
services.AddMemoryCache();
services.AddAuthentication()
.AddScheme<AuthenticationSchemeOptions, AuthHandler>("auth.allowskip", _ => { })
.AddScheme<AuthenticationSchemeOptions, AuthHandler>("auth.allowskip.registerportal", _ => { });
diHelper.AddNLogManager("ASC.Apisystem");
diHelper
.AddPortalController()
.AddCoreSettingsController()
.AddCalDavController()
.AddRegistrationController()
.AddSettingsController()
.AddTariffController();
services.AddAutofac(Configuration, HostEnvironment.ContentRootPath, false);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseCors(builder =>
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View File

@ -0,0 +1,3 @@
{
"pathToConf": "..\\..\\..\\config"
}

View File

@ -148,4 +148,10 @@ server {
}
}
}
location /apisystem {
rewrite apisystem/(.*) /$1 break;
proxy_pass http://localhost:5010;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
}

View File

@ -1,7 +1,5 @@
import React from "react";
import { TreeMenu, TreeNode, Icons, toastr, utils } from "asc-web-components";
import { fetchFiles } from "../../../store/files/actions";
import store from "../../../store/store";
import { api } from "asc-web-common";
const { files } = api;
@ -10,8 +8,7 @@ class TreeFolders extends React.Component {
super(props);
const treeData = props.data;
this.state = { treeData, expandedKeys: this.props.expandedKeys };
this.state = { treeData, expandedKeys: props.expandedKeys };
this.ref = React.createRef();
}
@ -56,18 +53,6 @@ class TreeFolders extends React.Component {
}
};
onSelect = data => {
if (this.props.selectedKeys[0] !== data[0]) {
this.props.onLoading(true);
const newFilter = this.props.filter.clone();
fetchFiles(data[0], newFilter, store.dispatch).catch(err =>
toastr.error("Something went wrong", err)
).finally(() => this.props.onLoading(false));
}
//this.props.selectFolder(data && data.length === 1 && data[0] !== "root" ? data[0] : null);
};
loop = (data, curId, child, level) => {
//if (level < 1 || curId.length - 3 > level * 2) return;
data.forEach(item => {
@ -156,7 +141,7 @@ class TreeFolders extends React.Component {
const treeData = [...this.state.treeData];
this.getNewTreeData(treeData, listIds, data.folders, 10);
this.props.setTreeFolders(treeData);
this.props.needUpdate && this.props.setTreeFolders(treeData);
this.setState({ treeData });
})
.catch(() => this.props.onLoading(false))
@ -164,16 +149,18 @@ class TreeFolders extends React.Component {
};
onExpand = data => {
const newFilter = this.props.filter;
newFilter.treeFolders = data;
if(this.props.needUpdate) {
const newFilter = this.props.filter.clone();
newFilter.treeFolders = data;
this.props.setFilter(newFilter);
}
this.props.setFilter(newFilter);
this.setState({ expandedKeys: data });
};
componentDidUpdate(prevProps) {
const { expandedKeys, data } = this.props;
if (this.state.expandedKeys.length !== expandedKeys.length) {
const { expandedKeys, data, needUpdate } = this.props;
if (needUpdate && expandedKeys && this.state.expandedKeys.length !== expandedKeys.length) {
this.setState({ expandedKeys });
}
@ -183,7 +170,7 @@ class TreeFolders extends React.Component {
}
render() {
const { selectedKeys, fakeNewDocuments, isLoading } = this.props;
const { selectedKeys, fakeNewDocuments, isLoading, onSelect } = this.props;
const { treeData, expandedKeys } = this.state;
return (
@ -196,7 +183,7 @@ class TreeFolders extends React.Component {
multiple={false}
showIcon
switcherIcon={this.switcherIcon}
onSelect={this.onSelect}
onSelect={onSelect}
selectedKeys={selectedKeys}
badgeLabel={fakeNewDocuments}
onBadgeClick={() => console.log("onBadgeClick")}
@ -210,4 +197,9 @@ class TreeFolders extends React.Component {
}
}
TreeFolders.defaultProps = {
selectedKeys: [],
needUpdate: true
};
export default TreeFolders;

View File

@ -29,10 +29,10 @@ class ArticleBodyContent extends React.Component {
expandedKeys.pop();
fetchFiles(folderId, newFilter, store.dispatch).catch(err =>
toastr.error("Something went wrong", err)
toastr.error(err)
);
})
.catch(err => toastr.error("Something went wrong", err))
.catch(err => toastr.error(err))
.finally(() => this.setState({ expandedKeys }));
}
@ -46,12 +46,22 @@ class ArticleBodyContent extends React.Component {
}
}
onSelect = data => {
const { selectedKeys, filter, onLoading } = this.props;
if (selectedKeys[0] !== data[0]) {
onLoading(true);
const newFilter = filter.clone();
fetchFiles(data[0], newFilter, store.dispatch).catch(err =>
toastr.error(err)
).finally(() => onLoading(false));
}
};
render() {
const {
data,
selectedKeys,
fakeNewDocuments,
currentModule,
filter,
setFilter,
setTreeFolders,
@ -64,7 +74,7 @@ class ArticleBodyContent extends React.Component {
<TreeFolders
selectedKeys={selectedKeys}
fakeNewDocuments={fakeNewDocuments}
currentModule={currentModule}
onSelect={this.onSelect}
data={data}
filter={filter}
setFilter={setFilter}
@ -86,7 +96,6 @@ function mapStateToProps(state) {
data: treeFolders,
selectedKeys: selectedFolder ? [currentFolderId] : [""],
fakeNewDocuments,
currentModule: currentFolderId,
filter
};
}

View File

@ -13,7 +13,8 @@ import {
import { withTranslation } from "react-i18next";
import i18n from "./i18n";
import { api, utils } from "asc-web-common";
import { fetchFiles, setTreeFolders } from "../../../store/files/actions";
import { fetchFiles, setTreeFolders, getProgress } from "../../../store/files/actions";
import { loopTreeFolders } from "../../../store/files/selectors";
import store from "../../../store/store";
const { files } = api;
@ -39,31 +40,42 @@ class DeleteDialogComponent extends React.Component {
}
changeLanguage(i18n);
this.state = { isLoading: false, foldersList, filesList, selection };
this.state = { foldersList, filesList, selection };
}
loop = (path, item, folderId, folders, foldersCount) => {
const newPath = path;
while (path.length !== 0) {
const newItems = item.find(x => x.id === path[0]);
newPath.shift();
if (path.length === 0) {
const currentItem = item.find(x => x.id === folderId);
currentItem.folders = folders;
currentItem.foldersCount = foldersCount;
return;
loopDeleteOperation = id => {
const { currentFolderId, filter, treeFolders, setTreeFolders, isRecycleBinFolder, setProgressValue, finishFilesOperations, getProgress } = this.props;
const successMessage = "Files and folders was deleted";
getProgress().then(res => {
const currentProcess = res.find(x => x.id === id);
if(currentProcess && currentProcess.progress !== 100) {
setProgressValue(currentProcess.progress);
setTimeout(() => this.loopDeleteOperation(id), 1000);
} else {
fetchFiles(currentFolderId, filter, store.dispatch).then(data => {
if (!isRecycleBinFolder) {
const path = data.selectedFolder.pathParts.slice(0);
const newTreeFolders = treeFolders;
const folders = data.selectedFolder.folders;
const foldersCount = data.selectedFolder.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
setTreeFolders(newTreeFolders);
}
setProgressValue(100);
finishFilesOperations();
toastr.success(successMessage);
})
}
this.loop(newPath, newItems.folders, folderId, folders, foldersCount);
}
};
}).catch(err => finishFilesOperations(err))
}
onDelete = () => {
const { isRecycleBinFolder, onClose } = this.props;
const { isRecycleBinFolder, onClose, startFilesOperations, finishFilesOperations, t } = this.props;
const { selection } = this.state;
const deleteAfter = true; //Delete after finished
const immediately = isRecycleBinFolder ? true : false; //Don't move to the Recycle Bin
const successMessage = "Files and folders was deleted";
const folderIds = [];
const fileIds = [];
@ -78,44 +90,13 @@ class DeleteDialogComponent extends React.Component {
i++;
}
this.setState({ isLoading: true }, () => {
const {
currentFolderId,
filter,
treeFolders,
setTreeFolders
} = this.props;
files
.removeFiles(folderIds, fileIds, deleteAfter, immediately)
.then(() =>
fetchFiles(currentFolderId, filter, store.dispatch).then(data => {
if (!isRecycleBinFolder) {
const path = data.selectedFolder.pathParts;
const newTreeFolders = treeFolders;
const folders = data.selectedFolder.folders;
const foldersCount = data.selectedFolder.foldersCount;
this.loop(
path,
newTreeFolders,
currentFolderId,
folders,
foldersCount
);
setTreeFolders(newTreeFolders);
}
})
)
.then(() => toastr.success(successMessage))
.catch(err => {
toastr.error(err);
})
.finally(() => {
this.setState({ isLoading: false }, () => onClose());
});
});
startFilesOperations(t("DeleteOperation"));
onClose();
files
.removeFiles(folderIds, fileIds, deleteAfter, immediately)
.then(res =>
this.loopDeleteOperation(res[0].id))
.catch(err => finishFilesOperations(err))
};
onChange = event => {
@ -137,8 +118,8 @@ class DeleteDialogComponent extends React.Component {
};
render() {
const { onClose, visible, t } = this.props;
const { isLoading, filesList, foldersList, selection } = this.state;
const { onClose, visible, t, isLoading } = this.props;
const { filesList, foldersList, selection } = this.state;
const checkedSelections = selection.filter(x => x.checked === true);
@ -253,6 +234,6 @@ const mapStateToProps = state => {
};
};
export default connect(mapStateToProps, { setTreeFolders })(
export default connect(mapStateToProps, { setTreeFolders, getProgress })(
withRouter(DeleteDialog)
);

View File

@ -9,5 +9,6 @@
"DeleteDialogQuestion": "Are you sure you want to delete these elements?",
"DeleteDialogNote": "Note: This action can not be undone.",
"QuestionDeleteFolder": "Are you sure you want to delete this folder?",
"QuestionDeleteElements": "Are you sure you want to delete these elements?"
"QuestionDeleteElements": "Are you sure you want to delete these elements?",
"DeleteOperation": "Deleting"
}

View File

@ -10,5 +10,6 @@
"DeleteDialogMessage": "Пожалуйста, обратите внимание, что если вы предоставили кому-то доступ к этим файлам/папкам, они станут недоступны. Вы уверены, что хотите продолжить?",
"DeleteDialogNote": "Замечание: Это действие не может быть отменено.",
"QuestionDeleteFolder": "Вы собираетесь удалить эту папку?",
"QuestionDeleteElements": "Вы собираетесь удалить эти элементы?"
"QuestionDeleteElements": "Вы собираетесь удалить эти элементы?",
"DeleteOperation": "Удаление"
}

View File

@ -162,17 +162,17 @@ class DownloadDialogComponent extends React.Component {
}
onDownload = () => {
const { startUploadSession, closeUploadSession, onDownloadProgress, onClose } = this.props;
const { startUploadSession, finishFilesOperations, onDownloadProgress, onClose, t } = this.props;
const downloadItems = this.getDownloadItems();
const fileConvertIds = downloadItems[0];
const folderIds = downloadItems[1];
startUploadSession();
startUploadSession(t("ArchivingData"));
api.files
.downloadFormatFiles(fileConvertIds, folderIds)
.then(() => { onClose(); onDownloadProgress(false); })
.catch(err => closeUploadSession(err));
.catch(err => finishFilesOperations(err));
};
getItemIcon = (item) => {

View File

@ -11,5 +11,6 @@
"ConvertToZip": "Files will be compressed into the .zip file",
"ConvertMessage": "If you choose to convert the file to the format different from the original, some data might be lost.",
"DownloadButton": "Download",
"CancelButton": "Cancel"
"CancelButton": "Cancel",
"ArchivingData": "Archiving data"
}

View File

@ -11,5 +11,6 @@
"ConvertToZip": "Файлы будут упакованы в .zip файл",
"ConvertMessage": "Если вы решите сконвертировать файл в формат, отличный от исходного, некоторые данные могут быть потеряны.",
"DownloadButton": "Скачать",
"CancelButton": "Отмена"
"CancelButton": "Отмена",
"ArchivingData": "Архивирование данных"
}

View File

@ -53,19 +53,19 @@ class FilesRowContent extends React.PureComponent {
};
createItem = () => {
const { createFile, createFolder, item } = this.props;
const { createFile, createFolder, item, onLoading } = this.props;
const { itemTitle } = this.state;
//this.setState({ loading: true });
onLoading(true);
if (itemTitle.trim() === '')
return this.completeAction();
!item.fileExst
? createFolder(item.parentId, itemTitle)
.then(() => this.completeAction())
.then(() => this.completeAction()).finally(() => onLoading(false))
: createFile(item.parentId, `${itemTitle}.${item.fileExst}`)
.then(() => this.completeAction())
.then(() => this.completeAction()).finally(() => onLoading(false))
}
componentDidUpdate(prevProps) {

View File

@ -24,7 +24,8 @@ import {
//fetchRootFolders,
selectFile,
setAction,
setTreeFolders
setTreeFolders,
getProgress
} from '../../../../../store/files/actions';
import { isFileSelected, getFileIcon, getFolderIcon, getFolderType, loopTreeFolders, isImage, isSound, isVideo } from '../../../../../store/files/selectors';
import store from "../../../../../store/store";
@ -138,35 +139,47 @@ class SectionBodyContent extends React.Component {
}
onDeleteFile = (fileId, currentFolderId) => {
const { deleteFile, filter, onLoading } = this.props;
const { deleteFile, t, startFilesOperations, finishFilesOperations } = this.props;
onLoading(true);
startFilesOperations(t("DeleteOperation"));
deleteFile(fileId)
.catch(err => toastr.error(err))
.then(() => fetchFiles(currentFolderId, filter, store.dispatch))
.then(() => toastr.success(`File moved to recycle bin`))
.finally(() => onLoading(false));
.then(res => this.loopDeleteProgress(res[0].id, currentFolderId, false))
.catch(err => finishFilesOperations(err))
}
onDeleteFolder = (folderId, currentFolderId) => {
const { deleteFolder, filter, treeFolders, setTreeFolders, onLoading, currentFolderType } = this.props;
onLoading(true);
deleteFolder(folderId, currentFolderId)
.catch(err => toastr.error(err))
.then(() =>
fetchFiles(currentFolderId, filter, store.dispatch).then(data => {
if (currentFolderType !== "Trash") {
const path = data.selectedFolder.pathParts;
loopDeleteProgress = (id, folderId, isFolder) => {
const { filter, treeFolders, setTreeFolders, currentFolderType, getProgress, setProgressValue, finishFilesOperations } = this.props;
getProgress().then(res => {
const deleteProgress = res.find(x => x.id === id);
if(deleteProgress && deleteProgress.progress !== 100) {
setProgressValue(deleteProgress.progress);
setTimeout(() => this.loopDeleteProgress(id, folderId), 1000);
} else {
fetchFiles(folderId, filter, store.dispatch).then(data => {
if (currentFolderType !== "Trash" && isFolder) {
const path = data.selectedFolder.pathParts.slice(0);
const newTreeFolders = treeFolders;
const folders = data.selectedFolder.folders;
const foldersCount = data.selectedFolder.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
setTreeFolders(newTreeFolders);
}
})
)
.then(() => toastr.success(`Folder moved to recycle bin`))
.finally(() => onLoading(false));
isFolder
? toastr.success(`Folder moved to recycle bin`)
: toastr.success(`File moved to recycle bin`);
setProgressValue(100);
finishFilesOperations();
}).catch(err => finishFilesOperations(err))
}
})
}
onDeleteFolder = (folderId, currentFolderId) => {
const { deleteFolder, startFilesOperations, finishFilesOperations, t } = this.props;
startFilesOperations(t("DeleteOperation"));
deleteFolder(folderId, currentFolderId)
.then(res => this.loopDeleteProgress(res[0].id, currentFolderId, true))
.catch(err => finishFilesOperations(err))
}
onClickShare = item => {
@ -734,6 +747,7 @@ export default connect(
//fetchRootFolders,
selectFile,
setAction,
setTreeFolders
setTreeFolders,
getProgress
}
)(withRouter(withTranslation()(SectionBodyContent)));

View File

@ -11,10 +11,10 @@ import {
IconButton,
toastr
} from "asc-web-components";
import { fetchFiles, setAction } from "../../../../../store/files/actions";
import { fetchFiles, setAction, getProgress } from "../../../../../store/files/actions";
import { default as filesStore } from "../../../../../store/store";
import { EmptyTrashDialog, DeleteDialog, DownloadDialog } from "../../../../dialogs";
import { SharingPanel } from "../../../../panels";
import { SharingPanel, OperationsPanel } from "../../../../panels";
import { isCanBeDeleted, checkFolderType } from "../../../../../store/files/selectors";
const { isAdmin } = store.auth.selectors;
@ -103,7 +103,9 @@ class SectionHeaderContent extends React.Component {
showSharingPanel: false,
showDeleteDialog: false,
showDownloadDialog: false,
showEmptyTrashDialog: false
showEmptyTrashDialog: false,
showMoveToPanel: false,
showCopyPanel: false
};
}
@ -162,34 +164,20 @@ class SectionHeaderContent extends React.Component {
createLinkForPortalUsers = () =>
toastr.info("createLinkForPortalUsers click");
moveAction = () => toastr.info("moveAction click");
onMoveAction = () => this.setState({ showMoveToPanel: !this.state.showMoveToPanel });
copyAction = () => toastr.info("copyAction click");
startUploadSession = () => {
const { onLoading, t, setProgressLabel, setProgressVisible} = this.props;
onLoading(true);
setProgressLabel(t("ArchivingData"));
setProgressVisible(true);
}
closeUploadSession = (err) => {
const timeout = err ? 0 : null;
err && toastr.error(err);
this.props.onLoading(false);
this.props.setProgressVisible(false, timeout);
}
onCopyAction = () => this.setState({ showCopyPanel: !this.state.showCopyPanel });
loop = url => {
api.files.getProgress().then(res => {
this.props.getProgress().then(res => {
if(!url) {
this.props.setProgressValue(res[0].progress);
setTimeout(() => this.loop(res[0].url), 1000);
} else {
this.closeUploadSession();
this.props.finishFilesOperations();
return window.open(url, "_blank");
}
}).catch((err) => this.closeUploadSession(err));
}).catch((err) => this.props.finishFilesOperations(err));
}
downloadAction = () => {
@ -207,14 +195,14 @@ class SectionHeaderContent extends React.Component {
}
}
this.startUploadSession();
this.props.startFilesOperations(this.props.t("ArchivingData"));
api.files
.downloadFiles(fileIds, folderIds)
.then(res => {
this.loop(res[0].url);
})
.catch((err) => this.closeUploadSession(err));
.catch((err) => this.props.finishFilesOperations(err));
}
downloadAsAction = () => this.setState({ showDownloadDialog: !this.state.showDownloadDialog });
@ -249,13 +237,13 @@ class SectionHeaderContent extends React.Component {
{
key: "move-to",
label: t("MoveTo"),
onClick: this.moveAction,
onClick: this.onMoveAction,
disabled: true
},
{
key: "copy",
label: t("Copy"),
onClick: this.copyAction,
onClick: this.onCopyAction,
disabled: true
},
{
@ -287,8 +275,6 @@ class SectionHeaderContent extends React.Component {
);
};
render() {
//console.log("Body header render");
@ -306,13 +292,19 @@ class SectionHeaderContent extends React.Component {
onCheck,
title,
currentFolderId,
onLoading
onLoading,
isLoading,
setProgressValue,
startFilesOperations,
finishFilesOperations
} = this.props;
const {
showDeleteDialog,
showSharingPanel,
showEmptyTrashDialog,
showDownloadDialog
showDownloadDialog,
showMoveToPanel,
showCopyPanel
} = this.state;
const isItemsSelected = selection.length;
const isOnlyFolderSelected = selection.every(
@ -369,12 +361,12 @@ class SectionHeaderContent extends React.Component {
{
label: t("MoveTo"),
disabled: !isItemsSelected,
onClick: this.moveAction
onClick: this.onMoveAction
},
{
label: t("Copy"),
disabled: !isItemsSelected,
onClick: this.copyAction
onClick: this.onCopyAction
},
{
label: t("Delete"),
@ -389,6 +381,14 @@ class SectionHeaderContent extends React.Component {
onClick: this.onEmptyTrashAction
});
const operationsPanelProps = {
onLoading,
isLoading,
setProgressValue,
startFilesOperations,
finishFilesOperations
};
return (
<StyledContainer isHeaderVisible={isHeaderVisible}>
{isHeaderVisible ? (
@ -462,6 +462,7 @@ class SectionHeaderContent extends React.Component {
{showDeleteDialog && (
<DeleteDialog
{...operationsPanelProps}
isRecycleBinFolder={isRecycleBinFolder}
visible={showDeleteDialog}
onClose={this.onDeleteAction}
@ -486,12 +487,30 @@ class SectionHeaderContent extends React.Component {
/>
)}
{showMoveToPanel && (
<OperationsPanel
{...operationsPanelProps}
isCopy={false}
visible={showMoveToPanel}
onClose={this.onMoveAction}
/>
)}
{showCopyPanel && (
<OperationsPanel
{...operationsPanelProps}
isCopy={true}
visible={showCopyPanel}
onClose={this.onCopyAction}
/>
)}
{showDownloadDialog && (
<DownloadDialog
visible={showDownloadDialog}
onClose={this.downloadAsAction}
startUploadSession={this.startUploadSession}
closeUploadSession={this.closeUploadSession}
startUploadSession={startFilesOperations}
finishFilesOperations={finishFilesOperations}
onDownloadProgress={this.loop}
/>
)}
@ -526,6 +545,6 @@ const mapStateToProps = state => {
};
};
export default connect(mapStateToProps, { setAction })(
export default connect(mapStateToProps, { setAction, getProgress })(
withTranslation()(withRouter(SectionHeaderContent))
);

View File

@ -2,7 +2,7 @@ import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import { RequestLoader, Checkbox } from "asc-web-components";
import { RequestLoader, Checkbox, toastr } from "asc-web-components";
import { PageLayout, utils } from "asc-web-common";
import { withTranslation, I18nextProvider } from 'react-i18next';
import i18n from "./i18n";
@ -112,6 +112,17 @@ class PureHome extends React.Component {
onChangeOriginalFormat = () => this.setState({uploadOriginalFormatSetting: !this.state.uploadOriginalFormatSetting})
onChangeWindowVisible = () => this.setState({hideWindowSetting: !this.state.hideWindowSetting})
startFilesOperations = progressBarLabel => {
this.setState({ isLoading: true, progressBarLabel, showProgressBar: true })
}
finishFilesOperations = err => {
const timeout = err ? 0 : null;
err && toastr.error(err);
this.onLoading(false);
this.setProgressVisible(false, timeout);
}
render() {
const {
isHeaderVisible,
@ -151,7 +162,7 @@ class PureHome extends React.Component {
return (
<>
<RequestLoader
visible={this.state.isLoading}
visible={isLoading}
zIndex={256}
loaderSize='16px'
loaderColor={"#999"}
@ -184,9 +195,10 @@ class PureHome extends React.Component {
onSelect={this.onSectionHeaderContentSelect}
onClose={this.onClose}
onLoading={this.onLoading}
setProgressVisible={this.setProgressVisible}
isLoading={isLoading}
setProgressValue={this.setProgressValue}
setProgressLabel={this.setProgressLabel}
startFilesOperations={this.startFilesOperations}
finishFilesOperations={this.finishFilesOperations}
/>
}
sectionFilterContent={<SectionFilterContent onLoading={this.onLoading} />}
@ -196,6 +208,9 @@ class PureHome extends React.Component {
isLoading={isLoading}
onLoading={this.onLoading}
onChange={this.onRowChange}
setProgressValue={this.setProgressValue}
startFilesOperations={this.startFilesOperations}
finishFilesOperations={this.finishFilesOperations}
/>
}
sectionPagingContent={

View File

@ -72,5 +72,6 @@
"OverwriteSetting": "Overwrite existing file with the same name",
"UploadOriginalFormatSetting": "Upload the documents in original format as well",
"HideWindowSetting": "Show this window minimized",
"ArchivingData": "Archiving data"
"ArchivingData": "Archiving data",
"DeleteOperation": "Deleting"
}

View File

@ -72,5 +72,6 @@
"OverwriteSetting": "Перезаписывать существующий файл с таким же именем",
"UploadOriginalFormatSetting": "Сохранять также копию файла в исходном формате",
"HideWindowSetting": "Показывать это окно минимизированным",
"ArchivingData": "Архивирование данных"
"ArchivingData": "Архивирование данных",
"DeleteOperation": "Удаление"
}

View File

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

View File

@ -0,0 +1,196 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { withRouter } from "react-router";
import { Backdrop, Heading, Aside } from "asc-web-components";
import { withTranslation } from "react-i18next";
import { utils as commonUtils } from "asc-web-common";
import i18n from "./i18n";
import {
StyledAsidePanel,
StyledContent,
StyledHeaderContent,
StyledBody
} from "../StyledPanels";
import TreeFolders from "../../Article/Body/TreeFolders";
import {
getProgress,
fetchFiles,
setTreeFolders,
getFolder,
copyToFolder,
moveToFolder
} from "../../../store/files/actions";
import { default as filesStore } from "../../../store/store";
import { loopTreeFolders } from "../../../store/files/selectors";
const { changeLanguage } = commonUtils;
class OperationsPanelComponent extends React.Component {
constructor(props) {
super(props);
changeLanguage(i18n);
this.state = { visible: false };
}
loop = (id, destFolderId) => {
const {
getProgress,
setProgressValue,
finishFilesOperations,
filter,
currentFolderId,
treeFolders,
getFolder
} = this.props;
getProgress().then(res => {
const currentItem = res.find(x => x.id === id);
if(currentItem && currentItem.progress !== 100) {
setProgressValue(currentItem.progress);
setTimeout(() => this.loop(id, destFolderId), 1000);
} else {
getFolder(destFolderId).then(data => {
let newTreeFolders = treeFolders;
let path = data.pathParts.slice(0);
let folders = data.folders;
let foldersCount = data.current.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
fetchFiles(currentFolderId, filter, filesStore.dispatch).then((data) => {
newTreeFolders = treeFolders;
path = data.selectedFolder.pathParts.slice(0);
folders = data.selectedFolder.folders;
foldersCount = data.selectedFolder.foldersCount;
loopTreeFolders(path, newTreeFolders, folders, foldersCount);
setTreeFolders(newTreeFolders);
}).catch(err => finishFilesOperations(err))
.finally(() => {
setProgressValue(100);
finishFilesOperations();
})
}).catch(err => finishFilesOperations(err))
}
}).catch(err => finishFilesOperations(err));
}
onClose = () => {
this.setState({ visible: false });
setTimeout(() => this.props.onClose(), 1000);
}
componentDidMount() {
setTimeout(() => this.setState({visible: this.props.visible}), 1000);
}
onSelect = e => {
const {
t,
isCopy,
selection,
startFilesOperations,
finishFilesOperations,
copyToFolder,
moveToFolder
} = this.props;
const destFolderId = Number(e);
const conflictResolveType = "skip"; //Skip, Overwrite, Duplicate
const deleteAfter = true;
const folderIds = [];
const fileIds = [];
for(let item of selection) {
if(item.fileExst) {
fileIds.push(item.id);
} else {
folderIds.push(item.id);
}
}
this.onClose();
if(isCopy) {
startFilesOperations(t("CopyOperation"));
copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter)
.then(res => this.loop(res[0].id, destFolderId))
.catch(err => finishFilesOperations(err))
} else {
startFilesOperations(t("MoveToOperation"));
moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter)
.then(res => this.loop(res[0].id, destFolderId))
.catch(err => finishFilesOperations(err))
}
}
render() {
//console.log("Operations panel render");
const { t, onLoading, isLoading, filter, treeFolders, isCopy } = this.props;
const { visible } = this.state;
const zIndex = 310;
const fakeNewDocuments = 8;
const data = treeFolders.slice(0, 3);
const expandedKeys = this.props.expandedKeys.map(item => item.toString());
return (
<StyledAsidePanel visible={visible}>
<Backdrop onClick={this.onClose} visible={visible} zIndex={zIndex} />
<Aside className="header_aside-panel" visible={visible}>
<StyledContent>
<StyledHeaderContent className="files-operations-panel">
<Heading size="medium" truncate>
{isCopy ? t("Copy") : t("Move")}
</Heading>
</StyledHeaderContent>
<StyledBody className="files-operations-body">
<TreeFolders
expandedKeys={expandedKeys}
fakeNewDocuments={fakeNewDocuments}
data={data}
filter={filter}
onLoading={onLoading}
isLoading={isLoading}
onSelect={this.onSelect}
needUpdate={false}
/>
</StyledBody>
</StyledContent>
</Aside>
</StyledAsidePanel>
);
}
}
OperationsPanelComponent.propTypes = {
onClose: PropTypes.func,
visible: PropTypes.bool,
};
const OperationsPanelContainerTranslated = withTranslation()(OperationsPanelComponent);
const OperationsPanel = (props) => (
<OperationsPanelContainerTranslated i18n={i18n} {...props} />
);
const mapStateToProps = (state) => {
const { selectedFolder, selection, treeFolders, filter } = state.files;
const { pathParts, id } = selectedFolder;
return {
treeFolders,
filter,
selection,
expandedKeys: pathParts,
currentFolderId: id
};
};
export default connect(mapStateToProps, {
setTreeFolders,
getFolder,
getProgress,
copyToFolder,
moveToFolder,
})(withRouter(OperationsPanel));

View File

@ -0,0 +1,6 @@
{
"Copy": "Copy",
"Move": "Move",
"CopyOperation": "Copying",
"MoveToOperation": "Moving"
}

View File

@ -0,0 +1,6 @@
{
"Copy": "Копирование",
"Move": "Перемещение",
"CopyOperation": "Копирование",
"MoveToOperation": "Перемещение"
}

View File

@ -9,7 +9,6 @@ import {
Button,
DropDown,
DropDownItem,
utils,
toastr,
Textarea,
ComboBox,
@ -18,12 +17,12 @@ import {
import { connect } from "react-redux";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { utils as commonUtils, constants, api } from "asc-web-common";
import { utils as commonUtils, constants } from "asc-web-common";
import i18n from "./i18n";
import { getShareUsers, setShareFiles } from "../../../store/files/actions";
import { getAccessOption } from '../../../store/files/selectors';
import {
StyledSharingPanel,
StyledAsidePanel,
StyledContent,
StyledFooter,
StyledSharingHeaderContent,
@ -34,7 +33,6 @@ import SharingRow from "./SharingRow";
const { changeLanguage } = commonUtils;
const { ShareAccessRights } = constants;
const { files } = api;
class SharingPanelComponent extends React.Component {
constructor(props) {
@ -561,7 +559,7 @@ class SharingPanelComponent extends React.Component {
);
return (
<StyledSharingPanel visible={visible}>
<StyledAsidePanel visible={visible}>
<Backdrop onClick={this.onClose} visible={visible} zIndex={zIndex} />
<Aside className="header_aside-panel" visible={visible}>
<StyledContent>
@ -676,7 +674,7 @@ class SharingPanelComponent extends React.Component {
onClose={this.onShowEmbeddingPanel}
embeddingLink={shareLink}
/>
</StyledSharingPanel>
</StyledAsidePanel>
);
}
}

View File

@ -14,7 +14,7 @@ const PanelStyles = css`
}
`;
const StyledSharingPanel = styled.div`
const StyledAsidePanel = styled.div`
.header_aside-panel {
transform: translateX(${(props) => (props.visible ? "0" : "500px")});
width: 500px;
@ -73,6 +73,14 @@ const StyledContent = styled.div`
background-color: #fff;
padding: 0 16px 16px;
.files-operations-panel {
border-bottom: 1px solid #dee2e6;
}
.files-operations-body {
padding: 16px 0;
}
.header_aside-panel-header {
max-width: 500px;
margin: 0 0 0 16px;
@ -91,6 +99,10 @@ const StyledHeaderContent = styled.div`
`;
const StyledBody = styled.div`
.files-operations-body {
padding: 0 16px;
}
.selector-wrapper {
position: fixed;
height: 94%;
@ -257,7 +269,7 @@ const StyledFooter = styled.div`
`;
export {
StyledSharingPanel,
StyledAsidePanel,
StyledAddGroupsPanel,
StyledAddUsersPanelPanel,
StyledEmbeddingPanel,
@ -266,5 +278,5 @@ export {
StyledBody,
StyledSharingHeaderContent,
StyledSharingBody,
StyledFooter,
StyledFooter
};

View File

@ -2,5 +2,6 @@ import SharingPanel from "./SharingPanel/SharingPanel";
import AddUsersPanel from "./AddUsersPanel/AddUsersPanel";
import AddGroupsPanel from "./AddGroupsPanel/AddGroupsPanel";
import EmbeddingPanel from "./EmbeddingPanel/EmbeddingPanel";
import OperationsPanel from "./OperationsPanel";
export { SharingPanel, AddUsersPanel, AddGroupsPanel, EmbeddingPanel }
export { SharingPanel, AddUsersPanel, AddGroupsPanel, EmbeddingPanel, OperationsPanel }

View File

@ -14,7 +14,7 @@ import {
import config from "../../../package.json";
import { getTreeFolders } from "./selectors";
const { files, groups, FilesFilter } = api;
const { files, FilesFilter } = api;
export const SET_FOLDER = "SET_FOLDER";
export const SET_FOLDERS = "SET_FOLDERS";
@ -295,15 +295,7 @@ export function deleteFile(fileId, deleteAfter, immediately) {
}
export function deleteFolder(folderId, deleteAfter, immediately) {
return (dispatch, getState) => {
const { files } = getState();
const { folders } = files;
return api.files.deleteFolder(folderId, deleteAfter, immediately)
.then(res => {
return dispatch(setFolder(folders.filter(f => f.id !== folderId)));
})
}
return (dispatch) => api.files.deleteFolder(folderId, deleteAfter, immediately);
}
export function setShareFiles(folderIds, fileIds, share, notify, sharingMessage) {
@ -327,6 +319,24 @@ export function getShareUsers(folderIds, fileIds) {
return axios.all(requests).then(res => res);
}
export function getProgress() {
return dispatch => {
return files.getProgress();
};
};
export function copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) {
return dispatch => {
return files.copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter);
};
};
export function moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) {
return dispatch => {
return files.moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter);
};
};
/*export function deleteGroup(id) {
return (dispatch, getState) => {
const { people } = getState();

View File

@ -240,7 +240,7 @@ namespace ASC.Files.Helpers
public Configuration<T> OpenEdit(T fileId, int version, string doc)
{
DocumentServiceHelper.GetParams(fileId, version, doc, true, true, true, out var configuration);
configuration.Type = EditorType.External;
configuration.EditorType = EditorType.External;
configuration.Token = DocumentServiceHelper.GetSignature(configuration);
return configuration;
}

View File

@ -24,9 +24,12 @@
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
using ASC.Common;
using ASC.Files.Core;
@ -162,4 +165,30 @@ namespace ASC.Api.Documents
.AddFolderWrapperHelperService();
}
}
public class FileEntryWrapperConverter : JsonConverter<FileEntryWrapper>
{
public override FileEntryWrapper Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return null;
}
public override void Write(Utf8JsonWriter writer, FileEntryWrapper value, JsonSerializerOptions options)
{
if (value is FolderWrapper<string> f1)
{
JsonSerializer.Serialize(writer, f1, typeof(FolderWrapper<string>), options);
return;
}
if (value is FolderWrapper<int> f2)
{
JsonSerializer.Serialize(writer, f2, typeof(FolderWrapper<int>), options);
return;
}
JsonSerializer.Serialize(writer, value, options);
}
}
}

View File

@ -91,7 +91,7 @@ namespace ASC.Web.Files.Services.DocumentService
EditorConfig.SetConfiguration(this);
}
public EditorType Type
public EditorType EditorType
{
set { Document.Info.Type = value; }
get { return Document.Info.Type; }
@ -100,7 +100,7 @@ namespace ASC.Web.Files.Services.DocumentService
#region Property
[DataMember(Name = "document")]
public DocumentConfig<T> Document;
public DocumentConfig<T> Document { get; set; }
[DataMember(Name = "documentType")]
public string DocumentType
@ -114,16 +114,16 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "editorConfig")]
public EditorConfiguration<T> EditorConfig;
public EditorConfiguration<T> EditorConfig { get; set; }
[DataMember(Name = "token", EmitDefaultValue = false)]
public string Token;
public string Token { get; set; }
[DataMember(Name = "type")]
public string TypeString
public string Type
{
set { Type = (EditorType)Enum.Parse(typeof(EditorType), value, true); }
get { return Type.ToString().ToLower(); }
set { EditorType = (EditorType)Enum.Parse(typeof(EditorType), value, true); }
get { return EditorType.ToString().ToLower(); }
}
internal FileType GetFileType
@ -138,7 +138,7 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "error", EmitDefaultValue = false)]
public string ErrorMessage;
public string ErrorMessage { get; set; }
#endregion
@ -179,7 +179,7 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "info")]
public InfoConfig<T> Info;
public InfoConfig<T> Info { get; set; }
[DataMember(Name = "key")]
public string Key
@ -189,7 +189,7 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "permissions")]
public PermissionsConfig Permissions;
public PermissionsConfig Permissions { get; set; }
[DataMember(Name = "title")]
public string Title
@ -212,8 +212,8 @@ namespace ASC.Web.Files.Services.DocumentService
}
}
public DocumentServiceConnector DocumentServiceConnector { get; }
public PathProvider PathProvider { get; }
private DocumentServiceConnector DocumentServiceConnector { get; }
private PathProvider PathProvider { get; }
}
[DataContract(Name = "info", Namespace = "")]
@ -232,7 +232,7 @@ namespace ASC.Web.Files.Services.DocumentService
[Obsolete("Use owner (since v5.4)")]
[DataMember(Name = "author")]
public string Aouthor
public string Author
{
set { }
get { return File.CreateByString; }
@ -300,8 +300,8 @@ namespace ASC.Web.Files.Services.DocumentService
}
}
public BreadCrumbsManager BreadCrumbsManager { get; }
public FileSharing FileSharing { get; }
private BreadCrumbsManager BreadCrumbsManager { get; }
private FileSharing FileSharing { get; }
}
[DataContract(Name = "permissions", Namespace = "")]
@ -309,28 +309,28 @@ namespace ASC.Web.Files.Services.DocumentService
{
[Obsolete("Since DS v5.5")]
[DataMember(Name = "changeHistory")]
public bool ChangeHistory = false;
public bool ChangeHistory { get; set; } = false;
[DataMember(Name = "comment")]
public bool Comment = true;
public bool Comment { get; set; } = true;
[DataMember(Name = "download")]
public bool Download = true;
public bool Download { get; set; } = true;
[DataMember(Name = "edit")]
public bool Edit = true;
public bool Edit { get; set; } = true;
[DataMember(Name = "fillForms")]
public bool FillForms = true;
public bool FillForms { get; set; } = true;
[DataMember(Name = "print")]
public bool Print = true;
public bool Print { get; set; } = true;
[DataMember(Name = "rename")]
public bool Rename = false;
public bool Rename { get; set; } = false;
[DataMember(Name = "review")]
public bool Review = true;
public bool Review { get; set; } = true;
}
[DataContract(Name = "editorConfig", Namespace = "")]
@ -382,7 +382,7 @@ namespace ASC.Web.Files.Services.DocumentService
private EmbeddedConfig _embeddedConfig;
[DataMember(Name = "actionLink", EmitDefaultValue = false)]
public ActionLinkConfig ActionLink;
public ActionLinkConfig ActionLink { get; set; }
public string ActionLinkString
{
@ -405,7 +405,7 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "callbackUrl", EmitDefaultValue = false)]
public string CallbackUrl;
public string CallbackUrl { get; set; }
[DataMember(Name = "createUrl", EmitDefaultValue = false)]
public string CreateUrl
@ -421,10 +421,10 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "plugins", EmitDefaultValue = false)]
public PluginsConfig Plugins;
public PluginsConfig Plugins { get; set; }
[DataMember(Name = "customization", EmitDefaultValue = false)]
public CustomizationConfig<T> Customization;
public CustomizationConfig<T> Customization { get; set; }
[DataMember(Name = "embedded", EmitDefaultValue = false)]
public EmbeddedConfig Embedded
@ -434,7 +434,7 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "fileChoiceUrl", EmitDefaultValue = false)]
public string FileChoiceUrl;
public string FileChoiceUrl { get; set; }
[DataMember(Name = "lang")]
public string Lang
@ -445,7 +445,7 @@ namespace ASC.Web.Files.Services.DocumentService
//todo: remove old feild after release 5.2+
[DataMember(Name = "mergeFolderUrl", EmitDefaultValue = false)]
public string MergeFolderUrl;
public string MergeFolderUrl { get; set; }
[DataMember(Name = "mode")]
public string Mode
@ -454,19 +454,19 @@ namespace ASC.Web.Files.Services.DocumentService
get { return ModeWrite ? "edit" : "view"; }
}
public UserManager UserManager { get; }
public AuthContext AuthContext { get; }
public FilesLinkUtility FilesLinkUtility { get; }
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
private UserManager UserManager { get; }
private AuthContext AuthContext { get; }
private FilesLinkUtility FilesLinkUtility { get; }
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
[DataMember(Name = "saveAsUrl", EmitDefaultValue = false)]
public string SaveAsUrl;
public string SaveAsUrl { get; set; }
[DataMember(Name = "sharingSettingsUrl", EmitDefaultValue = false)]
public string SharingSettingsUrl;
public string SharingSettingsUrl { get; set; }
[DataMember(Name = "user")]
public UserConfig User;
public UserConfig User { get; set; }
private string GetCreateUrl(FileType fileType)
{
@ -501,17 +501,17 @@ namespace ASC.Web.Files.Services.DocumentService
public class ActionLinkConfig
{
[DataMember(Name = "action", EmitDefaultValue = false)]
public ActionConfig Action;
public ActionConfig Action { get; set; }
[DataContract(Name = "action", Namespace = "")]
public class ActionConfig
{
[DataMember(Name = "type", EmitDefaultValue = false)]
public string Type;
public string Type { get; set; }
[DataMember(Name = "data", EmitDefaultValue = false)]
public string Data;
public string Data { get; set; }
}
@ -530,7 +530,7 @@ namespace ASC.Web.Files.Services.DocumentService
[DataContract(Name = "embedded", Namespace = "")]
public class EmbeddedConfig
{
public string ShareLinkParam;
public string ShareLinkParam { get; set; }
[DataMember(Name = "embedUrl", EmitDefaultValue = false)]
public string EmbedUrl
@ -557,7 +557,7 @@ namespace ASC.Web.Files.Services.DocumentService
public FilesLinkUtility FilesLinkUtility { get; }
[DataMember(Name = "toolbarDocked")]
public string ToolbarDocked = "top";
public string ToolbarDocked { get => "top"; }
public EmbeddedConfig(BaseCommonLinkUtility baseCommonLinkUtility, FilesLinkUtility filesLinkUtility)
{
@ -595,8 +595,8 @@ namespace ASC.Web.Files.Services.DocumentService
}
}
public ConsumerFactory ConsumerFactory { get; }
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
private ConsumerFactory ConsumerFactory { get; }
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
public PluginsConfig(ConsumerFactory consumerFactory, BaseCommonLinkUtility baseCommonLinkUtility)
{
@ -659,7 +659,7 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "customer")]
public CustomerConfig<T> Customer;
public CustomerConfig<T> Customer { get; set; }
[DataMember(Name = "feedback", EmitDefaultValue = false)]
public FeedbackConfig Feedback
@ -698,7 +698,7 @@ namespace ASC.Web.Files.Services.DocumentService
set { }
get
{
if (_configuration.Type == EditorType.Embedded || _configuration.Type == EditorType.External) return null;
if (_configuration.EditorType == EditorType.Embedded || _configuration.EditorType == EditorType.External) return null;
if (!AuthContext.IsAuthenticated) return null;
if (GobackUrl != null)
{
@ -764,7 +764,7 @@ namespace ASC.Web.Files.Services.DocumentService
}
[DataMember(Name = "logo")]
public LogoConfig<T> Logo;
public LogoConfig<T> Logo { get; set; }
[DataMember(Name = "reviewDisplay", EmitDefaultValue = false)]
public string ReviewDisplay
@ -773,17 +773,17 @@ namespace ASC.Web.Files.Services.DocumentService
get { return _configuration.EditorConfig.ModeWrite ? null : "markup"; }
}
public CoreBaseSettings CoreBaseSettings { get; }
public SettingsManager SettingsManager { get; }
public FileUtility FileUtility { get; }
public FilesSettingsHelper FilesSettingsHelper { get; }
public AuthContext AuthContext { get; }
public FileSecurity FileSecurity { get; }
public IDaoFactory DaoFactory { get; }
public GlobalFolderHelper GlobalFolderHelper { get; }
public PathProvider PathProvider { get; }
public WebImageSupplier WebImageSupplier { get; }
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
private CoreBaseSettings CoreBaseSettings { get; }
private SettingsManager SettingsManager { get; }
private FileUtility FileUtility { get; }
private FilesSettingsHelper FilesSettingsHelper { get; }
private AuthContext AuthContext { get; }
private FileSecurity FileSecurity { get; }
private IDaoFactory DaoFactory { get; }
private GlobalFolderHelper GlobalFolderHelper { get; }
private PathProvider PathProvider { get; }
private WebImageSupplier WebImageSupplier { get; }
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
}
[DataContract(Name = "customer", Namespace = "")]
@ -824,26 +824,26 @@ namespace ASC.Web.Files.Services.DocumentService
}
}
public SettingsManager SettingsManager { get; }
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
public TenantLogoHelper TenantLogoHelper { get; }
private SettingsManager SettingsManager { get; }
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
private TenantLogoHelper TenantLogoHelper { get; }
}
[DataContract(Name = "feedback", Namespace = "")]
public class FeedbackConfig
{
[DataMember(Name = "url")]
public string Url;
public string Url { get; set; }
[DataMember(Name = "visible")]
public bool Visible = true;
public bool Visible { get => true; }
}
[DataContract(Name = "goback", Namespace = "")]
public class GobackConfig
{
[DataMember(Name = "url", EmitDefaultValue = false)]
public string Url;
public string Url { get; set; }
}
[DataContract(Name = "logo", Namespace = "")]
@ -872,7 +872,7 @@ namespace ASC.Web.Files.Services.DocumentService
get
{
return
_configuration.Type == EditorType.Embedded
_configuration.EditorType == EditorType.Embedded
? null
: BaseCommonLinkUtility.GetFullAbsolutePath(TenantLogoHelper.GetLogo(WhiteLabelLogoTypeEnum.DocsEditor, !_configuration.EditorConfig.Customization.IsRetina));
}
@ -885,7 +885,7 @@ namespace ASC.Web.Files.Services.DocumentService
get
{
return
_configuration.Type != EditorType.Embedded
_configuration.EditorType != EditorType.Embedded
? null
: BaseCommonLinkUtility.GetFullAbsolutePath(TenantLogoHelper.GetLogo(WhiteLabelLogoTypeEnum.Dark, !_configuration.EditorConfig.Customization.IsRetina));
}
@ -898,19 +898,19 @@ namespace ASC.Web.Files.Services.DocumentService
get { return CompanyWhiteLabelSettings.Instance(SettingsManager).Site; }
}
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
public TenantLogoHelper TenantLogoHelper { get; }
public SettingsManager SettingsManager { get; }
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
private TenantLogoHelper TenantLogoHelper { get; }
private SettingsManager SettingsManager { get; }
}
[DataContract(Name = "user", Namespace = "")]
public class UserConfig
{
[DataMember(Name = "id", EmitDefaultValue = false)]
public string Id;
public string Id { get; set; }
[DataMember(Name = "name", EmitDefaultValue = false)]
public string Name;
public string Name { get; set; }
}
public static class ConfigurationExtention

View File

@ -1,6 +1,7 @@
using System.Text;
using ASC.Api.Core;
using ASC.Api.Core.Auth;
using ASC.Api.Core.Core;
using ASC.Api.Core.Middleware;
@ -22,7 +23,6 @@ using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace ASC.Files
{
@ -44,10 +44,14 @@ namespace ASC.Files
services.AddHttpContextAccessor();
services.AddControllers()
.AddNewtonsoftJson()
.AddXmlSerializerFormatters();
services.AddTransient<IConfigureOptions<MvcNewtonsoftJsonOptions>, CustomJsonOptionsWrapper>();
.AddXmlSerializerFormatters()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = false;
options.JsonSerializerOptions.IgnoreNullValues = true;
options.JsonSerializerOptions.Converters.Add(new ApiDateTimeConverter());
options.JsonSerializerOptions.Converters.Add(new FileEntryWrapperConverter());
});
services.AddMemoryCache();

View File

@ -182,7 +182,7 @@ namespace ASC.Employee.Core.Controllers
[Read("@self")]
public EmployeeWraper Self()
{
return EmployeeWraperFullHelper.GetFull(UserManager.GetUsers(SecurityContext.CurrentAccount.ID));
return EmployeeWraperFullHelper.GetFull(UserManager.GetUser(SecurityContext.CurrentAccount.ID, EmployeeWraperFullHelper.GetExpression(ApiContext)));
}
[Read("email")]

View File

@ -27,7 +27,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using ASC.Common;
using ASC.Core;
@ -35,30 +35,21 @@ using ASC.Core.Users;
namespace ASC.Web.Api.Models
{
[DataContract(Name = "group", Namespace = "")]
public class GroupWrapperFull
{
[DataMember(Order = 5)]
public string Description { get; set; }
[DataMember(Order = 2)]
public string Name { get; set; }
[DataMember(Order = 4, EmitDefaultValue = true)]
public Guid? Parent { get; set; }
[DataMember(Order = 3)]
public Guid Category { get; set; }
[DataMember(Order = 1)]
public Guid Id { get; set; }
[DataMember(Order = 9, EmitDefaultValue = true)]
public EmployeeWraper Manager { get; set; }
[DataMember(Order = 10, EmitDefaultValue = false)]
public List<EmployeeWraper> Members { get; set; }
public UserManager UserManager { get; }
public static GroupWrapperFull GetSample()
{

View File

@ -25,12 +25,11 @@
using System;
using System.Runtime.Serialization;
using ASC.Web.Core.Users;
namespace ASC.Web.Api.Models
{
[DataContract]
public class ThumbnailsDataWrapper
{
public ThumbnailsDataWrapper(Guid userId, UserPhotoManager userPhotoManager)
@ -47,22 +46,16 @@ namespace ASC.Web.Api.Models
{
}
[DataMember]
public string Original { get; set; }
[DataMember]
public string Retina { get; set; }
[DataMember]
public string Max { get; set; }
[DataMember]
public string Big { get; set; }
[DataMember]
public string Medium { get; set; }
[DataMember]
public string Small { get; set; }

View File

@ -1,6 +1,7 @@
using System;
using ASC.Api.Core;
using ASC.Api.Core.Auth;
using ASC.Api.Core.Core;
using ASC.Api.Core.Middleware;
@ -24,7 +25,6 @@ using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace ASC.People
{
@ -44,10 +44,13 @@ namespace ASC.People
services.AddHttpContextAccessor();
services.AddControllers()
.AddNewtonsoftJson()
.AddXmlSerializerFormatters();
services.AddTransient<IConfigureOptions<MvcNewtonsoftJsonOptions>, CustomJsonOptionsWrapper>();
.AddXmlSerializerFormatters()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = false;
options.JsonSerializerOptions.IgnoreNullValues = true;
options.JsonSerializerOptions.Converters.Add(new ApiDateTimeConverter());
});
services.AddAuthentication("cookie")
.AddScheme<AuthenticationSchemeOptions, CookieAuthHandler>("cookie", a => { })

View File

@ -311,9 +311,9 @@ namespace ASC.Api.Settings
[AllowAnonymous]
[Read("cultures")]
public List<CultureInfo> GetSupportedCultures()
public IEnumerable<object> GetSupportedCultures()
{
return SetupInfo.EnabledCultures;
return SetupInfo.EnabledCultures.Select(r => new { r.Name });
}
[Read("timezones")]

View File

@ -24,37 +24,36 @@
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using ASC.Common;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration;
namespace ASC.Api.Settings
{
[DataContract(Name = "buildversion", Namespace = "")]
public class BuildVersion
{
[DataMember]
public string CommunityServer { get; set; }
[DataMember(EmitDefaultValue = false)]
public string DocumentServer { get; set; }
[DataMember(EmitDefaultValue = false)]
public string MailServer { get; set; }
public IConfiguration Configuration { get; }
public BuildVersion(IConfiguration configuration)
{
Configuration = configuration;
}
public string MailServer { get; set; }
[JsonIgnore]
private IConfiguration Configuration { get; }
public BuildVersion(IConfiguration configuration)
{
Configuration = configuration;
}
public BuildVersion GetCurrentBuildVersion()
{
CommunityServer = GetCommunityVersion();
DocumentServer = GetDocumentVersion();
MailServer = GetMailServerVersion();
{
CommunityServer = GetCommunityVersion();
DocumentServer = GetDocumentVersion();
MailServer = GetMailServerVersion();
return this;
}
@ -64,9 +63,9 @@ namespace ASC.Api.Settings
}
private static string GetDocumentVersion()
{
return "";
//TODO
{
return "";
//TODO
/*
if (string.IsNullOrEmpty(FilesLinkUtility.DocServiceApiUrl))
return null;
@ -75,12 +74,12 @@ namespace ASC.Api.Settings
}
private static string GetMailServerVersion()
{
//TODO
return "";
/*
{
//TODO
return "";
/*
try
{
{
var engineFactory = new EngineFactory(
CoreContext.TenantManager.GetCurrentTenant().TenantId,
@ -96,14 +95,14 @@ namespace ASC.Api.Settings
return null;*/
}
}
}
public static class BuildVersionExtension
{
public static DIHelper AddBuildVersionService(this DIHelper services)
{
services.TryAddSingleton<BuildVersion>();
{
services.TryAddSingleton<BuildVersion>();
return services;
}
}

View File

@ -27,7 +27,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using ASC.Core;
using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
@ -37,57 +38,50 @@ using ASC.Web.Studio.Utility;
namespace ASC.Web.Studio.Core.Quota
{
[DataContract(Name = "quota", Namespace = "")]
public class QuotaWrapper
{
[DataMember(Name = "storageSize")]
public ulong StorageSize { get; set; }
[DataMember(Name = "maxFileSize")]
public ulong MaxFileSize { get; set; }
[DataMember(Name = "usedSize")]
public ulong UsedSize { get; set; }
[DataMember(Name = "maxUsersCount")]
public int MaxUsersCount { get; set; }
[DataMember(Name = "usersCount")]
public int UsersCount { get; set; }
[DataMember(Name = "availableSize")]
public ulong AvailableSize
{
get { return Math.Max(0, StorageSize > UsedSize ? StorageSize - UsedSize : 0); }
set { throw new NotImplementedException(); }
}
[DataMember(Name = "availableUsersCount")]
public int AvailableUsersCount
{
get { return Math.Max(0, MaxUsersCount - UsersCount); }
set { throw new NotImplementedException(); }
}
[DataMember(Name = "storageUsage")]
public IList<QuotaUsage> StorageUsage { get; set; }
[DataMember(Name = "userStorageSize")]
public long UserStorageSize { get; set; }
[DataMember(Name = "userUsedSize")]
public long UserUsedSize { get; set; }
[DataMember(Name = "userAvailableSize")]
public long UserAvailableSize
{
get { return Math.Max(0, UserStorageSize - UserUsedSize); }
set { throw new NotImplementedException(); }
}
public TenantExtra TenantExtra { get; }
public TenantStatisticsProvider TenantStatisticsProvider { get; }
public WebItemManager WebItemManager { get; }
[JsonIgnore]
private TenantExtra TenantExtra { get; }
[JsonIgnore]
private TenantStatisticsProvider TenantStatisticsProvider { get; }
[JsonIgnore]
private WebItemManager WebItemManager { get; }
public QuotaWrapper()
{
@ -140,14 +134,10 @@ namespace ASC.Web.Studio.Core.Quota
};
}
[DataContract(Name = "quota_usage", Namespace = "")]
public class QuotaUsage
{
[DataMember(Name = "path")]
public string Path { get; set; }
[DataMember(Name = "size")]
public long Size { get; set; }
}
}

View File

@ -26,27 +26,21 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using ASC.Web.Api.Models;
namespace ASC.Api.Settings
{
[DataContract(Name = "security", Namespace = "")]
public class SecurityWrapper
{
[DataMember]
public string WebItemId { get; set; }
[DataMember]
public IEnumerable<EmployeeWraper> Users { get; set; }
[DataMember]
public IEnumerable<GroupWrapperSummary> Groups { get; set; }
[DataMember]
public bool Enabled { get; set; }
[DataMember]
public bool IsSubItem { get; set; }
public static SecurityWrapper GetSample()

View File

@ -26,42 +26,29 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using ASC.Core.Tenants;
namespace ASC.Api.Settings
{
[DataContract(Name = "settings", Namespace = "")]
public class SettingsWrapper
{
[DataMember(EmitDefaultValue = false)]
public string Timezone { get; set; }
[DataMember(EmitDefaultValue = false)]
public List<string> TrustedDomains { get; set; }
[DataMember(EmitDefaultValue = false)]
public TenantTrustedDomainsType TrustedDomainsType { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Culture { get; set; }
[DataMember(EmitDefaultValue = false)]
public TimeSpan UtcOffset { get; set; }
[DataMember(EmitDefaultValue = false)]
public double UtcHoursOffset { get; set; }
[DataMember(EmitDefaultValue = false)]
public string GreetingSettings { get; set; }
[DataMember(EmitDefaultValue = false)]
public Guid OwnerId { get; set; }
[DataMember(EmitDefaultValue = false)]
public string NameSchemaId { get; set; }
public static SettingsWrapper GetSample()

View File

@ -23,30 +23,20 @@
*
*/
using System.Runtime.Serialization;
namespace ASC.Api.Settings.Smtp
{
[DataContract]
public class SmtpOperationStatus
{
[DataMember]
public bool Completed { get; set; }
[DataMember]
public string Id { get; set; }
[DataMember]
public string Status { get; set; }
[DataMember]
public string Error { get; set; }
[DataMember]
public int Percents { get; set; }
[DataMember]
public string Source { get; set; }
public static SmtpOperationStatus GetSample()

View File

@ -23,36 +23,24 @@
*
*/
using System.Runtime.Serialization;
namespace ASC.Api.Settings.Smtp
{
[DataContract(Name = "quota", Namespace = "")]
public class SmtpSettingsWrapper
{
[DataMember]
public string Host { get; set; }
[DataMember]
public int? Port { get; set; }
[DataMember]
public string SenderAddress { get; set; }
[DataMember]
public string SenderDisplayName { get; set; }
[DataMember]
public string CredentialsUserName { get; set; }
[DataMember]
public string CredentialsUserPassword { get; set; }
[DataMember]
public bool EnableSSL { get; set; }
[DataMember]
public bool EnableAuth { get; set; }
public static SmtpSettingsWrapper GetSample()

View File

@ -1,3 +1,4 @@
using ASC.Api.Core;
using ASC.Api.Core.Auth;
using ASC.Api.Core.Core;
using ASC.Api.Core.Middleware;
@ -18,7 +19,6 @@ using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace ASC.Web.Api
{
@ -38,10 +38,13 @@ namespace ASC.Web.Api
services.AddHttpContextAccessor();
services.AddControllers()
.AddNewtonsoftJson()
.AddXmlSerializerFormatters();
services.AddTransient<IConfigureOptions<MvcNewtonsoftJsonOptions>, CustomJsonOptionsWrapper>();
.AddXmlSerializerFormatters()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = false;
options.JsonSerializerOptions.IgnoreNullValues = true;
options.JsonSerializerOptions.Converters.Add(new ApiDateTimeConverter());
});
services.AddAuthentication("cookie")
.AddScheme<AuthenticationSchemeOptions, CookieAuthHandler>("cookie", a => { })

View File

@ -335,3 +335,13 @@ export function downloadFormatFiles(fileConvertIds, folderIds) {
export function getProgress() {
return request({ method: "get", url: "/files/fileops" });
}
export function copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) {
const data = { destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter };
return request({ method: "put", url: "/files/fileops/copy", data });
}
export function moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) {
const data = { destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter };
return request({ method: "put", url: "/files/fileops/move", data });
}