Skip to content

Commit

Permalink
Merge branch 'main' into issue/OCORE-136
Browse files Browse the repository at this point in the history
  • Loading branch information
Piedone authored Apr 24, 2024
2 parents fc6d725 + 08a6771 commit a91d5b6
Show file tree
Hide file tree
Showing 22 changed files with 181 additions and 43 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -3122,6 +3122,15 @@
"contributions": [
"code"
]
},
{
"login": "sobotama",
"name": "sobotama",
"avatar_url": "https://avatars.githubusercontent.com/u/77323555?v=4",
"profile": "https://github.com/sobotama",
"contributions": [
"code"
]
}
],
"skipCi": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public override void ConfigureServices(IServiceCollection services)
services.AddObjectGraphType<LinkField, LinkFieldQueryObjectType>();
services.AddObjectGraphType<HtmlField, HtmlFieldQueryObjectType>();
services.AddObjectGraphType<ContentPickerField, ContentPickerFieldQueryObjectType>();
services.AddObjectGraphType<UserPickerField, UserPickerFieldQueryObjectType>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Linq;
using GraphQL;
using GraphQL.DataLoader;
using GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using OrchardCore.Apis.GraphQL;
using OrchardCore.ContentFields.Fields;
using OrchardCore.ContentManagement;
using OrchardCore.Users.GraphQL;
using OrchardCore.Users.Indexes;
using OrchardCore.Users.Models;
using YesSql;
using YesSql.Services;

