DocSpace-client/web/ASC.Web.Api/Controllers/AuthenticationController.cs

660 lines
27 KiB
C#

using System;
using System.Globalization;
using System.Linq;
using System.Security.Authentication;
using System.Threading;
using ASC.Api.Core;
using ASC.Api.Utils;
using ASC.Common;
using ASC.Common.Caching;
using ASC.Common.Utils;
using ASC.Core;
using ASC.Core.Common.Security;
using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
using ASC.Core.Users;
using ASC.FederatedLogin;
using ASC.FederatedLogin.LoginProviders;
using ASC.FederatedLogin.Profile;
using ASC.MessagingSystem;
using ASC.Security.Cryptography;
using ASC.Web.Api.Core;
using ASC.Web.Api.Models;
using ASC.Web.Api.Routing;
using ASC.Web.Core;
using ASC.Web.Core.PublicResources;
using ASC.Web.Core.Sms;
using ASC.Web.Core.Users;
using ASC.Web.Studio.Core;
using ASC.Web.Studio.Core.Notify;
using ASC.Web.Studio.Core.SMS;
using ASC.Web.Studio.Core.TFA;
using ASC.Web.Studio.Utility;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using static ASC.Security.Cryptography.EmailValidationKeyProvider;
namespace ASC.Web.Api.Controllers
{
[Scope]
[DefaultRoute]
[ApiController]
[AllowAnonymous]
public class AuthenticationController : ControllerBase
{
private UserManager UserManager { get; }
private TenantManager TenantManager { get; }
private SecurityContext SecurityContext { get; }
private TenantCookieSettingsHelper TenantCookieSettingsHelper { get; }
private CookiesManager CookiesManager { get; }
private PasswordHasher PasswordHasher { get; }
private EmailValidationKeyModelHelper EmailValidationKeyModelHelper { get; }
private ICache Cache { get; }
private SetupInfo SetupInfo { get; }
private MessageService MessageService { get; }
private ProviderManager ProviderManager { get; }
private IOptionsSnapshot<AccountLinker> AccountLinker { get; }
private CoreBaseSettings CoreBaseSettings { get; }
private PersonalSettingsHelper PersonalSettingsHelper { get; }
private StudioNotifyService StudioNotifyService { get; }
private UserHelpTourHelper UserHelpTourHelper { get; }
private Signature Signature { get; }
private InstanceCrypto InstanceCrypto { get; }
private DisplayUserSettingsHelper DisplayUserSettingsHelper { get; }
private MessageTarget MessageTarget { get; }
private StudioSmsNotificationSettingsHelper StudioSmsNotificationSettingsHelper { get; }
private SettingsManager SettingsManager { get; }
private SmsManager SmsManager { get; }
private TfaManager TfaManager { get; }
private TimeZoneConverter TimeZoneConverter { get; }
private SmsKeyStorage SmsKeyStorage { get; }
private CommonLinkUtility CommonLinkUtility { get; }
private ApiContext ApiContext { get; }
private AuthContext AuthContext { get; }
private UserManagerWrapper UserManagerWrapper { get; }
public AuthenticationController(
UserManager userManager,
TenantManager tenantManager,
SecurityContext securityContext,
TenantCookieSettingsHelper tenantCookieSettingsHelper,
CookiesManager cookiesManager,
PasswordHasher passwordHasher,
EmailValidationKeyModelHelper emailValidationKeyModelHelper,
ICache cache,
SetupInfo setupInfo,
MessageService messageService,
ProviderManager providerManager,
IOptionsSnapshot<AccountLinker> accountLinker,
CoreBaseSettings coreBaseSettings,
PersonalSettingsHelper personalSettingsHelper,
StudioNotifyService studioNotifyService,
UserManagerWrapper userManagerWrapper,
UserHelpTourHelper userHelpTourHelper,
Signature signature,
InstanceCrypto instanceCrypto,
DisplayUserSettingsHelper displayUserSettingsHelper,
MessageTarget messageTarget,
StudioSmsNotificationSettingsHelper studioSmsNotificationSettingsHelper,
SettingsManager settingsManager,
SmsManager smsManager,
TfaManager tfaManager,
TimeZoneConverter timeZoneConverter,
SmsKeyStorage smsKeyStorage,
CommonLinkUtility commonLinkUtility,
ApiContext apiContext,
AuthContext authContext)
{
UserManager = userManager;
TenantManager = tenantManager;
SecurityContext = securityContext;
TenantCookieSettingsHelper = tenantCookieSettingsHelper;
CookiesManager = cookiesManager;
PasswordHasher = passwordHasher;
EmailValidationKeyModelHelper = emailValidationKeyModelHelper;
Cache = cache;
SetupInfo = setupInfo;
MessageService = messageService;
ProviderManager = providerManager;
AccountLinker = accountLinker;
CoreBaseSettings = coreBaseSettings;
PersonalSettingsHelper = personalSettingsHelper;
StudioNotifyService = studioNotifyService;
UserHelpTourHelper = userHelpTourHelper;
Signature = signature;
InstanceCrypto = instanceCrypto;
DisplayUserSettingsHelper = displayUserSettingsHelper;
MessageTarget = messageTarget;
StudioSmsNotificationSettingsHelper = studioSmsNotificationSettingsHelper;
SettingsManager = settingsManager;
SmsManager = smsManager;
TfaManager = tfaManager;
TimeZoneConverter = timeZoneConverter;
SmsKeyStorage = smsKeyStorage;
CommonLinkUtility = commonLinkUtility;
ApiContext = apiContext;
AuthContext = authContext;
UserManagerWrapper = userManagerWrapper;
}
[Read]
public bool GetIsAuthentificated()
{
return SecurityContext.IsAuthenticated;
}
[Create("{code}", false, order: int.MaxValue)]
public AuthenticationTokenData AuthenticateMeFromBodyWithCode([FromBody] AuthModel auth)
{
return AuthenticateMeWithCode(auth);
}
[Create("{code}", false, order: int.MaxValue)]
[Consumes("application/x-www-form-urlencoded")]
public AuthenticationTokenData AuthenticateMeFromFormWithCode([FromForm] AuthModel auth)
{
return AuthenticateMeWithCode(auth);
}
[Create(false)]
public AuthenticationTokenData AuthenticateMeFromBody([FromBody] AuthModel auth)
{
return AuthenticateMe(auth);
}
[Create(false)]
[Consumes("application/x-www-form-urlencoded")]
public AuthenticationTokenData AuthenticateMeFromForm([FromForm] AuthModel auth)
{
return AuthenticateMe(auth);
}
[Create("logout")]
[Read("logout")]// temp fix
public void Logout()
{
if (SecurityContext.IsAuthenticated)
CookiesManager.ResetUserCookie(SecurityContext.CurrentAccount.ID);
CookiesManager.ClearCookies(CookiesType.AuthKey);
CookiesManager.ClearCookies(CookiesType.SocketIO);
SecurityContext.Logout();
}
[Create("confirm", false)]
public ValidationResult CheckConfirmFromBody([FromBody] EmailValidationKeyModel model)
{
return EmailValidationKeyModelHelper.Validate(model);
}
[Create("confirm", false)]
[Consumes("application/x-www-form-urlencoded")]
public ValidationResult CheckConfirmFromForm([FromForm] EmailValidationKeyModel model)
{
return EmailValidationKeyModelHelper.Validate(model);
}
[Authorize(AuthenticationSchemes = "confirm", Roles = "PhoneActivation")]
[Create("setphone", false)]
public AuthenticationTokenData SaveMobilePhoneFromBody([FromBody] MobileModel model)
{
return SaveMobilePhone(model);
}
[Authorize(AuthenticationSchemes = "confirm", Roles = "PhoneActivation")]
[Create("setphone", false)]
[Consumes("application/x-www-form-urlencoded")]
public AuthenticationTokenData SaveMobilePhoneFromForm([FromForm] MobileModel model)
{
return SaveMobilePhone(model);
}
private AuthenticationTokenData SaveMobilePhone(MobileModel model)
{
ApiContext.AuthByClaim();
var user = UserManager.GetUsers(AuthContext.CurrentAccount.ID);
model.MobilePhone = SmsManager.SaveMobilePhone(user, model.MobilePhone);
MessageService.Send(MessageAction.UserUpdatedMobileNumber, MessageTarget.Create(user.ID), user.DisplayUserName(false, DisplayUserSettingsHelper), model.MobilePhone);
return new AuthenticationTokenData
{
Sms = true,
PhoneNoise = SmsSender.BuildPhoneNoise(model.MobilePhone),
Expires = new ApiDateTime(TenantManager, TimeZoneConverter, DateTime.UtcNow.Add(SmsKeyStorage.StoreInterval))
};
}
[Create(@"sendsms", false)]
public AuthenticationTokenData SendSmsCodeFromBody([FromBody] AuthModel model)
{
return SendSmsCode(model);
}
[Create(@"sendsms", false)]
[Consumes("application/x-www-form-urlencoded")]
public AuthenticationTokenData SendSmsCodeFromForm([FromForm] AuthModel model)
{
return SendSmsCode(model);
}
private AuthenticationTokenData SendSmsCode(AuthModel model)
{
var user = GetUser(model, out _);
SmsManager.PutAuthCode(user, true);
return new AuthenticationTokenData
{
Sms = true,
PhoneNoise = SmsSender.BuildPhoneNoise(user.MobilePhone),
Expires = new ApiDateTime(TenantManager, TimeZoneConverter, DateTime.UtcNow.Add(SmsKeyStorage.StoreInterval))
};
}
private AuthenticationTokenData AuthenticateMe(AuthModel auth)
{
bool viaEmail;
var user = GetUser(auth, out viaEmail);
if (StudioSmsNotificationSettingsHelper.IsVisibleSettings() && StudioSmsNotificationSettingsHelper.Enable)
{
if (string.IsNullOrEmpty(user.MobilePhone) || user.MobilePhoneActivationStatus == MobilePhoneActivationStatus.NotActivated)
return new AuthenticationTokenData
{
Sms = true,
ConfirmUrl = CommonLinkUtility.GetConfirmationUrl(user.Email, ConfirmType.PhoneActivation)
};
SmsManager.PutAuthCode(user, false);
return new AuthenticationTokenData
{
Sms = true,
PhoneNoise = SmsSender.BuildPhoneNoise(user.MobilePhone),
Expires = new ApiDateTime(TenantManager, TimeZoneConverter, DateTime.UtcNow.Add(SmsKeyStorage.StoreInterval)),
ConfirmUrl = CommonLinkUtility.GetConfirmationUrl(user.Email, ConfirmType.PhoneAuth)
};
}
if (TfaAppAuthSettings.IsVisibleSettings && SettingsManager.Load<TfaAppAuthSettings>().EnableSetting)
{
if (!TfaAppUserSettings.EnableForUser(SettingsManager, user.ID))
return new AuthenticationTokenData
{
Tfa = true,
TfaKey = TfaManager.GenerateSetupCode(user).ManualEntryKey,
ConfirmUrl = CommonLinkUtility.GetConfirmationUrl(user.Email, ConfirmType.TfaActivation)
};
return new AuthenticationTokenData
{
Tfa = true,
ConfirmUrl = CommonLinkUtility.GetConfirmationUrl(user.Email, ConfirmType.TfaAuth)
};
}
try
{
var token = SecurityContext.AuthenticateMe(user.ID);
CookiesManager.SetCookies(CookiesType.AuthKey, token, auth.Session);
MessageService.Send(viaEmail ? MessageAction.LoginSuccessViaApi : MessageAction.LoginSuccessViaApiSocialAccount);
var tenant = TenantManager.GetCurrentTenant().TenantId;
var expires = TenantCookieSettingsHelper.GetExpiresTime(tenant);
return new AuthenticationTokenData
{
Token = token,
Expires = new ApiDateTime(TenantManager, TimeZoneConverter, expires)
};
}
catch
{
MessageService.Send(user.DisplayUserName(false, DisplayUserSettingsHelper), viaEmail ? MessageAction.LoginFailViaApi : MessageAction.LoginFailViaApiSocialAccount);
throw new AuthenticationException("User authentication failed");
}
finally
{
SecurityContext.Logout();
}
}
private AuthenticationTokenData AuthenticateMeWithCode(AuthModel auth)
{
var tenant = TenantManager.GetCurrentTenant().TenantId;
var user = GetUser(auth, out _);
var sms = false;
try
{
if (StudioSmsNotificationSettingsHelper.IsVisibleSettings() && StudioSmsNotificationSettingsHelper.Enable)
{
sms = true;
SmsManager.ValidateSmsCode(user, auth.Code);
}
else if (TfaAppAuthSettings.IsVisibleSettings && SettingsManager.Load<TfaAppAuthSettings>().EnableSetting)
{
if (TfaManager.ValidateAuthCode(user, auth.Code))
{
MessageService.Send(MessageAction.UserConnectedTfaApp, MessageTarget.Create(user.ID));
}
}
else
{
throw new System.Security.SecurityException("Auth code is not available");
}
var token = SecurityContext.AuthenticateMe(user.ID);
MessageService.Send(sms ? MessageAction.LoginSuccessViaApiSms : MessageAction.LoginSuccessViaApiTfa);
var expires = TenantCookieSettingsHelper.GetExpiresTime(tenant);
var result = new AuthenticationTokenData
{
Token = token,
Expires = new ApiDateTime(TenantManager, TimeZoneConverter, expires)
};
if (sms)
{
result.Sms = true;
result.PhoneNoise = SmsSender.BuildPhoneNoise(user.MobilePhone);
}
else
{
result.Tfa = true;
}
return result;
}
catch
{
MessageService.Send(user.DisplayUserName(false, DisplayUserSettingsHelper), sms
? MessageAction.LoginFailViaApiSms
: MessageAction.LoginFailViaApiTfa,
MessageTarget.Create(user.ID));
throw new AuthenticationException("User authentication failed");
}
finally
{
SecurityContext.Logout();
}
}
private UserInfo GetUser(AuthModel memberModel, out bool viaEmail)
{
viaEmail = true;
var action = MessageAction.LoginFailViaApi;
UserInfo user;
try
{
if ((string.IsNullOrEmpty(memberModel.Provider) && string.IsNullOrEmpty(memberModel.SerializedProfile)) || memberModel.Provider == "email")
{
memberModel.UserName.ThrowIfNull(new ArgumentException(@"userName empty", "userName"));
if (!string.IsNullOrEmpty(memberModel.Password))
{
memberModel.Password.ThrowIfNull(new ArgumentException(@"password empty", "password"));
}
else
{
memberModel.PasswordHash.ThrowIfNull(new ArgumentException(@"PasswordHash empty", "PasswordHash"));
}
int counter;
int.TryParse(Cache.Get<string>("loginsec/" + memberModel.UserName), out counter);
if (++counter > SetupInfo.LoginThreshold && !SetupInfo.IsSecretEmail(memberModel.UserName))
{
throw new BruteForceCredentialException();
}
Cache.Insert("loginsec/" + memberModel.UserName, counter.ToString(CultureInfo.InvariantCulture), DateTime.UtcNow.Add(TimeSpan.FromMinutes(1)));
memberModel.PasswordHash = (memberModel.PasswordHash ?? "").Trim();
if (string.IsNullOrEmpty(memberModel.PasswordHash))
{
memberModel.Password = (memberModel.Password ?? "").Trim();
if (!string.IsNullOrEmpty(memberModel.Password))
{
memberModel.PasswordHash = PasswordHasher.GetClientPassword(memberModel.Password);
}
}
user = UserManager.GetUsersByPasswordHash(
TenantManager.GetCurrentTenant().TenantId,
memberModel.UserName,
memberModel.PasswordHash);
if (user == null || !UserManager.UserExists(user))
{
throw new Exception("user not found");
}
Cache.Insert("loginsec/" + memberModel.UserName, (--counter).ToString(CultureInfo.InvariantCulture), DateTime.UtcNow.Add(TimeSpan.FromMinutes(1)));
}
else
{
viaEmail = false;
action = MessageAction.LoginFailViaApiSocialAccount;
LoginProfile thirdPartyProfile;
if (!string.IsNullOrEmpty(memberModel.SerializedProfile))
{
thirdPartyProfile = new LoginProfile(Signature, InstanceCrypto, memberModel.SerializedProfile);
}
else
{
thirdPartyProfile = ProviderManager.GetLoginProfile(memberModel.Provider, memberModel.AccessToken);
}
memberModel.UserName = thirdPartyProfile.EMail;
user = GetUserByThirdParty(thirdPartyProfile);
}
}
catch (BruteForceCredentialException)
{
MessageService.Send(!string.IsNullOrEmpty(memberModel.UserName) ? memberModel.UserName : AuditResource.EmailNotSpecified, MessageAction.LoginFailBruteForce);
throw new AuthenticationException("Login Fail. Too many attempts");
}
catch
{
MessageService.Send(!string.IsNullOrEmpty(memberModel.UserName) ? memberModel.UserName : AuditResource.EmailNotSpecified, action);
throw new AuthenticationException("User authentication failed");
}
return user;
}
private UserInfo GetUserByThirdParty(LoginProfile loginProfile)
{
try
{
if (!string.IsNullOrEmpty(loginProfile.AuthorizationError))
{
// ignore cancellation
if (loginProfile.AuthorizationError != "Canceled at provider")
{
throw new Exception(loginProfile.AuthorizationError);
}
return Constants.LostUser;
}
var userInfo = Constants.LostUser;
Guid userId;
if (TryGetUserByHash(loginProfile.HashId, out userId))
{
userInfo = UserManager.GetUsers(userId);
}
var isNew = false;
if (CoreBaseSettings.Personal)
{
if (UserManager.UserExists(userInfo.ID) && SetupInfo.IsSecretEmail(userInfo.Email))
{
try
{
SecurityContext.AuthenticateMeWithoutCookie(ASC.Core.Configuration.Constants.CoreSystem);
UserManager.DeleteUser(userInfo.ID);
userInfo = Constants.LostUser;
}
finally
{
SecurityContext.Logout();
}
}
if (!UserManager.UserExists(userInfo.ID))
{
userInfo = JoinByThirdPartyAccount(loginProfile);
isNew = true;
}
}
if (isNew)
{
//TODO:
//var spam = HttpContext.Current.Request["spam"];
//if (spam != "on")
//{
// try
// {
// const string _databaseID = "com";
// using (var db = DbManager.FromHttpContext(_databaseID))
// {
// db.ExecuteNonQuery(new SqlInsert("template_unsubscribe", false)
// .InColumnValue("email", userInfo.Email.ToLowerInvariant())
// .InColumnValue("reason", "personal")
// );
// Log.Debug(string.Format("Write to template_unsubscribe {0}", userInfo.Email.ToLowerInvariant()));
// }
// }
// catch (Exception ex)
// {
// Log.Debug(string.Format("ERROR write to template_unsubscribe {0}, email:{1}", ex.Message, userInfo.Email.ToLowerInvariant()));
// }
//}
StudioNotifyService.UserHasJoin();
UserHelpTourHelper.IsNewUser = true;
PersonalSettingsHelper.IsNewUser = true;
}
return userInfo;
}
catch (Exception)
{
CookiesManager.ClearCookies(CookiesType.AuthKey);
CookiesManager.ClearCookies(CookiesType.SocketIO);
SecurityContext.Logout();
throw;
}
}
private UserInfo JoinByThirdPartyAccount(LoginProfile loginProfile)
{
if (string.IsNullOrEmpty(loginProfile.EMail))
{
throw new Exception(Resource.ErrorNotCorrectEmail);
}
var userInfo = UserManager.GetUserByEmail(loginProfile.EMail);
if (!UserManager.UserExists(userInfo.ID))
{
var newUserInfo = ProfileToUserInfo(loginProfile);
try
{
SecurityContext.AuthenticateMeWithoutCookie(ASC.Core.Configuration.Constants.CoreSystem);
userInfo = UserManagerWrapper.AddUser(newUserInfo, UserManagerWrapper.GeneratePassword());
}
finally
{
SecurityContext.Logout();
}
}
var linker = AccountLinker.Get("webstudio");
linker.AddLink(userInfo.ID.ToString(), loginProfile);
return userInfo;
}
private UserInfo ProfileToUserInfo(LoginProfile loginProfile)
{
if (string.IsNullOrEmpty(loginProfile.EMail)) throw new Exception(Resource.ErrorNotCorrectEmail);
var firstName = loginProfile.FirstName;
if (string.IsNullOrEmpty(firstName)) firstName = loginProfile.DisplayName;
var userInfo = new UserInfo
{
FirstName = string.IsNullOrEmpty(firstName) ? UserControlsCommonResource.UnknownFirstName : firstName,
LastName = string.IsNullOrEmpty(loginProfile.LastName) ? UserControlsCommonResource.UnknownLastName : loginProfile.LastName,
Email = loginProfile.EMail,
Title = string.Empty,
Location = string.Empty,
CultureName = CoreBaseSettings.CustomMode ? "ru-RU" : Thread.CurrentThread.CurrentUICulture.Name,
ActivationStatus = EmployeeActivationStatus.Activated,
};
var gender = loginProfile.Gender;
if (!string.IsNullOrEmpty(gender))
{
userInfo.Sex = gender == "male";
}
return userInfo;
}
private bool TryGetUserByHash(string hashId, out Guid userId)
{
userId = Guid.Empty;
if (string.IsNullOrEmpty(hashId)) return false;
var linkedProfiles = AccountLinker.Get("webstudio").GetLinkedObjectsByHashId(hashId);
var tmp = Guid.Empty;
if (linkedProfiles.Any(profileId => Guid.TryParse(profileId, out tmp) && UserManager.UserExists(tmp)))
userId = tmp;
return true;
}
}
public class AuthenticationTokenData
{
public string Token { get; set; }
public DateTime Expires { get; set; }
public bool Sms { get; set; }
public string PhoneNoise { get; set; }
public bool Tfa { get; set; }
public string TfaKey { get; set; }
public string ConfirmUrl { get; set; }
public static AuthenticationTokenData GetSample()
{
return new AuthenticationTokenData
{
Expires = DateTime.UtcNow,
Token = "abcde12345",
Sms = false,
PhoneNoise = null,
Tfa = false,
TfaKey = null
};
}
}
}