Merge pull request #63 from ONLYOFFICE/feature/new-login-page
Feature/new login page
This commit is contained in:
commit
cfc40a6487
@ -41,6 +41,7 @@
|
||||
<PackageReference Include="JWT" Version="6.1.4" />
|
||||
<PackageReference Include="log4net" Version="2.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="3.1.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
|
@ -107,13 +107,15 @@ namespace ASC.Core.Common.Configuration
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache) : this()
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory) : this()
|
||||
{
|
||||
TenantManager = tenantManager;
|
||||
CoreBaseSettings = coreBaseSettings;
|
||||
CoreSettings = coreSettings;
|
||||
Configuration = configuration;
|
||||
Cache = cache;
|
||||
ConsumerFactory = consumerFactory;
|
||||
OnlyDefault = configuration["core:default-consumers"] == "true";
|
||||
Name = "";
|
||||
Order = int.MaxValue;
|
||||
@ -125,8 +127,9 @@ namespace ASC.Core.Common.Configuration
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> additional)
|
||||
: this(tenantManager, coreBaseSettings, coreSettings, configuration, cache)
|
||||
: this(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory)
|
||||
{
|
||||
Name = name;
|
||||
Order = order;
|
||||
@ -140,8 +143,9 @@ namespace ASC.Core.Common.Configuration
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional)
|
||||
: this(tenantManager, coreBaseSettings, coreSettings, configuration, cache)
|
||||
: this(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory)
|
||||
{
|
||||
Name = name;
|
||||
Order = order;
|
||||
@ -300,8 +304,9 @@ namespace ASC.Core.Common.Configuration
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache)
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory)
|
||||
{
|
||||
|
||||
}
|
||||
@ -312,8 +317,9 @@ namespace ASC.Core.Common.Configuration
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, additional)
|
||||
{
|
||||
Init(additional);
|
||||
}
|
||||
@ -324,8 +330,9 @@ namespace ASC.Core.Common.Configuration
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
Init(additional);
|
||||
}
|
||||
@ -361,12 +368,12 @@ namespace ASC.Core.Common.Configuration
|
||||
var additional = fromConfig.AdditionalKeys.ToDictionary(prop => prop, prop => fromConfig[prop]);
|
||||
additional.Add(HandlerTypeKey, HandlerType.AssemblyQualifiedName);
|
||||
|
||||
return new DataStoreConsumer(fromConfig.TenantManager, fromConfig.CoreBaseSettings, fromConfig.CoreSettings, fromConfig.Configuration, fromConfig.Cache, fromConfig.Name, fromConfig.Order, props, additional);
|
||||
return new DataStoreConsumer(fromConfig.TenantManager, fromConfig.CoreBaseSettings, fromConfig.CoreSettings, fromConfig.Configuration, fromConfig.Cache, fromConfig.ConsumerFactory, fromConfig.Name, fromConfig.Order, props, additional);
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new DataStoreConsumer(TenantManager, CoreBaseSettings, CoreSettings, Configuration, Cache, Name, Order, Props.ToDictionary(r => r.Key, r => r.Value), Additional.ToDictionary(r => r.Key, r => r.Value));
|
||||
return new DataStoreConsumer(TenantManager, CoreBaseSettings, CoreSettings, Configuration, Cache, ConsumerFactory, Name, Order, Props.ToDictionary(r => r.Key, r => r.Value), Additional.ToDictionary(r => r.Key, r => r.Value));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,8 @@ namespace ASC.Core
|
||||
|
||||
public string[] Methods { get; set; }
|
||||
|
||||
public string MethodsFromDb { set { Methods = value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } }
|
||||
|
||||
|
||||
public static implicit operator SubscriptionMethod(SubscriptionMethodCache cache)
|
||||
{
|
||||
|
@ -47,8 +47,8 @@ namespace ASC.Core.Data
|
||||
{
|
||||
ActionId = r.Action,
|
||||
ObjectId = r.Object,
|
||||
RecipientId = r.Recipient.ToString(),
|
||||
SourceId = r.Source.ToString(),
|
||||
RecipientId = r.Recipient,
|
||||
SourceId = r.Source,
|
||||
Subscribed = !r.Unsubscribed,
|
||||
Tenant = r.Tenant
|
||||
};
|
||||
@ -56,10 +56,10 @@ namespace ASC.Core.Data
|
||||
FromDbSubscriptionMethodToSubscriptionMethod = r => new SubscriptionMethod
|
||||
{
|
||||
ActionId = r.Action,
|
||||
RecipientId = r.Recipient.ToString(),
|
||||
SourceId = r.Source.ToString(),
|
||||
RecipientId = r.Recipient,
|
||||
SourceId = r.Source,
|
||||
Tenant = r.Tenant,
|
||||
Methods = r.Sender.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
MethodsFromDb = r.Sender
|
||||
};
|
||||
}
|
||||
|
||||
@ -164,8 +164,9 @@ namespace ASC.Core.Data
|
||||
q = q.Where(r => r.Recipient == recipientId);
|
||||
}
|
||||
|
||||
var a = q.OrderBy(r => r.Tenant)
|
||||
.GroupBy(r => new { r.Recipient, r.Action }, (x, y) => y.First());
|
||||
var a = q
|
||||
.OrderBy(r => r.Tenant)
|
||||
.Distinct();
|
||||
|
||||
|
||||
var methods = a.Select(FromDbSubscriptionMethodToSubscriptionMethod).ToList();
|
||||
|
@ -26,25 +26,25 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
using ASC.Common;
|
||||
using ASC.Common.Utils;
|
||||
using ASC.Core.Common.Configuration;
|
||||
using ASC.FederatedLogin.Helpers;
|
||||
using ASC.FederatedLogin.LoginProviders;
|
||||
using ASC.FederatedLogin.Profile;
|
||||
using ASC.Security.Cryptography;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ASC.FederatedLogin
|
||||
{
|
||||
@ -52,33 +52,30 @@ namespace ASC.FederatedLogin
|
||||
{
|
||||
private Dictionary<string, string> _params;
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
private IWebHostEnvironment WebHostEnvironment { get; }
|
||||
private IMemoryCache MemoryCache { get; }
|
||||
public Signature Signature { get; }
|
||||
public InstanceCrypto InstanceCrypto { get; }
|
||||
public ConsumerFactory ConsumerFactory { get; }
|
||||
public ProviderManager ProviderManager { get; }
|
||||
|
||||
public Login(
|
||||
RequestDelegate next,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
IMemoryCache memoryCache,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
ConsumerFactory consumerFactory)
|
||||
ProviderManager providerManager)
|
||||
{
|
||||
_next = next;
|
||||
WebHostEnvironment = webHostEnvironment;
|
||||
MemoryCache = memoryCache;
|
||||
Signature = signature;
|
||||
InstanceCrypto = instanceCrypto;
|
||||
ConsumerFactory = consumerFactory;
|
||||
ProviderManager = providerManager;
|
||||
}
|
||||
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
context.PushRewritenUri();
|
||||
var scope = context.PushRewritenUri();
|
||||
|
||||
if (string.IsNullOrEmpty(context.Request.Query["p"]))
|
||||
{
|
||||
@ -91,21 +88,22 @@ namespace ASC.FederatedLogin
|
||||
|
||||
//Pack and redirect
|
||||
var uriBuilder = new UriBuilder(context.Request.GetUrlRewriter());
|
||||
var token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_params)));
|
||||
var token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(_params)));
|
||||
uriBuilder.Query = "p=" + token;
|
||||
context.Response.Redirect(uriBuilder.Uri.ToString(), true);
|
||||
await context.Response.CompleteAsync();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_params = ((Dictionary<string, object>)JsonConvert.DeserializeObject(
|
||||
Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(context.Request.Query["p"])))).ToDictionary(x => x.Key, y => (string)y.Value);
|
||||
_params = JsonSerializer.Deserialize<Dictionary<string, string>>(Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(context.Request.Query["p"])));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Auth))
|
||||
{
|
||||
try
|
||||
{
|
||||
var profile = ProviderManager.Process(Auth, Signature, InstanceCrypto, ConsumerFactory, context, _params);
|
||||
var profile = ProviderManager.Process(Auth, context, _params);
|
||||
if (profile != null)
|
||||
{
|
||||
await SendClientData(context, profile);
|
||||
@ -185,7 +183,7 @@ namespace ASC.FederatedLogin
|
||||
switch (Mode)
|
||||
{
|
||||
case LoginMode.Redirect:
|
||||
RedirectToReturnUrl(context, profile);
|
||||
await RedirectToReturnUrl(context, profile);
|
||||
break;
|
||||
case LoginMode.Popup:
|
||||
await SendJsCallback(context, profile);
|
||||
@ -200,7 +198,7 @@ namespace ASC.FederatedLogin
|
||||
await context.Response.WriteAsync(JsCallbackHelper.GetCallbackPage().Replace("%PROFILE%", profile.ToJson()).Replace("%CALLBACK%", Callback));
|
||||
}
|
||||
|
||||
private void RedirectToReturnUrl(HttpContext context, LoginProfile profile)
|
||||
private async Task RedirectToReturnUrl(HttpContext context, LoginProfile profile)
|
||||
{
|
||||
var useMinimalProfile = Minimal;
|
||||
if (useMinimalProfile)
|
||||
@ -219,6 +217,49 @@ namespace ASC.FederatedLogin
|
||||
{
|
||||
context.Response.Redirect(new Uri(ReturnUrl, UriKind.Absolute).AddProfile(profile).ToString(), true);
|
||||
}
|
||||
|
||||
await context.Response.CompleteAsync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginHandler
|
||||
{
|
||||
public RequestDelegate Next { get; }
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public LoginHandler(RequestDelegate next, IServiceProvider serviceProvider)
|
||||
{
|
||||
Next = next;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var login = scope.ServiceProvider.GetService<Login>();
|
||||
await login.Invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginHandlerExtensions
|
||||
{
|
||||
public static DIHelper AddLoginHandlerService(this DIHelper services)
|
||||
{
|
||||
if (services.TryAddScoped<Login>())
|
||||
{
|
||||
return services
|
||||
.AddSignatureService()
|
||||
.AddInstanceCryptoService()
|
||||
.AddProviderManagerService();
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseLoginHandler(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<LoginHandler>();
|
||||
}
|
||||
}
|
||||
}
|
@ -82,10 +82,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
Signature = signature;
|
||||
InstanceCrypto = instanceCrypto;
|
||||
@ -95,7 +96,12 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = Auth(context, Scopes);
|
||||
var token = Auth(context, Scopes, out var redirect);
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetLoginProfile(token?.AccessToken);
|
||||
}
|
||||
@ -109,7 +115,7 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual OAuth20Token Auth(HttpContext context, string scopes, Dictionary<string, string> additionalArgs = null)
|
||||
protected virtual OAuth20Token Auth(HttpContext context, string scopes, out bool redirect, Dictionary<string, string> additionalArgs = null)
|
||||
{
|
||||
var error = context.Request.Query["error"];
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
@ -125,9 +131,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
if (string.IsNullOrEmpty(code))
|
||||
{
|
||||
OAuth20TokenHelper.RequestCode<T>(context, ConsumerFactory, scopes, additionalArgs);
|
||||
redirect = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
redirect = false;
|
||||
return OAuth20TokenHelper.GetAccessToken<T>(ConsumerFactory, code);
|
||||
}
|
||||
|
||||
|
@ -66,8 +66,9 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -62,8 +62,9 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -66,8 +66,9 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -62,8 +62,9 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -59,10 +59,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional) { }
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional) { }
|
||||
|
||||
public override LoginProfile GetLoginProfile(string accessToken)
|
||||
{
|
||||
|
@ -74,10 +74,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional) { }
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional) { }
|
||||
|
||||
public override LoginProfile GetLoginProfile(string accessToken)
|
||||
{
|
||||
@ -89,7 +90,7 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
|
||||
public OAuth20Token Auth(HttpContext context)
|
||||
{
|
||||
return Auth(context, GoogleScopeContacts, (context.Request.Query["access_type"].ToString() ?? "") == "offline"
|
||||
return Auth(context, GoogleScopeContacts, out var _, (context.Request.Query["access_type"].ToString() ?? "") == "offline"
|
||||
? new Dictionary<string, string>
|
||||
{
|
||||
{ "access_type", "offline" },
|
||||
|
@ -102,10 +102,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
|
||||
@ -114,7 +115,12 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = Auth(context, Scopes);
|
||||
var token = Auth(context, Scopes, out var redirect);
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
@ -133,7 +139,7 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
}
|
||||
}
|
||||
|
||||
protected override OAuth20Token Auth(HttpContext context, string scopes, Dictionary<string, string> additionalArgs = null)
|
||||
protected override OAuth20Token Auth(HttpContext context, string scopes, out bool redirect, Dictionary<string, string> additionalArgs = null)
|
||||
{
|
||||
var error = context.Request.Query["error"];
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
@ -149,8 +155,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
if (string.IsNullOrEmpty(code))
|
||||
{
|
||||
RequestCode(context, scopes);
|
||||
redirect = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
redirect = false;
|
||||
var state = context.Request.Query["state"];
|
||||
return GetAccessToken(state, code);
|
||||
}
|
||||
|
@ -82,10 +82,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional) { }
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional) { }
|
||||
|
||||
public override LoginProfile GetLoginProfile(string accessToken)
|
||||
{
|
||||
|
@ -85,10 +85,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
|
||||
@ -96,7 +97,12 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = Auth(context, Scopes);
|
||||
var token = Auth(context, Scopes, out var redirect);
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
|
@ -65,8 +65,9 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using ASC.Common;
|
||||
using ASC.Common.Utils;
|
||||
using ASC.Core.Common.Configuration;
|
||||
using ASC.FederatedLogin.Profile;
|
||||
@ -38,21 +40,44 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
public class ProviderManager
|
||||
{
|
||||
public static ILoginProvider GetLoginProvider(string providerType, Signature signature, InstanceCrypto instanceCrypto, ConsumerFactory consumerFactory)
|
||||
public static List<string> AuthProviders = new List<string>
|
||||
{
|
||||
ProviderConstants.Google,
|
||||
ProviderConstants.Facebook,
|
||||
ProviderConstants.Twitter,
|
||||
ProviderConstants.LinkedIn,
|
||||
ProviderConstants.MailRu,
|
||||
ProviderConstants.VK,
|
||||
ProviderConstants.Yandex,
|
||||
ProviderConstants.GosUslugi
|
||||
};
|
||||
|
||||
public Signature Signature { get; }
|
||||
public InstanceCrypto InstanceCrypto { get; }
|
||||
public ConsumerFactory ConsumerFactory { get; }
|
||||
|
||||
public ProviderManager(Signature signature, InstanceCrypto instanceCrypto, ConsumerFactory consumerFactory)
|
||||
{
|
||||
Signature = signature;
|
||||
InstanceCrypto = instanceCrypto;
|
||||
ConsumerFactory = consumerFactory;
|
||||
}
|
||||
|
||||
public ILoginProvider GetLoginProvider(string providerType)
|
||||
{
|
||||
return providerType == ProviderConstants.OpenId
|
||||
? new OpenIdLoginProvider(signature, instanceCrypto, consumerFactory)
|
||||
: consumerFactory.GetByKey(providerType) as ILoginProvider;
|
||||
? new OpenIdLoginProvider(Signature, InstanceCrypto, ConsumerFactory)
|
||||
: ConsumerFactory.GetByKey(providerType) as ILoginProvider;
|
||||
}
|
||||
|
||||
public static LoginProfile Process(string providerType, Signature signature, InstanceCrypto instanceCrypto, ConsumerFactory consumerFactory, HttpContext context, IDictionary<string, string> @params)
|
||||
public LoginProfile Process(string providerType, HttpContext context, IDictionary<string, string> @params)
|
||||
{
|
||||
return GetLoginProvider(providerType, signature, instanceCrypto, consumerFactory).ProcessAuthoriztion(context, @params);
|
||||
return GetLoginProvider(providerType).ProcessAuthoriztion(context, @params);
|
||||
}
|
||||
|
||||
public static LoginProfile GetLoginProfile(string providerType, string accessToken, Signature signature, InstanceCrypto instanceCrypto, ConsumerFactory consumerFactory)
|
||||
public LoginProfile GetLoginProfile(string providerType, string accessToken)
|
||||
{
|
||||
var consumer = GetLoginProvider(providerType, signature, instanceCrypto, consumerFactory);
|
||||
var consumer = GetLoginProvider(providerType);
|
||||
if (consumer == null) throw new ArgumentException("Unknown provider type", "providerType");
|
||||
|
||||
try
|
||||
@ -61,8 +86,34 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return LoginProfile.FromError(signature, instanceCrypto, ex);
|
||||
return LoginProfile.FromError(Signature, InstanceCrypto, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsNotEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return AuthProviders
|
||||
.Select(GetLoginProvider)
|
||||
.Any(loginProvider => loginProvider != null && loginProvider.IsEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProviderManagerServiceExtension
|
||||
{
|
||||
public static DIHelper AddProviderManagerService(this DIHelper services)
|
||||
{
|
||||
if (services.TryAddScoped<ProviderManager>())
|
||||
{
|
||||
return services
|
||||
.AddSignatureService()
|
||||
.AddInstanceCryptoService()
|
||||
.AddConsumerFactoryService();
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
@ -89,10 +89,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
|
||||
@ -101,13 +102,18 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = Auth(context, Scopes, context.Request.Query["access_type"] == "offline"
|
||||
var token = Auth(context, Scopes, out var redirect, context.Request.Query["access_type"] == "offline"
|
||||
? new Dictionary<string, string>
|
||||
{
|
||||
{ "revoke", "1" }
|
||||
}
|
||||
: null);
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
throw new Exception("Login failed");
|
||||
|
@ -54,10 +54,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -57,14 +57,15 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional) { }
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional) { }
|
||||
|
||||
public OAuth20Token Auth(HttpContext context)
|
||||
{
|
||||
return Auth(context, Scopes);
|
||||
return Auth(context, Scopes, out var _);
|
||||
}
|
||||
|
||||
public override LoginProfile GetLoginProfile(string accessToken)
|
||||
|
@ -82,10 +82,11 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
Signature signature,
|
||||
InstanceCrypto instanceCrypto,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, signature, instanceCrypto, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, signature, instanceCrypto, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
|
||||
@ -93,12 +94,16 @@ namespace ASC.FederatedLogin.LoginProviders
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = Auth(context, Scopes, context.Request.Query["access_type"] == "offline"
|
||||
var token = Auth(context, Scopes, out var redirect, context.Request.Query["access_type"] == "offline"
|
||||
? new Dictionary<string, string>
|
||||
{
|
||||
{ "force_confirm", "true" }
|
||||
}
|
||||
: null);
|
||||
if (redirect)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetLoginProfile(token?.AccessToken);
|
||||
}
|
||||
|
@ -41,11 +41,11 @@
|
||||
"type": "ASC.FederatedLogin.LoginProviders.BoxLoginProvider, ASC.FederatedLogin"
|
||||
},
|
||||
{
|
||||
"key": "Box",
|
||||
"key": "box",
|
||||
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
|
||||
},
|
||||
{
|
||||
"key": "Box",
|
||||
"key": "box",
|
||||
"type": "ASC.FederatedLogin.LoginProviders.BoxLoginProvider, ASC.FederatedLogin"
|
||||
}
|
||||
],
|
||||
@ -191,11 +191,11 @@
|
||||
"type": "ASC.FederatedLogin.LoginProviders.FacebookLoginProvider, ASC.FederatedLogin"
|
||||
},
|
||||
{
|
||||
"key": "Facebook",
|
||||
"key": "facebook",
|
||||
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
|
||||
},
|
||||
{
|
||||
"key": "Facebook",
|
||||
"key": "facebook",
|
||||
"type": "ASC.FederatedLogin.LoginProviders.FacebookLoginProvider, ASC.FederatedLogin"
|
||||
}
|
||||
],
|
||||
@ -252,11 +252,11 @@
|
||||
"type": "ASC.FederatedLogin.LoginProviders.GoogleLoginProvider, ASC.FederatedLogin"
|
||||
},
|
||||
{
|
||||
"key": "Google",
|
||||
"key": "google",
|
||||
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
|
||||
},
|
||||
{
|
||||
"key": "Google",
|
||||
"key": "google",
|
||||
"type": "ASC.FederatedLogin.LoginProviders.GoogleLoginProvider, ASC.FederatedLogin"
|
||||
}
|
||||
],
|
||||
@ -283,11 +283,11 @@
|
||||
"type": "ASC.FederatedLogin.LoginProviders.LinkedInLoginProvider, ASC.FederatedLogin"
|
||||
},
|
||||
{
|
||||
"key": "LinkedIn",
|
||||
"key": "linkedin",
|
||||
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
|
||||
},
|
||||
{
|
||||
"key": "LinkedIn",
|
||||
"key": "linkedin",
|
||||
"type": "ASC.FederatedLogin.LoginProviders.LinkedInLoginProvider, ASC.FederatedLogin"
|
||||
}
|
||||
],
|
||||
@ -406,11 +406,11 @@
|
||||
"type": "ASC.FederatedLogin.LoginProviders.TwitterLoginProvider, ASC.FederatedLogin"
|
||||
},
|
||||
{
|
||||
"key": "Twitter",
|
||||
"key": "twitter",
|
||||
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
|
||||
},
|
||||
{
|
||||
"key": "Twitter",
|
||||
"key": "twitter",
|
||||
"type": "ASC.FederatedLogin.LoginProviders.TwitterLoginProvider, ASC.FederatedLogin"
|
||||
}
|
||||
],
|
||||
|
@ -95,6 +95,11 @@ server {
|
||||
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
|
||||
}
|
||||
|
||||
location /login.ashx {
|
||||
proxy_pass http://localhost:5003;
|
||||
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
|
||||
}
|
||||
|
||||
location /products {
|
||||
location ~* /people {
|
||||
#rewrite products/people/(.*) /$1 break;
|
||||
|
@ -74,11 +74,12 @@ namespace ASC.Web.Files.Helpers
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory factory,
|
||||
string name,
|
||||
int order,
|
||||
Dictionary<string, string> props,
|
||||
Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, factory, name, order, props, additional)
|
||||
{
|
||||
Log = option.CurrentValue;
|
||||
}
|
||||
|
@ -144,8 +144,9 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, additional)
|
||||
{
|
||||
PathProvider = pathProvider;
|
||||
TenantUtil = tenantUtil;
|
||||
|
@ -154,8 +154,9 @@ namespace ASC.Web.Files.ThirdPartyApp
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, additional)
|
||||
{
|
||||
Logger = option.CurrentValue;
|
||||
PathProvider = pathProvider;
|
||||
|
@ -30,7 +30,9 @@ using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.ServiceModel.Security;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
|
||||
using ASC.Api.Collections;
|
||||
@ -39,6 +41,7 @@ using ASC.Api.Utils;
|
||||
using ASC.Common;
|
||||
using ASC.Common.Logging;
|
||||
using ASC.Common.Utils;
|
||||
using ASC.Common.Web;
|
||||
using ASC.Core;
|
||||
using ASC.Core.Billing;
|
||||
using ASC.Core.Common.Configuration;
|
||||
@ -48,12 +51,16 @@ using ASC.Core.Users;
|
||||
using ASC.Data.Storage;
|
||||
using ASC.Data.Storage.Configuration;
|
||||
using ASC.Data.Storage.Migration;
|
||||
using ASC.FederatedLogin;
|
||||
using ASC.FederatedLogin.LoginProviders;
|
||||
using ASC.FederatedLogin.Profile;
|
||||
using ASC.IPSecurity;
|
||||
using ASC.MessagingSystem;
|
||||
using ASC.Security.Cryptography;
|
||||
using ASC.Web.Api.Models;
|
||||
using ASC.Web.Api.Routing;
|
||||
using ASC.Web.Core;
|
||||
using ASC.Web.Core.Mobile;
|
||||
using ASC.Web.Core.PublicResources;
|
||||
using ASC.Web.Core.Sms;
|
||||
using ASC.Web.Core.Users;
|
||||
@ -74,6 +81,7 @@ using ASC.Web.Studio.Utility;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@ -99,6 +107,11 @@ namespace ASC.Api.Settings
|
||||
public SmsProviderManager SmsProviderManager { get; }
|
||||
public TimeZoneConverter TimeZoneConverter { get; }
|
||||
public CustomNamingPeople CustomNamingPeople { get; }
|
||||
public IPSecurity.IPSecurity IpSecurity { get; }
|
||||
public IMemoryCache MemoryCache { get; }
|
||||
public ProviderManager ProviderManager { get; }
|
||||
public MobileDetector MobileDetector { get; }
|
||||
public IOptionsSnapshot<AccountLinker> AccountLinker { get; }
|
||||
public UserManager UserManager { get; }
|
||||
public TenantManager TenantManager { get; }
|
||||
public TenantExtra TenantExtra { get; }
|
||||
@ -178,7 +191,12 @@ namespace ASC.Api.Settings
|
||||
ConsumerFactory consumerFactory,
|
||||
SmsProviderManager smsProviderManager,
|
||||
TimeZoneConverter timeZoneConverter,
|
||||
CustomNamingPeople customNamingPeople)
|
||||
CustomNamingPeople customNamingPeople,
|
||||
IPSecurity.IPSecurity ipSecurity,
|
||||
IMemoryCache memoryCache,
|
||||
ProviderManager providerManager,
|
||||
MobileDetector mobileDetector,
|
||||
IOptionsSnapshot<AccountLinker> accountLinker)
|
||||
{
|
||||
Log = option.Get("ASC.Api");
|
||||
WebHostEnvironment = webHostEnvironment;
|
||||
@ -188,6 +206,11 @@ namespace ASC.Api.Settings
|
||||
SmsProviderManager = smsProviderManager;
|
||||
TimeZoneConverter = timeZoneConverter;
|
||||
CustomNamingPeople = customNamingPeople;
|
||||
IpSecurity = ipSecurity;
|
||||
MemoryCache = memoryCache;
|
||||
ProviderManager = providerManager;
|
||||
MobileDetector = mobileDetector;
|
||||
AccountLinker = accountLinker;
|
||||
MessageService = messageService;
|
||||
StudioNotifyService = studioNotifyService;
|
||||
ApiContext = apiContext;
|
||||
@ -247,10 +270,158 @@ namespace ASC.Api.Settings
|
||||
settings.OwnerId = Tenant.OwnerId;
|
||||
settings.NameSchemaId = CustomNamingPeople.Current.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.EnabledJoin =
|
||||
(Tenant.TrustedDomainsType == TenantTrustedDomainsType.Custom &&
|
||||
Tenant.TrustedDomains.Count > 0) ||
|
||||
Tenant.TrustedDomainsType == TenantTrustedDomainsType.All;
|
||||
|
||||
var studioAdminMessageSettings = SettingsManager.Load<StudioAdminMessageSettings>();
|
||||
|
||||
settings.EnableAdmMess = studioAdminMessageSettings.Enable || TenantExtra.IsNotPaid();
|
||||
|
||||
settings.ThirdpartyEnable = SetupInfo.ThirdPartyAuthEnabled && ProviderManager.IsNotEmpty;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
[Create("messagesettings")]
|
||||
public ContentResult EnableAdminMessageSettings(AdminMessageSettingsModel model)
|
||||
{
|
||||
PermissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
|
||||
|
||||
SettingsManager.Save(new StudioAdminMessageSettings { Enable = model.TurnOn });
|
||||
|
||||
MessageService.Send(MessageAction.AdministratorMessageSettingsUpdated);
|
||||
|
||||
return new ContentResult() { Content = Resource.SuccessfullySaveSettingsMessage };
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Create("sendadmmail")]
|
||||
public ContentResult SendAdmMail(AdminMessageSettingsModel model)
|
||||
{
|
||||
var studioAdminMessageSettings = SettingsManager.Load<StudioAdminMessageSettings>();
|
||||
var enableAdmMess = studioAdminMessageSettings.Enable || TenantExtra.IsNotPaid();
|
||||
|
||||
if (!enableAdmMess)
|
||||
throw new MethodAccessException("Method not available");
|
||||
|
||||
if (!model.Email.TestEmailRegex())
|
||||
throw new Exception(Resource.ErrorNotCorrectEmail);
|
||||
|
||||
if (string.IsNullOrEmpty(model.Message))
|
||||
throw new Exception(Resource.ErrorEmptyMessage);
|
||||
|
||||
CheckCache("sendadmmail");
|
||||
|
||||
StudioNotifyService.SendMsgToAdminFromNotAuthUser(model.Email, model.Message);
|
||||
MessageService.Send(MessageAction.ContactAdminMailSent);
|
||||
|
||||
return new ContentResult() { Content = Resource.AdminMessageSent };
|
||||
}
|
||||
|
||||
[Create("maildomainsettings")]
|
||||
public ContentResult SaveMailDomainSettings(MailDomainSettingsModel model)
|
||||
{
|
||||
PermissionContext.DemandPermissions(SecutiryConstants.EditPortalSettings);
|
||||
|
||||
if (model.Type == TenantTrustedDomainsType.Custom)
|
||||
{
|
||||
Tenant.TrustedDomains.Clear();
|
||||
foreach (var d in model.Domains.Select(domain => (domain ?? "").Trim().ToLower()))
|
||||
{
|
||||
if (!(!string.IsNullOrEmpty(d) && new Regex("^[a-z0-9]([a-z0-9-.]){1,98}[a-z0-9]$").IsMatch(d)))
|
||||
return new ContentResult() { Content = Resource.ErrorNotCorrectTrustedDomain };
|
||||
|
||||
Tenant.TrustedDomains.Add(d);
|
||||
}
|
||||
|
||||
if (Tenant.TrustedDomains.Count == 0)
|
||||
model.Type = TenantTrustedDomainsType.None;
|
||||
}
|
||||
|
||||
Tenant.TrustedDomainsType = model.Type;
|
||||
|
||||
SettingsManager.Save(new StudioTrustedDomainSettings { InviteUsersAsVisitors = model.InviteUsersAsVisitors });
|
||||
|
||||
TenantManager.SaveTenant(Tenant);
|
||||
|
||||
MessageService.Send(MessageAction.TrustedMailDomainSettingsUpdated);
|
||||
|
||||
return new ContentResult { Content = Resource.SuccessfullySaveSettingsMessage };
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Create("sendjoininvite")]
|
||||
public ContentResult SendJoinInviteMail(AdminMessageSettingsModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var email = model.Email;
|
||||
if (!(
|
||||
(Tenant.TrustedDomainsType == TenantTrustedDomainsType.Custom &&
|
||||
Tenant.TrustedDomains.Count > 0) ||
|
||||
Tenant.TrustedDomainsType == TenantTrustedDomainsType.All))
|
||||
throw new MethodAccessException("Method not available");
|
||||
|
||||
if (!email.TestEmailRegex())
|
||||
throw new Exception(Resource.ErrorNotCorrectEmail);
|
||||
|
||||
CheckCache("sendjoininvite");
|
||||
|
||||
var user = UserManager.GetUserByEmail(email);
|
||||
if (!user.ID.Equals(Constants.LostUser.ID))
|
||||
throw new Exception(CustomNamingPeople.Substitute<Resource>("ErrorEmailAlreadyExists"));
|
||||
|
||||
var settings = SettingsManager.Load<IPRestrictionsSettings>();
|
||||
|
||||
if (settings.Enable && !IpSecurity.Verify())
|
||||
throw new Exception(Resource.ErrorAccessRestricted);
|
||||
|
||||
var trustedDomainSettings = SettingsManager.Load<StudioTrustedDomainSettings>();
|
||||
var emplType = trustedDomainSettings.InviteUsersAsVisitors ? EmployeeType.Visitor : EmployeeType.User;
|
||||
var enableInviteUsers = TenantStatisticsProvider.GetUsersCount() < TenantExtra.GetTenantQuota().ActiveUsers;
|
||||
|
||||
if (!enableInviteUsers)
|
||||
emplType = EmployeeType.Visitor;
|
||||
|
||||
switch (Tenant.TrustedDomainsType)
|
||||
{
|
||||
case TenantTrustedDomainsType.Custom:
|
||||
{
|
||||
var address = new MailAddress(email);
|
||||
if (Tenant.TrustedDomains.Any(d => address.Address.EndsWith("@" + d, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
StudioNotifyService.SendJoinMsg(email, emplType);
|
||||
MessageService.Send(MessageInitiator.System, MessageAction.SentInviteInstructions, email);
|
||||
return new ContentResult() { Content = Resource.FinishInviteJoinEmailMessage };
|
||||
}
|
||||
|
||||
throw new Exception(Resource.ErrorEmailDomainNotAllowed);
|
||||
}
|
||||
case TenantTrustedDomainsType.All:
|
||||
{
|
||||
StudioNotifyService.SendJoinMsg(email, emplType);
|
||||
MessageService.Send(MessageInitiator.System, MessageAction.SentInviteInstructions, email);
|
||||
return new ContentResult() { Content = Resource.FinishInviteJoinEmailMessage };
|
||||
}
|
||||
default:
|
||||
throw new Exception(Resource.ErrorNotCorrectEmail);
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return new ContentResult() { Content = Resource.ErrorNotCorrectEmail };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ContentResult() { Content = e.Message.HtmlEncode() };
|
||||
}
|
||||
}
|
||||
|
||||
[Read("customschemas")]
|
||||
public List<SchemaModel> PeopleSchemas()
|
||||
{
|
||||
@ -309,6 +480,50 @@ namespace ASC.Api.Settings
|
||||
return new QuotaWrapper(Tenant, CoreBaseSettings, CoreConfiguration, TenantExtra, TenantStatisticsProvider, AuthContext, SettingsManager, WebItemManager);
|
||||
}
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
[Read("authproviders")]
|
||||
public ICollection<AccountInfo> GetAuthProviders(bool inviteView, bool settingsView, string clientCallback, string fromOnly)
|
||||
{
|
||||
ICollection<AccountInfo> infos = new List<AccountInfo>();
|
||||
IEnumerable<LoginProfile> linkedAccounts = new List<LoginProfile>();
|
||||
|
||||
if (AuthContext.IsAuthenticated)
|
||||
{
|
||||
linkedAccounts = AccountLinker.Get("webstudio").GetLinkedProfiles(AuthContext.CurrentAccount.ID.ToString());
|
||||
}
|
||||
|
||||
fromOnly = string.IsNullOrWhiteSpace(fromOnly) ? string.Empty : fromOnly.ToLower();
|
||||
|
||||
foreach (var provider in ProviderManager.AuthProviders.Where(provider => string.IsNullOrEmpty(fromOnly) || fromOnly == provider || (provider == "google" && fromOnly == "openid")))
|
||||
{
|
||||
if (inviteView && provider.ToLower() == "twitter") continue;
|
||||
|
||||
var loginProvider = ProviderManager.GetLoginProvider(provider);
|
||||
if (loginProvider != null && loginProvider.IsEnabled)
|
||||
{
|
||||
|
||||
var url = VirtualPathUtility.ToAbsolute("~/login.ashx") + $"?auth={provider}";
|
||||
var mode = (settingsView || inviteView || (!MobileDetector.IsMobile() && !Request.DesktopApp())
|
||||
? ("&mode=popup&callback=" + clientCallback)
|
||||
: ("&mode=Redirect&returnurl="
|
||||
+ HttpUtility.UrlEncode(new Uri(Request.GetUrlRewriter(),
|
||||
"Auth.aspx"
|
||||
+ (Request.DesktopApp() ? "?desktop=true" : "")
|
||||
).ToString())));
|
||||
|
||||
infos.Add(new AccountInfo
|
||||
{
|
||||
Linked = linkedAccounts.Any(x => x.Provider == provider),
|
||||
Provider = provider,
|
||||
Url = url + mode
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return infos;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Read("cultures")]
|
||||
public IEnumerable<object> GetSupportedCultures()
|
||||
@ -1488,6 +1703,21 @@ namespace ASC.Api.Settings
|
||||
|
||||
return new { Url = hubUrl };
|
||||
}
|
||||
|
||||
|
||||
private readonly int maxCount = 10;
|
||||
private readonly int expirationMinutes = 2;
|
||||
private void CheckCache(string basekey)
|
||||
{
|
||||
var key = ApiContext.HttpContextAccessor.HttpContext.Request.GetUserHostAddress() + basekey;
|
||||
if (MemoryCache.TryGetValue<int>(key, out var count))
|
||||
{
|
||||
if (count > maxCount)
|
||||
throw new Exception(Resource.ErrorRequestLimitExceeded);
|
||||
}
|
||||
|
||||
MemoryCache.Set(key, ++count, TimeSpan.FromMinutes(expirationMinutes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SettingsControllerExtension
|
||||
@ -1537,7 +1767,10 @@ namespace ASC.Api.Settings
|
||||
.AddEmployeeWraper()
|
||||
.AddConsumerFactoryService()
|
||||
.AddSmsProviderManagerService()
|
||||
.AddCustomNamingPeopleService();
|
||||
.AddCustomNamingPeopleService()
|
||||
.AddProviderManagerService()
|
||||
.AddAccountLinker()
|
||||
.AddMobileDetectorService();
|
||||
}
|
||||
}
|
||||
}
|
10
web/ASC.Web.Api/Models/AccountInfo.cs
Normal file
10
web/ASC.Web.Api/Models/AccountInfo.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace ASC.Web.Api.Models
|
||||
{
|
||||
public class AccountInfo
|
||||
{
|
||||
public string Provider { get; set; }
|
||||
public string Url { get; set; }
|
||||
public bool Linked { get; set; }
|
||||
public string Class { get; set; }
|
||||
}
|
||||
}
|
20
web/ASC.Web.Api/Models/MailDomainSettingsModel.cs
Normal file
20
web/ASC.Web.Api/Models/MailDomainSettingsModel.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using ASC.Core.Tenants;
|
||||
|
||||
namespace ASC.Web.Api.Models
|
||||
{
|
||||
public class MailDomainSettingsModel
|
||||
{
|
||||
public TenantTrustedDomainsType Type { get; set; }
|
||||
public List<string> Domains { get; set; }
|
||||
public bool InviteUsersAsVisitors { get; set; }
|
||||
}
|
||||
|
||||
public class AdminMessageSettingsModel
|
||||
{
|
||||
public string Email { get; set; }
|
||||
public string Message { get; set; }
|
||||
public bool TurnOn { get; set; }
|
||||
}
|
||||
}
|
@ -51,6 +51,12 @@ namespace ASC.Api.Settings
|
||||
|
||||
public string NameSchemaId { get; set; }
|
||||
|
||||
public bool? EnabledJoin { get; set; }
|
||||
|
||||
public bool? EnableAdmMess { get; set; }
|
||||
|
||||
public bool? ThirdpartyEnable { get; set; }
|
||||
|
||||
public static SettingsWrapper GetSample()
|
||||
{
|
||||
return new SettingsWrapper
|
||||
|
@ -25,6 +25,8 @@ namespace ASC.Web.Api
|
||||
|
||||
public override void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMemoryCache();
|
||||
|
||||
var diHelper = new DIHelper(services);
|
||||
|
||||
diHelper
|
||||
|
@ -91,3 +91,12 @@ export function getSettings() {
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function sendRegisterRequest(email) {
|
||||
const data = { email };
|
||||
return request({
|
||||
method: "post",
|
||||
url: `/settings/sendjoininvite`,
|
||||
data
|
||||
});
|
||||
}
|
@ -11,6 +11,7 @@ import HeaderUnauth from "./sub-components/header-unauth";
|
||||
|
||||
import { withTranslation } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
class Layout extends React.Component {
|
||||
constructor(props) {
|
||||
@ -286,4 +287,11 @@ LayoutWrapper.propTypes = {
|
||||
language: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default LayoutWrapper;
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(LayoutWrapper);
|
||||
|
||||
|
@ -19,13 +19,13 @@ const Header = styled.header`
|
||||
padding: 0 32px;
|
||||
|
||||
.header-items-wrapper {
|
||||
width: 896px;
|
||||
width: 960px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 411px;
|
||||
width: 475px;
|
||||
}
|
||||
@media (max-width: 375px) {
|
||||
width: 247px;
|
||||
width: 311px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,26 +16,21 @@ const Header = styled.header`
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 56px;
|
||||
|
||||
.header-logo-wrapper {
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.header-module-title {
|
||||
display: block;
|
||||
font-size: 21px;
|
||||
line-height: 0;
|
||||
|
||||
@media ${desktop} {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-logo-min_icon {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
@media (max-width: 620px) {
|
||||
@ -43,14 +38,12 @@ const Header = styled.header`
|
||||
display: ${props => props.module && "block"};
|
||||
}
|
||||
}
|
||||
|
||||
.header-logo-icon {
|
||||
width: 146px;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
padding: 4px 20px 0 6px;
|
||||
cursor: pointer;
|
||||
|
||||
@media (max-width: 620px) {
|
||||
display: ${props => (props.module ? "none" : "block")};
|
||||
padding: 0px 20px 0 6px;
|
||||
|
@ -59,16 +59,14 @@ const RecoverAccess = ({ t }) => {
|
||||
else {
|
||||
setLoading(true);
|
||||
sendRecoverRequest(email, description)
|
||||
.then(
|
||||
res => {
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
toastr.success(res);
|
||||
},
|
||||
message => {
|
||||
toastr.success("Successfully sent")
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
toastr.error(message);
|
||||
}
|
||||
)
|
||||
toastr.error(error)
|
||||
})
|
||||
.finally(onRecoverModalClose)
|
||||
}
|
||||
};
|
||||
|
@ -2,48 +2,70 @@ import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { withRouter } from "react-router";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
TextInput,
|
||||
Text,
|
||||
Heading,
|
||||
Link,
|
||||
toastr,
|
||||
Checkbox,
|
||||
HelpButton
|
||||
HelpButton,
|
||||
PasswordInput,
|
||||
FieldContainer
|
||||
} from "asc-web-components";
|
||||
import PageLayout from "../../components/PageLayout";
|
||||
import { connect } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
import SubModalDialog from "./sub-components/modal-dialog";
|
||||
import ForgotPasswordModalDialog from "./sub-components/forgot-password-modal-dialog";
|
||||
import { login, setIsLoaded } from "../../store/auth/actions";
|
||||
import { sendInstructionsToChangePassword } from "../../api/people";
|
||||
import Register from "./sub-components/register-container";
|
||||
|
||||
const FormContainer = styled.form`
|
||||
margin: 50px auto 0 auto;
|
||||
max-width: 432px;
|
||||
const LoginContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 120px auto 0 auto;
|
||||
max-width: 960px;
|
||||
|
||||
.login-header {
|
||||
min-height: 79px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.login-logo {
|
||||
max-width: 216px;
|
||||
max-height: 35px;
|
||||
@media (max-width: 768px) {
|
||||
padding: 0 16px;
|
||||
max-width: 475px;
|
||||
}
|
||||
@media (max-width: 375px) {
|
||||
margin: 72px auto 0 auto;
|
||||
max-width: 311px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
margin: 8px 0;
|
||||
.greeting-title {
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
text-align: left;
|
||||
}
|
||||
@media (max-width: 375px) {
|
||||
font-size: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-input {
|
||||
margin-bottom: 24px;
|
||||
.auth-form-container {
|
||||
margin: 32px 22.5% 0 22.5%;
|
||||
width: 32.4%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
margin: 32px 0 0 0;
|
||||
width: 100%
|
||||
}
|
||||
@media (max-width: 375px) {
|
||||
margin: 32px 0 0 0;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.login-forgot-wrapper {
|
||||
height: 36px;
|
||||
padding: 14px 0;
|
||||
|
||||
.login-checkbox-wrapper {
|
||||
position: absolute;
|
||||
@ -55,6 +77,7 @@ const FormContainer = styled.form`
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-tooltip {
|
||||
display: inline-flex;
|
||||
|
||||
@ -67,6 +90,7 @@ const FormContainer = styled.form`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-link {
|
||||
float: right;
|
||||
line-height: 16px;
|
||||
@ -80,6 +104,17 @@ const FormContainer = styled.form`
|
||||
.login-button-dialog {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.login-bottom-border {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #ECEEF1;
|
||||
}
|
||||
|
||||
.login-bottom-text {
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class Form extends Component {
|
||||
@ -96,7 +131,9 @@ class Form extends Component {
|
||||
isChecked: false,
|
||||
openDialog: false,
|
||||
email: "",
|
||||
errorText: ""
|
||||
emailError: false,
|
||||
errorText: "",
|
||||
socialButtons: []
|
||||
};
|
||||
}
|
||||
|
||||
@ -113,7 +150,7 @@ class Form extends Component {
|
||||
};
|
||||
|
||||
onChangeEmail = event => {
|
||||
this.setState({ email: event.target.value });
|
||||
this.setState({ email: event.target.value, emailError: false });
|
||||
};
|
||||
|
||||
onChangeCheckbox = () => this.setState({ isChecked: !this.state.isChecked });
|
||||
@ -135,6 +172,10 @@ class Form extends Component {
|
||||
};
|
||||
|
||||
onSendPasswordInstructions = () => {
|
||||
if (!this.state.email.trim()) {
|
||||
this.setState({ emailError: true });
|
||||
}
|
||||
else {
|
||||
this.setState({ isLoading: true });
|
||||
sendInstructionsToChangePassword(this.state.email)
|
||||
.then(
|
||||
@ -142,6 +183,7 @@ class Form extends Component {
|
||||
message => toastr.error(message)
|
||||
)
|
||||
.finally(this.onDialogClose());
|
||||
}
|
||||
};
|
||||
|
||||
onDialogClose = () => {
|
||||
@ -149,7 +191,8 @@ class Form extends Component {
|
||||
openDialog: false,
|
||||
isDisabled: false,
|
||||
isLoading: false,
|
||||
email: ""
|
||||
email: "",
|
||||
emailError: false
|
||||
});
|
||||
};
|
||||
|
||||
@ -203,6 +246,13 @@ class Form extends Component {
|
||||
window.removeEventListener("keyup", this.onKeyPress);
|
||||
}
|
||||
|
||||
settings = {
|
||||
minLength: 6,
|
||||
upperCase: false,
|
||||
digits: false,
|
||||
specSymbols: false
|
||||
}
|
||||
|
||||
render() {
|
||||
const { greetingTitle, match, t } = this.props;
|
||||
|
||||
@ -215,32 +265,40 @@ class Form extends Component {
|
||||
isChecked,
|
||||
openDialog,
|
||||
email,
|
||||
errorText
|
||||
emailError,
|
||||
errorText,
|
||||
socialButtons
|
||||
} = this.state;
|
||||
const { params } = match;
|
||||
|
||||
//console.log("Login render");
|
||||
|
||||
return (
|
||||
<FormContainer>
|
||||
<div className="login-header">
|
||||
<img
|
||||
className="login-logo"
|
||||
src="images/dark_general.png"
|
||||
alt="Logo"
|
||||
/>
|
||||
<Heading className="login-title" color="#116d9d">
|
||||
{greetingTitle}
|
||||
</Heading>
|
||||
</div>
|
||||
<>
|
||||
<LoginContainer>
|
||||
|
||||
<Text fontSize="32px"
|
||||
fontWeight={600}
|
||||
textAlign="center"
|
||||
className="greeting-title">
|
||||
{greetingTitle}
|
||||
</Text>
|
||||
|
||||
<form className="auth-form-container">
|
||||
|
||||
<FieldContainer
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={!identifierValid}
|
||||
errorMessage={t("RequiredFieldMessage")}
|
||||
>
|
||||
<TextInput
|
||||
id="login"
|
||||
name="login"
|
||||
hasError={!identifierValid}
|
||||
value={identifier}
|
||||
placeholder={t("RegistrationEmailWatermark")}
|
||||
size="huge"
|
||||
size="large"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
@ -248,33 +306,39 @@ class Form extends Component {
|
||||
autoComplete="username"
|
||||
onChange={this.onChangeLogin}
|
||||
onKeyDown={this.onKeyPress}
|
||||
className="login-input"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
</FieldContainer>
|
||||
<FieldContainer
|
||||
isVertical={true}
|
||||
labelVisible={false}
|
||||
hasError={!passwordValid}
|
||||
errorMessage={t("RequiredFieldMessage")}
|
||||
>
|
||||
<PasswordInput
|
||||
simpleView={true}
|
||||
passwordSettings={this.settings}
|
||||
id="password"
|
||||
name="password"
|
||||
inputName="password"
|
||||
placeholder={t("Password")}
|
||||
type="password"
|
||||
hasError={!passwordValid}
|
||||
value={password}
|
||||
placeholder={t("Password")}
|
||||
size="huge"
|
||||
inputValue={password}
|
||||
size="large"
|
||||
scale={true}
|
||||
tabIndex={2}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
autoComplete="current-password"
|
||||
onChange={this.onChangePassword}
|
||||
onKeyDown={this.onKeyPress}
|
||||
className="login-input"
|
||||
/>
|
||||
|
||||
</FieldContainer>
|
||||
<div className="login-forgot-wrapper">
|
||||
<div className="login-checkbox-wrapper">
|
||||
<Checkbox
|
||||
className="login-checkbox"
|
||||
isChecked={isChecked}
|
||||
onChange={this.onChangeCheckbox}
|
||||
label={t("Remember")}
|
||||
label={<Text fontSize='13px'>{t("Remember")}</Text>}
|
||||
/>
|
||||
<HelpButton
|
||||
className="login-tooltip"
|
||||
@ -286,35 +350,38 @@ class Form extends Component {
|
||||
</div>
|
||||
|
||||
<Link
|
||||
fontSize='12px'
|
||||
fontSize='13px'
|
||||
color="#316DAA"
|
||||
className="login-link"
|
||||
type="page"
|
||||
isHovered={true}
|
||||
isHovered={false}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
{t("ForgotPassword")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{openDialog ? (
|
||||
<SubModalDialog
|
||||
{openDialog &&
|
||||
<ForgotPasswordModalDialog
|
||||
openDialog={openDialog}
|
||||
isLoading={isLoading}
|
||||
email={email}
|
||||
emailError={emailError}
|
||||
onChangeEmail={this.onChangeEmail}
|
||||
onSendPasswordInstructions={this.onSendPasswordInstructions}
|
||||
onDialogClose={this.onDialogClose}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
}
|
||||
|
||||
<Button
|
||||
id="button"
|
||||
className="login-button"
|
||||
primary
|
||||
size="big"
|
||||
size="large"
|
||||
scale={true}
|
||||
label={isLoading ? t("LoadingProcessing") : t("LoginButton")}
|
||||
tabIndex={3}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={this.onSubmit}
|
||||
@ -328,7 +395,19 @@ class Form extends Component {
|
||||
<Text fontSize='14px' color="#c30">
|
||||
{errorText}
|
||||
</Text>
|
||||
</FormContainer>
|
||||
|
||||
{socialButtons.length ? (<Box displayProp="flex" alignItems="center">
|
||||
<div className="login-bottom-border"></div>
|
||||
<Text className="login-bottom-text" color="#A3A9AE">{t("Or")}</Text>
|
||||
<div className="login-bottom-border"></div>
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
</form>
|
||||
|
||||
</LoginContainer>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -341,7 +420,8 @@ Form.propTypes = {
|
||||
greetingTitle: PropTypes.string.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
i18n: PropTypes.object.isRequired,
|
||||
language: PropTypes.string.isRequired
|
||||
language: PropTypes.string.isRequired,
|
||||
socialButtons: PropTypes.array
|
||||
};
|
||||
|
||||
Form.defaultProps = {
|
||||
@ -351,29 +431,38 @@ Form.defaultProps = {
|
||||
};
|
||||
|
||||
const FormWrapper = withTranslation()(Form);
|
||||
const RegisterWrapper = withTranslation()(Register);
|
||||
|
||||
const LoginForm = props => {
|
||||
const { language, isLoaded } = props;
|
||||
const { language, isLoaded, enabledJoin } = props;
|
||||
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoaded && <PageLayout sectionBodyContent={<FormWrapper i18n={i18n} {...props} />} />}
|
||||
{isLoaded && <>
|
||||
<PageLayout sectionBodyContent={<FormWrapper i18n={i18n} {...props} />} />
|
||||
{enabledJoin &&
|
||||
<RegisterWrapper i18n={i18n} {...props} />
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
LoginForm.propTypes = {
|
||||
language: PropTypes.string.isRequired,
|
||||
isLoaded: PropTypes.bool
|
||||
isLoaded: PropTypes.bool,
|
||||
enabledJoin: PropTypes.bool
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isLoaded: state.auth.isLoaded,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
greetingTitle: state.auth.settings.greetingSettings
|
||||
greetingTitle: state.auth.settings.greetingSettings,
|
||||
enabledJoin: state.auth.settings.enabledJoin
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2,17 +2,30 @@
|
||||
"LoadingProcessing": "Loading...",
|
||||
"LoginButton": "Sign In",
|
||||
"Password": "Password",
|
||||
"RegistrationEmailWatermark": "Your registration email",
|
||||
"RegistrationEmailWatermark": "E-mail",
|
||||
"MessageEmailConfirmed": "Your email was activated successfully.",
|
||||
"MessageAuthorize": "Please authorize yourself.",
|
||||
"ForgotPassword": "Forgot your password?",
|
||||
|
||||
"PasswordRecoveryTitle": "Password recovery",
|
||||
"PasswordRecoveryPlaceholder": "Your registration e-mail",
|
||||
"MessageSendPasswordRecoveryInstructionsOnEmail": "Please enter the email you used while registering on the portal. The password recovery instructions will be send to that email address.",
|
||||
|
||||
"RegisterTitle": "Registration request",
|
||||
"RegisterTextBodyBeforeDomainsList": "Registration is available to users with an email account at",
|
||||
"RegisterTextBodyAfterDomainsList": "To register, enter your email and click on Send request. An email message with a link to activate your account will be sent to the specified email. Please enter the email address where the invitation will be sent:",
|
||||
"RegisterPlaceholder": "Your registration e-mail",
|
||||
"RegisterSendButton": "Send request",
|
||||
"RegisterProcessSending": "Sending...",
|
||||
|
||||
"SendButton": "Send",
|
||||
"RequiredFieldMessage": "Required field",
|
||||
"CancelButton": "Cancel",
|
||||
"Remember": "Remember",
|
||||
"Remember": "Remember me",
|
||||
"RememberHelper": "The default session lifetime is 20 minutes. Check this option to set it to 1 year. To set your own value, go to the settings.",
|
||||
"CookieSettingsTitle": "Session Lifetime",
|
||||
"Authorization": "Authorization",
|
||||
"Or": "OR",
|
||||
"Register": "Register",
|
||||
"OrganizationName": "ONLYOFFICE"
|
||||
}
|
@ -6,13 +6,26 @@
|
||||
"MessageEmailConfirmed": "Ваш email успешно активирован.",
|
||||
"MessageAuthorize": "Пожалуйста авторизуйтесь.",
|
||||
"ForgotPassword": "Забыли пароль?",
|
||||
|
||||
"PasswordRecoveryTitle": "Восстановление пароля",
|
||||
"PasswordRecoveryPlaceholder": "Ваш регистрационный email",
|
||||
"MessageSendPasswordRecoveryInstructionsOnEmail": "Пожалуйста, введите адрес электронной почты, указанный при регистрации на портале. Инструкции для восстановления пароля будут отправлены на этот адрес электронной почты.",
|
||||
|
||||
"RegisterTitle": "Запрос на регистрацию",
|
||||
"RegisterTextBodyBeforeDomainsList": "Регистрация доступна для пользователей, которые имеют почтовый ящик на",
|
||||
"RegisterTextBodyAfterDomainsList": "Чтобы зарегистрироваться, введите свой email и нажмите кнопку Отправить запрос. Сообщение со ссылкой для активации вашей учётной записи будет отправлено на указанный адрес.",
|
||||
"RegisterPlaceholder": "Ваш регистрационный email",
|
||||
"RegisterSendButton": "Отправить запрос",
|
||||
"RegisterProcessSending": "Отправка...",
|
||||
|
||||
"SendButton": "Отправить",
|
||||
"RequiredFieldMessage": "Обязательное поле",
|
||||
"CancelButton": "Отмена",
|
||||
"Remember": "Запомнить",
|
||||
"RememberHelper": "Время существования сессии по умолчанию составляет 20 минут. Отметьте эту опцию, чтобы установить значение 1 год. Чтобы задать собственное значение, перейдите в настройки.",
|
||||
"CookieSettingsTitle": "Время жизни сессии",
|
||||
"Authorization": "Авторизация",
|
||||
"Or": "ИЛИ",
|
||||
"Register": "Регистрация",
|
||||
"OrganizationName": "ONLYOFFICE"
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Button, TextInput, Text, ModalDialog, FieldContainer } from "asc-web-components";
|
||||
import ModalDialogContainer from "./modal-dialog-container";
|
||||
|
||||
class ForgotPasswordModalDialog extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
openDialog,
|
||||
isLoading,
|
||||
email,
|
||||
emailError,
|
||||
onChangeEmail,
|
||||
onSendPasswordInstructions,
|
||||
onDialogClose,
|
||||
t
|
||||
} = this.props;
|
||||
return (
|
||||
<ModalDialogContainer>
|
||||
<ModalDialog
|
||||
visible={openDialog}
|
||||
bodyPadding="16px 0 0 0"
|
||||
headerContent={
|
||||
<Text isBold={true} fontSize='21px'>
|
||||
{t("PasswordRecoveryTitle")}
|
||||
</Text>
|
||||
}
|
||||
bodyContent={[
|
||||
<Text
|
||||
key="text-body"
|
||||
className="text-body"
|
||||
isBold={false}
|
||||
fontSize='13px'
|
||||
>
|
||||
{t("MessageSendPasswordRecoveryInstructionsOnEmail")}
|
||||
</Text>,
|
||||
<FieldContainer
|
||||
key="e-mail"
|
||||
isVertical={true}
|
||||
hasError={emailError}
|
||||
labelVisible={false}
|
||||
errorMessage={t("RequiredFieldMessage")}>
|
||||
<TextInput
|
||||
hasError={emailError}
|
||||
placeholder={t("PasswordRecoveryPlaceholder")}
|
||||
isAutoFocussed={true}
|
||||
id="e-mail"
|
||||
name="e-mail"
|
||||
type="text"
|
||||
size="base"
|
||||
scale={true}
|
||||
tabIndex={2}
|
||||
isDisabled={isLoading}
|
||||
value={email}
|
||||
onChange={onChangeEmail}
|
||||
/>
|
||||
</FieldContainer>
|
||||
]}
|
||||
footerContent={[
|
||||
<Button
|
||||
className="modal-dialog-button"
|
||||
key="SendBtn"
|
||||
label={isLoading ? t("LoadingProcessing") : t("SendButton")}
|
||||
size="big"
|
||||
scale={false}
|
||||
primary={true}
|
||||
onClick={onSendPasswordInstructions}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isLoading}
|
||||
tabIndex={2}
|
||||
/>
|
||||
]}
|
||||
onClose={onDialogClose}
|
||||
/>
|
||||
</ModalDialogContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ForgotPasswordModalDialog.propTypes = {
|
||||
openDialog: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
emailError: PropTypes.bool.isRequired,
|
||||
onChangeEmail: PropTypes.func.isRequired,
|
||||
onSendPasswordInstructions: PropTypes.func.isRequired,
|
||||
onDialogClose: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ForgotPasswordModalDialog;
|
@ -0,0 +1,19 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const ModalDialogContainer = styled.div`
|
||||
.modal-dialog-aside-footer {
|
||||
@media(max-width: 1024px) {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
.modal-dialog-button {
|
||||
@media(max-width: 1024px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.field-body {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default ModalDialogContainer;
|
@ -1,86 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Button, TextInput, Text, ModalDialog } from "asc-web-components";
|
||||
|
||||
class SubModalDialog extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
openDialog,
|
||||
isLoading,
|
||||
email,
|
||||
onChangeEmail,
|
||||
onSendPasswordInstructions,
|
||||
onDialogClose,
|
||||
t
|
||||
} = this.props;
|
||||
return (
|
||||
<ModalDialog
|
||||
visible={openDialog}
|
||||
headerContent={
|
||||
<Text isBold={false} fontSize='21px'>
|
||||
{t("PasswordRecoveryTitle")}
|
||||
</Text>
|
||||
}
|
||||
bodyContent={[
|
||||
<Text
|
||||
key="text-body"
|
||||
className="text-body"
|
||||
isBold={false}
|
||||
fontSize='13px'
|
||||
>
|
||||
{t("MessageSendPasswordRecoveryInstructionsOnEmail")}
|
||||
</Text>,
|
||||
<TextInput
|
||||
key="e-mail"
|
||||
id="e-mail"
|
||||
name="e-mail"
|
||||
type="text"
|
||||
size="base"
|
||||
scale={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
value={email}
|
||||
onChange={onChangeEmail}
|
||||
/>
|
||||
]}
|
||||
footerContent={[
|
||||
<Button
|
||||
className="login-button-dialog"
|
||||
key="SendBtn"
|
||||
label={isLoading ? t("LoadingProcessing") : t("SendButton")}
|
||||
size="big"
|
||||
scale={false}
|
||||
primary={true}
|
||||
onClick={onSendPasswordInstructions}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isLoading}
|
||||
tabIndex={2}
|
||||
/>,
|
||||
<Button
|
||||
key="CancelBtn"
|
||||
label={t("CancelButton")}
|
||||
size="big"
|
||||
scale={false}
|
||||
primary={false}
|
||||
onClick={onDialogClose}
|
||||
isDisabled={isLoading}
|
||||
tabIndex={3}
|
||||
/>
|
||||
]}
|
||||
onClose={onDialogClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SubModalDialog.propTypes = {
|
||||
openDialog: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
onChangeEmail: PropTypes.func.isRequired,
|
||||
onSendPasswordInstructions: PropTypes.func.isRequired,
|
||||
onDialogClose: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SubModalDialog;
|
@ -0,0 +1,86 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Text, toastr } from "asc-web-components";
|
||||
import RegisterModalDialog from "./register-modal-dialog";
|
||||
import styled from "styled-components";
|
||||
import PropTypes from 'prop-types';
|
||||
import { sendRegisterRequest } from "../../../api/settings/index";
|
||||
|
||||
const StyledRegister = styled(Box)`
|
||||
position: absolute;
|
||||
z-index: 184;
|
||||
width: 100%;
|
||||
height: 66px;
|
||||
padding: 1.5em;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: #F8F9F9;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const Register = ({ t }) => {
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const [emailErr, setEmailErr] = useState(false);
|
||||
|
||||
const onRegisterClick = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
const onRegisterModalClose = () => {
|
||||
setVisible(false);
|
||||
setEmail("");
|
||||
setEmailErr(false);
|
||||
};
|
||||
const onChangeEmail = (e) => {
|
||||
setEmail(e.currentTarget.value);
|
||||
setEmailErr(false);
|
||||
};
|
||||
const onSendRegisterRequest = () => {
|
||||
if (!email.trim()) {
|
||||
setEmailErr(true);
|
||||
}
|
||||
else {
|
||||
setLoading(true);
|
||||
sendRegisterRequest(email)
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
toastr.success("Successfully sent")
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
toastr.error(error)
|
||||
})
|
||||
.finally(onRegisterModalClose)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledRegister onClick={onRegisterClick}>
|
||||
<Text color="#316DAA" textAlign="center">
|
||||
{t("Register")}
|
||||
</Text>
|
||||
</StyledRegister>
|
||||
|
||||
{visible &&
|
||||
<RegisterModalDialog
|
||||
visible={visible}
|
||||
loading={loading}
|
||||
email={email}
|
||||
emailErr={emailErr}
|
||||
t={t}
|
||||
onChangeEmail={onChangeEmail}
|
||||
onRegisterModalClose={onRegisterModalClose}
|
||||
onSendRegisterRequest={onSendRegisterRequest}
|
||||
/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Register.propTypes = {
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Register;
|
@ -0,0 +1,98 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Button, TextInput, Text, ModalDialog, FieldContainer } from "asc-web-components";
|
||||
import ModalDialogContainer from "./modal-dialog-container";
|
||||
|
||||
const domains = ['mail.ru', 'gmail.com', 'yandex.ru'];
|
||||
const domainList = domains
|
||||
.map((domain, i) =>
|
||||
<span key={i}>
|
||||
<b>
|
||||
{domain}{i === domains.length - 1 ? "." : ", "}
|
||||
</b>
|
||||
</span>
|
||||
);
|
||||
|
||||
const RegisterModalDialog = ({
|
||||
visible,
|
||||
loading,
|
||||
email,
|
||||
emailErr,
|
||||
t,
|
||||
onChangeEmail,
|
||||
onRegisterModalClose,
|
||||
onSendRegisterRequest
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<ModalDialogContainer>
|
||||
<ModalDialog
|
||||
visible={visible}
|
||||
bodyPadding="16px 0 0 0"
|
||||
headerContent={
|
||||
<Text isBold={true} fontSize='21px'>
|
||||
{t("RegisterTitle")}
|
||||
</Text>
|
||||
}
|
||||
bodyContent={[
|
||||
<Text
|
||||
key="text-body"
|
||||
isBold={false}
|
||||
fontSize='13px'
|
||||
>
|
||||
{t("RegisterTextBodyBeforeDomainsList")} {domainList} {t("RegisterTextBodyAfterDomainsList")}
|
||||
</Text>,
|
||||
<FieldContainer
|
||||
key="e-mail"
|
||||
isVertical={true}
|
||||
hasError={emailErr}
|
||||
labelVisible={false}
|
||||
errorMessage={t("RequiredFieldMessage")}>
|
||||
<TextInput
|
||||
hasError={emailErr}
|
||||
placeholder={t("RegisterPlaceholder")}
|
||||
isAutoFocussed={true}
|
||||
id="e-mail"
|
||||
name="e-mail"
|
||||
type="text"
|
||||
size="base"
|
||||
scale={true}
|
||||
tabIndex={3}
|
||||
isDisabled={loading}
|
||||
value={email}
|
||||
onChange={onChangeEmail}
|
||||
/>
|
||||
</FieldContainer>
|
||||
]}
|
||||
footerContent={[
|
||||
<Button
|
||||
className="modal-dialog-button"
|
||||
key="SendBtn"
|
||||
label={loading ? t("RegisterProcessSending") : t("RegisterSendButton")}
|
||||
size="big"
|
||||
scale={false}
|
||||
primary={true}
|
||||
onClick={onSendRegisterRequest}
|
||||
isLoading={loading}
|
||||
isDisabled={loading}
|
||||
tabIndex={3}
|
||||
/>
|
||||
]}
|
||||
onClose={onRegisterModalClose}
|
||||
/>
|
||||
</ModalDialogContainer>
|
||||
);
|
||||
};
|
||||
|
||||
RegisterModalDialog.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
email: PropTypes.string,
|
||||
emailErr: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
onChangeEmail: PropTypes.func.isRequired,
|
||||
onSendRegisterRequest: PropTypes.func.isRequired,
|
||||
onRegisterModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default RegisterModalDialog;
|
@ -139,7 +139,7 @@ Checkbox.propTypes = {
|
||||
id: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
|
||||
isChecked: PropTypes.bool,
|
||||
isIndeterminate: PropTypes.bool,
|
||||
|
@ -547,7 +547,7 @@ PasswordInput.propTypes = {
|
||||
autoComplete: PropTypes.string,
|
||||
inputType: PropTypes.oneOf(["text", "password"]),
|
||||
inputName: PropTypes.string,
|
||||
emailInputName: PropTypes.string.isRequired,
|
||||
emailInputName: PropTypes.string,
|
||||
inputValue: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
inputWidth: PropTypes.string,
|
||||
|
@ -2,7 +2,7 @@ import {css} from 'styled-components';
|
||||
|
||||
const commonTextStyles = css`
|
||||
font-family: 'Open Sans', sans-serif, Arial;
|
||||
text-align: left;
|
||||
text-align: ${props => props.textAlign};
|
||||
color: ${props => props.colorProp};
|
||||
${props => props.truncate && css`
|
||||
white-space: nowrap;
|
||||
|
@ -22,13 +22,14 @@ const StyledText = styled.p`
|
||||
${commonTextStyles};
|
||||
`;
|
||||
|
||||
const Text = ({ title, tag, as, fontSize, fontWeight, color, ...rest }) => {
|
||||
const Text = ({ title, tag, as, fontSize, fontWeight, color, textAlign, ...rest }) => {
|
||||
//console.log("Text render", rest)
|
||||
return (
|
||||
<StyledText
|
||||
fontSizeProp={fontSize}
|
||||
fontWeightProp={fontWeight}
|
||||
colorProp={color}
|
||||
textAlign={textAlign}
|
||||
as={!as && tag ? tag : as}
|
||||
title={title}
|
||||
{...rest}
|
||||
@ -41,6 +42,7 @@ Text.propTypes = {
|
||||
tag: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
textAlign: PropTypes.string,
|
||||
fontSize: PropTypes.string,
|
||||
fontWeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
backgroundColor: PropTypes.string,
|
||||
@ -54,6 +56,7 @@ Text.propTypes = {
|
||||
Text.defaultProps = {
|
||||
title: null,
|
||||
color: '#333333',
|
||||
textAlign: 'left',
|
||||
fontSize: '13px',
|
||||
truncate: false,
|
||||
isBold: false,
|
||||
|
@ -24,6 +24,7 @@ import { Textarea } from "asc-web-components";
|
||||
| `id` | `string` | - | - | - | Used as HTML `id` property |
|
||||
| `isDisabled` | `bool` | - | - | `false` | Indicates that the field cannot be used |
|
||||
| `isReadOnly` | `bool` | - | - | `false` | Indicates that the field is displaying read-only content |
|
||||
| `hasError` | `bool` | - | - | - | Indicates the input field has an error |
|
||||
| `name` | `string` | - | - | - | Used as HTML `name` property |
|
||||
| `onChange` | `func` | - | - | - | Allow you to handle changing events of component |
|
||||
| `placeholder` | `string` | - | - | - | Placeholder for Textarea |
|
||||
|
@ -6,23 +6,23 @@ import commonInputStyle from '../text-input/common-input-styles';
|
||||
import TextareaAutosize from 'react-autosize-textarea';
|
||||
|
||||
// eslint-disable-next-line react/prop-types, no-unused-vars
|
||||
const ClearScrollbar = ({ isDisabled, ...props }) => <Scrollbar {...props} />
|
||||
const ClearScrollbar = ({ isDisabled, heightScale, hasError, ...props }) => <Scrollbar {...props} />
|
||||
const StyledScrollbar = styled(ClearScrollbar)`
|
||||
${commonInputStyle};
|
||||
:focus-within {
|
||||
border-color: #2DA7DB;
|
||||
border-color: ${props => props.hasError ? '#c30' : '#2DA7DB'};
|
||||
}
|
||||
:focus{
|
||||
outline: none;
|
||||
}
|
||||
width: 100% !important;
|
||||
height: 91px !important;
|
||||
height: ${props => props.heightScale ? '67vh' : '91px'} !important;
|
||||
background-color: ${props => props.isDisabled && '#F8F9F9'};
|
||||
|
||||
`;
|
||||
|
||||
// eslint-disable-next-line react/prop-types, no-unused-vars
|
||||
const ClearTextareaAutosize = ({ isDisabled, ...props }) => <TextareaAutosize {...props} />
|
||||
const ClearTextareaAutosize = ({ isDisabled, heightScale, hasError, ...props }) => <TextareaAutosize {...props} />
|
||||
const StyledTextarea = styled(ClearTextareaAutosize)`
|
||||
${commonInputStyle};
|
||||
width: 100%;
|
||||
@ -44,6 +44,29 @@ const StyledTextarea = styled(ClearTextareaAutosize)`
|
||||
outline: none;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: ${props => props.isDisabled ? '#D0D5DA' : '#D0D5DA'};
|
||||
font-family: 'Open Sans',sans-serif;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
:-moz-placeholder {
|
||||
color: ${props => props.isDisabled ? '#D0D5DA' : '#D0D5DA'};
|
||||
font-family: 'Open Sans',sans-serif;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: ${props => props.isDisabled ? '#D0D5DA' : '#D0D5DA'};
|
||||
font-family: 'Open Sans',sans-serif;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: ${props => props.isDisabled ? '#D0D5DA' : '#D0D5DA'};
|
||||
font-family: 'Open Sans',sans-serif;
|
||||
user-select: none;
|
||||
}
|
||||
`;
|
||||
|
||||
class Textarea extends React.PureComponent {
|
||||
@ -54,6 +77,8 @@ class Textarea extends React.PureComponent {
|
||||
id,
|
||||
isDisabled,
|
||||
isReadOnly,
|
||||
hasError,
|
||||
heightScale,
|
||||
maxLength,
|
||||
name,
|
||||
onChange,
|
||||
@ -68,6 +93,8 @@ class Textarea extends React.PureComponent {
|
||||
style={style}
|
||||
stype='preMediumBlack'
|
||||
isDisabled={isDisabled}
|
||||
hasError={hasError}
|
||||
heightScale={heightScale}
|
||||
>
|
||||
<StyledTextarea
|
||||
id={id}
|
||||
@ -91,6 +118,8 @@ Textarea.propTypes = {
|
||||
id: PropTypes.string,
|
||||
isDisabled: PropTypes.bool,
|
||||
isReadOnly: PropTypes.bool,
|
||||
hasError: PropTypes.bool,
|
||||
heightScale: PropTypes.bool,
|
||||
maxLength: PropTypes.number,
|
||||
name: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
@ -104,6 +133,8 @@ Textarea.defaultProps = {
|
||||
className: '',
|
||||
isDisabled: false,
|
||||
isReadOnly: false,
|
||||
hasError: false,
|
||||
heightScale: false,
|
||||
placeholder: '',
|
||||
tabIndex: -1,
|
||||
value: '',
|
||||
|
@ -28,6 +28,7 @@ storiesOf('Components|Input', module)
|
||||
placeholder={text('placeholder', 'Add comment')}
|
||||
isDisabled={boolean('isDisabled', false)}
|
||||
isReadOnly={boolean('isReadOnly', false)}
|
||||
hasError={boolean('hasError', false)}
|
||||
maxLength={number('maxLength', 1000)}
|
||||
id={text('id', '')}
|
||||
name={text('name', '')}
|
||||
|
@ -66,8 +66,9 @@ namespace ASC.Web.Core.Jabber
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,10 @@
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ASC.Common;
|
||||
using ASC.Common.Caching;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
@ -79,4 +82,13 @@ namespace ASC.Web.Core.Mobile
|
||||
return result.GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MobileDetectorExtension
|
||||
{
|
||||
public static DIHelper AddMobileDetectorService(this DIHelper services)
|
||||
{
|
||||
services.TryAddScoped<MobileDetector>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
@ -150,9 +150,10 @@ namespace ASC.Web.Core.Sms
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
IOptionsMonitor<ILog> options,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
|
||||
{
|
||||
Log = options.CurrentValue;
|
||||
}
|
||||
@ -211,9 +212,10 @@ namespace ASC.Web.Core.Sms
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
IOptionsMonitor<ILog> options,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, options, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
|
||||
@ -347,9 +349,10 @@ namespace ASC.Web.Core.Sms
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
IOptionsMonitor<ILog> options,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, options, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, name, order, props, additional)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -366,9 +369,10 @@ namespace ASC.Web.Core.Sms
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
IOptionsMonitor<ILog> options,
|
||||
string name, int order, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, options, name, order, null, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, name, order, null, additional)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -437,9 +441,10 @@ namespace ASC.Web.Core.Sms
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
IOptionsMonitor<ILog> options,
|
||||
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, options, name, order, props, additional)
|
||||
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, name, order, props, additional)
|
||||
{
|
||||
VoipDao = voipDao;
|
||||
}
|
||||
@ -486,9 +491,10 @@ namespace ASC.Web.Core.Sms
|
||||
CoreSettings coreSettings,
|
||||
IConfiguration configuration,
|
||||
ICacheNotify<ConsumerCacheItem> cache,
|
||||
ConsumerFactory consumerFactory,
|
||||
IOptionsMonitor<ILog> options,
|
||||
string name, int order, Dictionary<string, string> additional = null)
|
||||
: base(voipDao, tenantManager, coreBaseSettings, coreSettings, configuration, cache, options, name, order, null, additional)
|
||||
: base(voipDao, tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, name, order, null, additional)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
47
web/ASC.Web.Core/StudioAdminMessageSettings.cs
Normal file
47
web/ASC.Web.Core/StudioAdminMessageSettings.cs
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
*
|
||||
* (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.Core.Common.Settings;
|
||||
|
||||
namespace ASC.Web.Studio.Core
|
||||
{
|
||||
public class StudioAdminMessageSettings : ISettings
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
|
||||
public Guid ID
|
||||
{
|
||||
get { return new Guid("{28902650-58A9-11E1-B6A9-0F194924019B}"); }
|
||||
}
|
||||
|
||||
public ISettings GetDefault(IServiceProvider serviceProvider)
|
||||
{
|
||||
return new StudioAdminMessageSettings { Enable = false };
|
||||
}
|
||||
}
|
||||
}
|
47
web/ASC.Web.Core/StudioTrustedDomainSettings.cs
Normal file
47
web/ASC.Web.Core/StudioTrustedDomainSettings.cs
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
*
|
||||
* (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.Core.Common.Settings;
|
||||
|
||||
namespace ASC.Web.Studio.Core
|
||||
{
|
||||
public class StudioTrustedDomainSettings : ISettings
|
||||
{
|
||||
public bool InviteUsersAsVisitors { get; set; }
|
||||
|
||||
public Guid ID
|
||||
{
|
||||
get { return new Guid("{00A2DB01-BAE3-48aa-BE32-CE768D7C874E}"); }
|
||||
}
|
||||
|
||||
public ISettings GetDefault(IServiceProvider serviceProvider)
|
||||
{
|
||||
return new StudioTrustedDomainSettings { InviteUsersAsVisitors = false };
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using ASC.Common.DependencyInjection;
|
||||
using ASC.Data.Storage;
|
||||
using ASC.Data.Storage.Configuration;
|
||||
using ASC.Data.Storage.DiscStorage;
|
||||
using ASC.FederatedLogin;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@ -32,7 +33,10 @@ namespace ASC.Web.Studio
|
||||
diHelper
|
||||
.AddStorage()
|
||||
.AddPathUtilsService()
|
||||
.AddStorageHandlerService();
|
||||
.AddStorageHandlerService()
|
||||
.AddLoginHandlerService();
|
||||
|
||||
services.AddMemoryCache();
|
||||
|
||||
base.ConfigureServices(services);
|
||||
services.AddAutofac(Configuration, HostEnvironment.ContentRootPath);
|
||||
@ -59,6 +63,13 @@ namespace ASC.Web.Studio
|
||||
{
|
||||
endpoints.InitializeHttpHandlers();
|
||||
});
|
||||
|
||||
app.MapWhen(
|
||||
context => context.Request.Path.ToString().EndsWith("login.ashx"),
|
||||
appBranch =>
|
||||
{
|
||||
appBranch.UseLoginHandler();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user