Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Grand.Infrastructure.Extensions;
using Grand.SharedKernel.Extensions;
using Grand.Mediator;
using Microsoft.AspNetCore.Mvc;

namespace Grand.Business.Authentication.Services;

Expand All @@ -25,7 +24,6 @@ public class ExternalAuthenticationService : IExternalAuthenticationService
#region Ctor

public ExternalAuthenticationService(
IGrandAuthenticationService authenticationService,
ICustomerManagerService customerManagerService,
ICustomerService customerService,
IGroupService groupService,
Expand All @@ -37,7 +35,6 @@ public ExternalAuthenticationService(
{
_customerSettings = customerSettings;
_externalAuthenticationSettings = externalAuthenticationSettings;
_authenticationService = authenticationService;
_customerManagerService = customerManagerService;
_customerService = customerService;
_groupService = groupService;
Expand All @@ -50,7 +47,6 @@ public ExternalAuthenticationService(

#region Fields

private readonly IGrandAuthenticationService _authenticationService;
private readonly ICustomerManagerService _customerManagerService;
private readonly ICustomerService _customerService;
private readonly IGroupService _groupService;
Expand All @@ -72,22 +68,19 @@ public ExternalAuthenticationService(
/// <param name="customer">Customer of the request (a guest when nobody is logged in)</param>
/// <param name="storeId">Store identifier</param>
/// <param name="language">Working language of the request</param>
/// <param name="returnUrl">URL to which the user will return after authentication</param>
/// <returns>Result of an authentication</returns>
protected virtual async Task<IActionResult> AuthenticateExistingUser(Customer associatedUser,
Customer currentLoggedInUser, Customer customer, string storeId, Language language, string returnUrl)
protected virtual async Task<ExternalAuthResult> AuthenticateExistingUser(Customer associatedUser,
Customer currentLoggedInUser, Customer customer, string storeId, Language language)
{
//log in guest user
if (currentLoggedInUser == null)
return await LoginUser(associatedUser, customer, storeId, language, returnUrl);
return await LoginUser(associatedUser, customer, storeId, language);

//account is already assigned to another user
if (currentLoggedInUser.Id != associatedUser.Id)
return Error(["Account is already assigned"]);
return ExternalAuthResult.AlreadyAssigned();

if (string.IsNullOrEmpty(returnUrl))
return new RedirectToRouteResult("HomePage", new { area = "" });
return new RedirectResult(returnUrl);
return ExternalAuthResult.Associated(currentLoggedInUser);
}

/// <summary>
Expand All @@ -98,26 +91,23 @@ protected virtual async Task<IActionResult> AuthenticateExistingUser(Customer as
/// <param name="customer">Customer of the request (a guest when nobody is logged in)</param>
/// <param name="storeId">Store identifier</param>
/// <param name="language">Working language of the request</param>
/// <param name="returnUrl">URL to which the user will return after authentication</param>
/// <returns>Result of an authentication</returns>
protected virtual async Task<IActionResult> AuthenticateNewUser(Customer currentLoggedInUser,
ExternalAuthParam parameters, Customer customer, string storeId, Language language, string returnUrl)
protected virtual async Task<ExternalAuthResult> AuthenticateNewUser(Customer currentLoggedInUser,
ExternalAuthParam parameters, Customer customer, string storeId, Language language)
{
//associate external account with logged-in user
if (currentLoggedInUser != null)
{
await AssociateCustomer(currentLoggedInUser, parameters);
if (string.IsNullOrEmpty(returnUrl))
return new RedirectToRouteResult("HomePage", new { area = "" });
return new RedirectResult(returnUrl);
return ExternalAuthResult.Associated(currentLoggedInUser);
}

//or try to register new user
if (_customerSettings.UserRegistrationType != UserRegistrationType.Disabled)
return await RegisterNewUser(parameters, customer, storeId, language, returnUrl);
return await RegisterNewUser(parameters, customer, storeId, language);

//registration is disabled
return Error(["Registration is disabled"]);
return ExternalAuthResult.RegistrationDisabled();
}

/// <summary>
Expand All @@ -127,10 +117,9 @@ protected virtual async Task<IActionResult> AuthenticateNewUser(Customer current
/// <param name="customer">Guest customer of the request; registration turns it into the new user</param>
/// <param name="storeId">Store identifier</param>
/// <param name="language">Working language of the request</param>
/// <param name="returnUrl">URL to which the user will return after authentication</param>
/// <returns>Result of an authentication</returns>
protected virtual async Task<IActionResult> RegisterNewUser(ExternalAuthParam parameters, Customer customer,
string storeId, Language language, string returnUrl)
protected virtual async Task<ExternalAuthResult> RegisterNewUser(ExternalAuthParam parameters, Customer customer,
string storeId, Language language)
{
var approved =
_customerSettings.UserRegistrationType is UserRegistrationType.Standard
Expand All @@ -156,15 +145,8 @@ _customerSettings.UserRegistrationType is UserRegistrationType.Standard
//associate external account with registered user
await AssociateCustomer(customer, parameters);

//authenticate
if (!approved)
return _customerSettings.UserRegistrationType == UserRegistrationType.AdminApproval
? new RedirectToRouteResult("RegisterResult",
new { resultId = (int)UserRegistrationType.AdminApproval })
: Error(["Error on registration"]);
await _authenticationService.SignIn(customer, false);

return new RedirectToRouteResult("RegisterResult", new { resultId = (int)UserRegistrationType.Standard });
//Disabled never gets here, so the only unapproved registration type is AdminApproval
return approved ? ExternalAuthResult.Registered(customer) : ExternalAuthResult.AwaitingApproval(customer);
}

/// <summary>
Expand All @@ -174,31 +156,14 @@ _customerSettings.UserRegistrationType is UserRegistrationType.Standard
/// <param name="previousCustomer">Customer of the request before the login (the guest whose cart is merged)</param>
/// <param name="storeId">Store identifier</param>
/// <param name="language">Working language of the request</param>
/// <param name="returnUrl">URL to which the user will return after authentication</param>
/// <returns>Result of an authentication</returns>
protected virtual async Task<IActionResult> LoginUser(Customer user, Customer previousCustomer, string storeId,
Language language, string returnUrl)
protected virtual async Task<ExternalAuthResult> LoginUser(Customer user, Customer previousCustomer,
string storeId, Language language)
{
//raise event
//raise event; the caller signs the user in afterwards
await _mediator.Publish(new CustomerLoggedInEvent(user, previousCustomer, storeId, language));

//authenticate
await _authenticationService.SignIn(user, false);

if (string.IsNullOrEmpty(returnUrl))
return new RedirectToRouteResult("HomePage", new { area = "" });

return new RedirectResult(returnUrl);
}

/// <summary>
/// Add errors that occurred during authentication
/// </summary>
/// <param name="errors">Collection of errors</param>
/// <returns>Result of an authentication</returns>
protected virtual IActionResult Error(IEnumerable<string> errors)
{
return new RedirectToActionResult("ExternalAuthenticationError", "Common", new { Errors = errors });
return ExternalAuthResult.LoggedIn(user);
}

#endregion
Expand Down Expand Up @@ -276,30 +241,28 @@ public virtual bool AuthenticationProviderIsAvailable(string systemName, Custome
/// <param name="customer">Customer of the request (a guest when nobody is logged in)</param>
/// <param name="store">Store of the request</param>
/// <param name="language">Working language of the request</param>
/// <param name="returnUrl">URL to which the user will return after authentication</param>
/// <returns>Result of an authentication</returns>
public virtual async Task<IActionResult> Authenticate(ExternalAuthParam parameters, Customer customer, Store store,
Language language, string returnUrl = null)
public virtual async Task<ExternalAuthResult> Authenticate(ExternalAuthParam parameters, Customer customer,
Store store, Language language)
{
ArgumentNullException.ThrowIfNull(parameters);
ArgumentNullException.ThrowIfNull(customer);
ArgumentNullException.ThrowIfNull(store);
ArgumentNullException.ThrowIfNull(language);

if (!AuthenticationProviderIsAvailable(parameters.ProviderSystemName, customer, store))
return Error(["External authentication method cannot be loaded"]);
return ExternalAuthResult.Error("External authentication method cannot be loaded");

//get current logged-in user
var currentLoggedInUser = await _groupService.IsRegistered(customer) ? customer : null;

//authenticate associated user if already exists
var associatedUser = await GetCustomer(parameters);
if (associatedUser != null)
return await AuthenticateExistingUser(associatedUser, currentLoggedInUser, customer, store.Id, language,
returnUrl);
return await AuthenticateExistingUser(associatedUser, currentLoggedInUser, customer, store.Id, language);

//or associate and authenticate new user
return await AuthenticateNewUser(currentLoggedInUser, parameters, customer, store.Id, language, returnUrl);
return await AuthenticateNewUser(currentLoggedInUser, parameters, customer, store.Id, language);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Grand.Domain.Customers;
using Grand.Domain.Localization;
using Grand.Domain.Stores;
using Microsoft.AspNetCore.Mvc;

namespace Grand.Business.Core.Interfaces.Authentication;

Expand All @@ -14,16 +13,18 @@ public interface IExternalAuthenticationService
#region Authentication

/// <summary>
/// Use passed parameters to authenticate customer
/// Use passed parameters to find, associate or register the customer. Nothing is signed in here: the caller
/// must sign in the customer of a LoggedIn or Registered result (IExternalSignInService in Grand.Web.Common
/// does it). A LoggedIn result has already published CustomerLoggedInEvent, which merges the guest's cart into
/// the customer, so a caller that skips the sign-in leaves the request as a guest with an emptied cart.
/// </summary>
/// <param name="parameters">External authentication parameters</param>
/// <param name="customer">Customer of the request (a guest when nobody is logged in)</param>
/// <param name="store">Store of the request</param>
/// <param name="language">Working language of the request</param>
/// <param name="returnUrl">Defines the URL of user return, when successfully authenticated</param>
/// <returns>Result of an authentication</returns>
Task<IActionResult> Authenticate(ExternalAuthParam parameters, Customer customer, Store store, Language language,
string returnUrl = null);
Task<ExternalAuthResult> Authenticate(ExternalAuthParam parameters, Customer customer, Store store,
Language language);

#endregion

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Grand.Domain.Customers;

namespace Grand.Business.Core.Utilities.Authentication;

public enum ExternalAuthStatus
{
/// <summary>An existing customer associated with the external account is to be signed in</summary>
LoggedIn,

/// <summary>A new customer was registered, approved and is to be signed in</summary>
Registered,

/// <summary>The external account is associated with the customer already signed in; nothing to sign in</summary>
Associated,

/// <summary>A new customer was registered and waits for admin approval</summary>
AwaitingApproval,

/// <summary>The external account belongs to another customer than the one signed in</summary>
AlreadyAssigned,

/// <summary>The external account is unknown and registration of new customers is disabled</summary>
RegistrationDisabled,

/// <summary>The authentication failed; <see cref="ExternalAuthResult.Errors" /> says why</summary>
Error
}

/// <summary>
/// Outcome of an external authentication; signing in, redirecting and the texts shown to the customer are left
/// to the caller
/// </summary>
public class ExternalAuthResult
{
private ExternalAuthResult(ExternalAuthStatus status, Customer customer, IReadOnlyList<string> errors)
{
Status = status;
Customer = customer;
Errors = errors;
}

public ExternalAuthStatus Status { get; }

/// <summary>The customer to sign in (LoggedIn, Registered) or the one the account was associated with</summary>
public Customer Customer { get; }

/// <summary>Technical reasons of an Error result; empty for every other status</summary>
public IReadOnlyList<string> Errors { get; }

public static ExternalAuthResult LoggedIn(Customer customer) => new(ExternalAuthStatus.LoggedIn, customer, []);
public static ExternalAuthResult Registered(Customer customer) => new(ExternalAuthStatus.Registered, customer, []);
public static ExternalAuthResult Associated(Customer customer) => new(ExternalAuthStatus.Associated, customer, []);

public static ExternalAuthResult AwaitingApproval(Customer customer) =>
new(ExternalAuthStatus.AwaitingApproval, customer, []);

public static ExternalAuthResult AlreadyAssigned() =>
new(ExternalAuthStatus.AlreadyAssigned, null, []);

public static ExternalAuthResult RegistrationDisabled() =>
new(ExternalAuthStatus.RegistrationDisabled, null, []);

public static ExternalAuthResult Error(string error) => new(ExternalAuthStatus.Error, null, [error]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Grand.Infrastructure;
using Grand.SharedKernel;
using Grand.Web.Common.Controllers;
using Grand.Web.Common.Security.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Facebook;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -18,10 +19,12 @@ public class FacebookAuthenticationController : BasePluginController

public FacebookAuthenticationController(
IExternalAuthenticationService externalAuthenticationService,
IExternalSignInService externalSignInService,
IConfiguration configuration,
IContextAccessor contextAccessor)
{
_externalAuthenticationService = externalAuthenticationService;
_externalSignInService = externalSignInService;
_configuration = configuration;
_contextAccessor = contextAccessor;
}
Expand All @@ -31,6 +34,7 @@ public FacebookAuthenticationController(
#region Fields

private readonly IExternalAuthenticationService _externalAuthenticationService;
private readonly IExternalSignInService _externalSignInService;
private readonly IConfiguration _configuration;
private readonly IContextAccessor _contextAccessor;

Expand Down Expand Up @@ -76,7 +80,7 @@ public async Task<IActionResult> FacebookLoginCallback(string returnUrl)
};

//authenticate Grand user
return await _externalAuthenticationService.Authenticate(authenticationParameters,
return await _externalSignInService.Authenticate(authenticationParameters,
_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
_contextAccessor.WorkContext.WorkingLanguage, returnUrl);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Grand.Infrastructure;
using Grand.SharedKernel;
using Grand.Web.Common.Controllers;
using Grand.Web.Common.Security.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -17,10 +18,12 @@ public class GoogleAuthenticationController : BasePluginController
#region Ctor

public GoogleAuthenticationController(IExternalAuthenticationService externalAuthenticationService,
IExternalSignInService externalSignInService,
IConfiguration configuration,
IContextAccessor contextAccessor)
{
_externalAuthenticationService = externalAuthenticationService;
_externalSignInService = externalSignInService;
_configuration = configuration;
_contextAccessor = contextAccessor;
}
Expand All @@ -30,6 +33,7 @@ public GoogleAuthenticationController(IExternalAuthenticationService externalAut
#region Fields

private readonly IExternalAuthenticationService _externalAuthenticationService;
private readonly IExternalSignInService _externalSignInService;
private readonly IConfiguration _configuration;
private readonly IContextAccessor _contextAccessor;

Expand Down Expand Up @@ -75,7 +79,7 @@ public async Task<IActionResult> GoogleLoginCallback(string returnUrl)
};

//authenticate grand user
return await _externalAuthenticationService.Authenticate(authenticationParameters,
return await _externalSignInService.Authenticate(authenticationParameters,
_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
_contextAccessor.WorkContext.WorkingLanguage, returnUrl);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Grand.Infrastructure.Configuration;
using Grand.Mediator;
using Grand.Web.Common.Controllers;
using Grand.Web.Common.Security.Authentication;
using Grand.Web.Common.Security.ExternalRegistration;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
Expand Down
Loading
Loading