namespace OrchardCore.ContentFields.GraphQL
{
public class UserPickerFieldQueryObjectType : ObjectGraphType<UserPickerField>
{
public UserPickerFieldQueryObjectType(IStringLocalizer<UserPickerFieldQueryObjectType> S)
{
Name = nameof(UserPickerField);

Field<ListGraphType<StringGraphType>, IEnumerable<string>>("userIds")
.Description(S["user ids"])
.PagingArguments()
.Resolve(resolve =>
{
return resolve.Page(resolve.Source.UserIds);
});

Field<ListGraphType<UserType>, IEnumerable<User>>("users")
.Description(S["the user items"])
.PagingArguments()
.ResolveAsync(resolve =>
{
var userLoader = GetOrAddUserProfileByIdDataLoader(resolve);
return userLoader.LoadAsync(resolve.Page(resolve.Source.UserIds)).Then(itemResultSet =>
{
return itemResultSet.SelectMany(users => users);
});
});
}

private static IDataLoader<string, IEnumerable<User>> GetOrAddUserProfileByIdDataLoader<T>(IResolveFieldContext<T> context)
{
var dataLoaderContextAccessor = context.RequestServices.GetRequiredService<IDataLoaderContextAccessor>();

return dataLoaderContextAccessor.Context.GetOrAddCollectionBatchLoader("GetOrAddUserByIds", async (IEnumerable<string> userIds) =>
{
if (userIds == null || !userIds.Any())
{
return default;
}
var session = context.RequestServices.GetService<ISession>();
var users = await session.Query<User, UserIndex>(user => user.UserId.IsIn(userIds)).ListAsync();
return users.ToLookup(user => user.UserId);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Shortcodes.Abstractions\OrchardCore.Shortcodes.Abstractions.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ResourceManagement\OrchardCore.ResourceManagement.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.Users.Core\OrchardCore.Users.Core.csproj" />
<ProjectReference Include="..\OrchardCore.Users\OrchardCore.Users.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
using System;
using System.Threading.Tasks;
using Esprima;
using Jint.Runtime;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Mvc.ModelBinding;
using OrchardCore.Rules.Models;
using OrchardCore.Rules.Services;
using OrchardCore.Rules.ViewModels;

namespace OrchardCore.Rules.Drivers
{
public class JavascriptConditionDisplayDriver : DisplayDriver<Condition, JavascriptCondition>
{
private readonly IHtmlLocalizer H;
private readonly IStringLocalizer S;
private readonly INotifier _notifier;
private readonly JavascriptConditionEvaluator _evaluator;

public JavascriptConditionDisplayDriver(
IHtmlLocalizer<JavascriptConditionDisplayDriver> htmlLocalizer,
IStringLocalizer<JavascriptConditionDisplayDriver> stringLocalizer,
JavascriptConditionEvaluator evaluator,
INotifier notifier)
{
H = htmlLocalizer;
S = stringLocalizer;
_evaluator = evaluator;
_notifier = notifier;
}

public override IDisplayResult Display(JavascriptCondition condition)
{
return
Expand All @@ -32,8 +57,40 @@ public override async Task<IDisplayResult> UpdateAsync(JavascriptCondition condi
var model = new JavascriptConditionViewModel();
await updater.TryUpdateModelAsync(model, Prefix);

// TODO is empty.
condition.Script = model.Script;
// CodeMirror hides the textarea which displays the error when updater.ModelState.AddModelError() is used,
// that's why a notifier is used to show validation errors.
if (string.IsNullOrWhiteSpace(model.Script))
{
updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["Please provide a script."]);
await _notifier.ErrorAsync(H["Please provide a script."]);
return Edit(condition);
}

try
{
_ = await _evaluator.EvaluateAsync(new()
{
ConditionId = condition.ConditionId,
Name = condition.Name,
Script = model.Script
});
condition.Script = model.Script;
}
catch (ParserException ex) // Invalid syntax
{
updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["The script couldn't be parsed. Details: {0}", ex.Message]);
await _notifier.ErrorAsync(H["The script couldn't be parsed. Details: {0}", ex.Message]);
}
catch (JavaScriptException ex) // Evaluation threw an Error
{
updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["JavaScript evaluation resulted in an exception. Details: {0}", ex.Message]);
await _notifier.ErrorAsync(H["JavaScript evaluation resulted in an exception. Details: {0}", ex.Message]);
}
catch (Exception ex) when (ex is InvalidCastException or FormatException) // Evaluation completes successfully, but the result cannot be converted to Boolean
{
updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["The script evaluation failed. Details: {0}", ex.Message]);
await _notifier.ErrorAsync(H["The script evaluation failed. Details: {0}", ex.Message]);
}

return Edit(condition);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Jint" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ContentManagement.Display\OrchardCore.ContentManagement.Display.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.DisplayManagement\OrchardCore.DisplayManagement.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ private async Task<SignInResult> ExternalLoginSignInAsync(IUser user, ExternalLo
}
catch (Exception ex)
{
_logger.LogError(ex, "{externalLoginHandler} - IExternalLoginHandler.UpdateRoles threw an exception", item.GetType());
_logger.LogError(ex, "{ExternalLoginHandler}.UpdateRoles threw an exception", item.GetType());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,15 +499,15 @@ public async Task<IActionResult> EditPassword(string id)
return Forbid();
}

var model = new ResetPasswordViewModel { Identifier = user.UserName };
var model = new ResetPasswordViewModel { UsernameOrEmail = user.UserName };

return View(model);
}

[HttpPost]
public async Task<IActionResult> EditPassword(ResetPasswordViewModel model)
{
if (await _userService.GetUserAsync(model.Identifier) is not User user)
if (await _userService.GetUserAsync(model.UsernameOrEmail) is not User user)
{
return NotFound();
}
Expand All @@ -521,7 +521,7 @@ public async Task<IActionResult> EditPassword(ResetPasswordViewModel model)
{
var token = await _userManager.GeneratePasswordResetTokenAsync(user);

if (await _userService.ResetPasswordAsync(model.Identifier, token, model.NewPassword, ModelState.AddModelError))
if (await _userService.ResetPasswordAsync(model.UsernameOrEmail, token, model.NewPassword, ModelState.AddModelError))
{
await _notifier.SuccessAsync(H["Password updated correctly."]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public async Task<IActionResult> ForgotPasswordPOST()

if (ModelState.IsValid)
{
var user = await _userService.GetForgotPasswordUserAsync(model.Identifier) as User;
var user = await _userService.GetForgotPasswordUserAsync(model.UsernameOrEmail) as User;
if (user == null || await MustValidateEmailAsync(user))
{
// returns to confirmation page anyway: we don't want to let scrapers know if a username or an email exist
Expand Down Expand Up @@ -166,7 +166,7 @@ public async Task<IActionResult> ResetPasswordPOST()
{
var token = Encoding.UTF8.GetString(Convert.FromBase64String(model.ResetToken));

if (await _userService.ResetPasswordAsync(model.Identifier, token, model.NewPassword, ModelState.AddModelError))
if (await _userService.ResetPasswordAsync(model.UsernameOrEmail, token, model.NewPassword, ModelState.AddModelError))
{
return RedirectToAction(nameof(ResetPasswordConfirmation));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public override IDisplayResult Edit(ForgotPasswordForm model)
{
return Initialize<ForgotPasswordViewModel>("ForgotPasswordFormIdentifier", vm =>
{
vm.Identifier = model.Identifier;
vm.UsernameOrEmail = model.UsernameOrEmail;
}).Location("Content");
}

Expand All @@ -23,7 +23,7 @@ public override async Task<IDisplayResult> UpdateAsync(ForgotPasswordForm model,

await updater.TryUpdateModelAsync(viewModel, Prefix);

model.Identifier = viewModel.Identifier;
model.UsernameOrEmail = viewModel.UsernameOrEmail;

return Edit(model);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public override IDisplayResult Edit(ResetPasswordForm model)
{
return Initialize<ResetPasswordViewModel>("ResetPasswordFormIdentifier", vm =>
{
vm.Identifier = model.Identifier;
vm.UsernameOrEmail = model.UsernameOrEmail;
vm.NewPassword = model.NewPassword;
vm.ResetToken = model.ResetToken;
}).Location("Content");
Expand All @@ -25,7 +25,7 @@ public override async Task<IDisplayResult> UpdateAsync(ResetPasswordForm model,

await updater.TryUpdateModelAsync(vm, Prefix);

model.Identifier = vm.Identifier;
model.UsernameOrEmail = vm.UsernameOrEmail;
model.NewPassword = vm.NewPassword;
model.ResetToken = vm.ResetToken;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ namespace OrchardCore.Users.ViewModels
{
public class ForgotPasswordViewModel
{
[Obsolete("Email property is no longer used and will be removed in future releases. Instead use Identifier.")]
[Obsolete("Email property is no longer used and will be removed in future releases. Instead use UsernameOrEmail.")]
[Email.EmailAddress(ErrorMessage = "Invalid Email.")]
public string Email { get; set; }

[Required(ErrorMessage = "Username or email address is required.")]
public string Identifier { get; set; }
public string UsernameOrEmail { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ namespace OrchardCore.Users.ViewModels
{
public class ResetPasswordViewModel
{
[Obsolete("Email property is no longer used and will be removed in future releases. Instead use Identifier.")]
[Obsolete("Email property is no longer used and will be removed in future releases. Instead use UsernameOrEmail.")]
[Email.EmailAddress(ErrorMessage = "Invalid Email.")]
public string Email { get; set; }

[Required(ErrorMessage = "Username or email address is required.")]
public string Identifier { get; set; }
public string UsernameOrEmail { get; set; }

[Required(ErrorMessage = "New password is required.")]
[DataType(DataType.Password)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<form asp-controller="Admin" asp-action="EditPassword" method="post" class="form-horizontal no-multisubmit">
<div asp-validation-summary="All"></div>
<input asp-for="Identifier" type="hidden" />
<input asp-for="UsernameOrEmail" type="hidden" />
<div class="mb-3">
<label asp-for="NewPassword" class="w-lg-75 w-xl-50 form-label">@T["New password"]</label>
<div class="w-lg-75 w-xl-50">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@model ForgotPasswordViewModel

<div class="mb-3">
<label asp-for="Identifier" class="form-label">@T["Username or email address"]</label>
<input asp-for="Identifier" class="form-control" />
<label asp-for="UsernameOrEmail" class="form-label">@T["Username or email address"]</label>
<input asp-for="UsernameOrEmail" class="form-control" />
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<input asp-for="ResetToken" type="hidden" />

<div class="mb-3">
<label asp-for="Identifier" class="form-label">@T["Username or email address"]</label>
<input asp-for="Identifier" class="form-control" />
<span asp-validation-for="Identifier" class="text-danger"></span>
<label asp-for="UsernameOrEmail" class="form-label">@T["Username or email address"]</label>
<input asp-for="UsernameOrEmail" class="form-control" />
<span asp-validation-for="UsernameOrEmail" class="text-danger"></span>
</div>

<div class="mb-3">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ public interface IUserService
/// <summary>
/// Authenticates the user credentials.
/// </summary>
/// <param name="identifier">The username or email address.</param>
/// <param name="usernameOrEmail">The username or email address.</param>
/// <param name="password">The user password.</param>
/// <param name="reportError">The error reported in case failure happened during the authentication process.</param>
/// <returns>A <see cref="IUser"/> that represents an authenticated user.</returns>
Task<IUser> AuthenticateAsync(string identifier, string password, Action<string, string> reportError);
Task<IUser> AuthenticateAsync(string usernameOrEmail, string password, Action<string, string> reportError);

/// <summary>
/// Creates a user.
Expand Down Expand Up @@ -56,9 +56,9 @@ public interface IUserService
/// <summary>
/// Gets the user with a specified username or email address.
/// </summary>
/// <param name="identifier">The username or email address.</param>
/// <param name="usernameOrEmail">The username or email address.</param>
/// <returns>The <see cref="IUser"/> represents the retrieved user.</returns>
Task<IUser> GetUserAsync(string identifier);
Task<IUser> GetUserAsync(string usernameOrEmail);

/// <summary>
/// Gets the user with a specified ID.
Expand All @@ -77,12 +77,12 @@ public interface IUserService
/// <summary>
/// Resets the user password.
/// </summary>
/// <param name="identifier">The username or email address.</param>
/// <param name="usernameOrEmail">The username or email address.</param>
/// <param name="resetToken">The token used to reset the password.</param>
/// <param name="newPassword">The new password.</param>
/// <param name="reportError">The error reported in case failure happened during the reset process.</param>
/// <returns>Returns <c>true</c> if the password reset, otherwise <c>false</c>.</returns>
Task<bool> ResetPasswordAsync(string identifier, string resetToken, string newPassword, Action<string, string> reportError);
Task<bool> ResetPasswordAsync(string usernameOrEmail, string resetToken, string newPassword, Action<string, string> reportError);

/// <summary>
/// Creates a <see cref="ClaimsPrincipal"/> for a given user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace OrchardCore.Users.Models;

public class ForgotPasswordForm : Entity
{
public string Identifier { get; set; }
public string UsernameOrEmail { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace OrchardCore.Users.Models;

public class ResetPasswordForm : Entity
{
public string Identifier { get; set; }
public string UsernameOrEmail { get; set; }

public string NewPassword { get; set; }

Expand Down
Loading

0 comments on commit a91d5b6

Please sign in to comment.