Merge branch 'feature/files' into feature/media-viewer
This commit is contained in:
commit
e8a0bbcd73
@ -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
|
||||
|
2
build/run/ApiSystemService.bat
Normal file
2
build/run/ApiSystemService.bat
Normal 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
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
26
common/services/ASC.ApiSystem/ASC.ApiSystem.csproj
Normal file
26
common/services/ASC.ApiSystem/ASC.ApiSystem.csproj
Normal 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>
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
172
common/services/ASC.ApiSystem/Classes/AuthHandler.cs
Normal file
172
common/services/ASC.ApiSystem/Classes/AuthHandler.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
97
common/services/ASC.ApiSystem/Classes/CommonConstants.cs
Normal file
97
common/services/ASC.ApiSystem/Classes/CommonConstants.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
349
common/services/ASC.ApiSystem/Classes/CommonMethods.cs
Normal file
349
common/services/ASC.ApiSystem/Classes/CommonMethods.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
45
common/services/ASC.ApiSystem/Classes/EmailAttribute.cs
Normal file
45
common/services/ASC.ApiSystem/Classes/EmailAttribute.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
154
common/services/ASC.ApiSystem/Classes/TimeZonesProvider.cs
Normal file
154
common/services/ASC.ApiSystem/Classes/TimeZonesProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
376
common/services/ASC.ApiSystem/Controllers/CalDavController.cs
Normal file
376
common/services/ASC.ApiSystem/Controllers/CalDavController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
685
common/services/ASC.ApiSystem/Controllers/PortalController.cs
Normal file
685
common/services/ASC.ApiSystem/Controllers/PortalController.cs
Normal 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()
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
207
common/services/ASC.ApiSystem/Controllers/SettingsController.cs
Normal file
207
common/services/ASC.ApiSystem/Controllers/SettingsController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
230
common/services/ASC.ApiSystem/Controllers/TariffController.cs
Normal file
230
common/services/ASC.ApiSystem/Controllers/TariffController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
34
common/services/ASC.ApiSystem/Interfaces/IModel.cs
Normal file
34
common/services/ASC.ApiSystem/Interfaces/IModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
51
common/services/ASC.ApiSystem/Models/CoreSettingsModel.cs
Normal file
51
common/services/ASC.ApiSystem/Models/CoreSettingsModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
51
common/services/ASC.ApiSystem/Models/SettingsModel.cs
Normal file
51
common/services/ASC.ApiSystem/Models/SettingsModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
61
common/services/ASC.ApiSystem/Models/TariffModel.cs
Normal file
61
common/services/ASC.ApiSystem/Models/TariffModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
119
common/services/ASC.ApiSystem/Models/TenantModel.cs
Normal file
119
common/services/ASC.ApiSystem/Models/TenantModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
72
common/services/ASC.ApiSystem/Program.cs
Normal file
72
common/services/ASC.ApiSystem/Program.cs
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
33
common/services/ASC.ApiSystem/Properties/launchSettings.json
Normal file
33
common/services/ASC.ApiSystem/Properties/launchSettings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
115
common/services/ASC.ApiSystem/Startup.cs
Normal file
115
common/services/ASC.ApiSystem/Startup.cs
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
3
common/services/ASC.ApiSystem/appsettings.json
Normal file
3
common/services/ASC.ApiSystem/appsettings.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"pathToConf": "..\\..\\..\\config"
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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)
|
||||
);
|
||||
|
@ -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"
|
||||
}
|
@ -10,5 +10,6 @@
|
||||
"DeleteDialogMessage": "Пожалуйста, обратите внимание, что если вы предоставили кому-то доступ к этим файлам/папкам, они станут недоступны. Вы уверены, что хотите продолжить?",
|
||||
"DeleteDialogNote": "Замечание: Это действие не может быть отменено.",
|
||||
"QuestionDeleteFolder": "Вы собираетесь удалить эту папку?",
|
||||
"QuestionDeleteElements": "Вы собираетесь удалить эти элементы?"
|
||||
"QuestionDeleteElements": "Вы собираетесь удалить эти элементы?",
|
||||
"DeleteOperation": "Удаление"
|
||||
}
|
@ -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) => {
|
||||
|
@ -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"
|
||||
}
|
@ -11,5 +11,6 @@
|
||||
"ConvertToZip": "Файлы будут упакованы в .zip файл",
|
||||
"ConvertMessage": "Если вы решите сконвертировать файл в формат, отличный от исходного, некоторые данные могут быть потеряны.",
|
||||
"DownloadButton": "Скачать",
|
||||
"CancelButton": "Отмена"
|
||||
"CancelButton": "Отмена",
|
||||
"ArchivingData": "Архивирование данных"
|
||||
}
|
@ -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) {
|
||||
|
@ -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)));
|
||||
|
@ -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))
|
||||
);
|
||||
|
@ -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={
|
||||
|
@ -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"
|
||||
}
|
@ -72,5 +72,6 @@
|
||||
"OverwriteSetting": "Перезаписывать существующий файл с таким же именем",
|
||||
"UploadOriginalFormatSetting": "Сохранять также копию файла в исходном формате",
|
||||
"HideWindowSetting": "Показывать это окно минимизированным",
|
||||
"ArchivingData": "Архивирование данных"
|
||||
"ArchivingData": "Архивирование данных",
|
||||
"DeleteOperation": "Удаление"
|
||||
}
|
@ -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;
|
@ -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));
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"Copy": "Copy",
|
||||
"Move": "Move",
|
||||
"CopyOperation": "Copying",
|
||||
"MoveToOperation": "Moving"
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"Copy": "Копирование",
|
||||
"Move": "Перемещение",
|
||||
"CopyOperation": "Копирование",
|
||||
"MoveToOperation": "Перемещение"
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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 }
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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")]
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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; }
|
||||
|
||||
|
||||
|
@ -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 => { })
|
||||
|
@ -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")]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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 => { })
|
||||
|
@ -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 });
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user