Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to Remove User from a Role (Issue #14632) #14652

Merged
merged 14 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
@@ -0,0 +1,9 @@
@using OrchardCore.Workflows.ViewModels
@using OrchardCore.Workflows.Helpers
@using OrchardCore.Users.Workflows.Activities
@using OrchardCore.Users.Workflows.ViewModels
@model ActivityViewModel<RemoveUserRoleTask>
<header>
<h4><i class="fa-solid fa-user" aria-hidden="true"></i>@Model.Activity.GetTitleOrDefault(() => T["Remove user from role"])</h4>
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
</header>
<em>@T["{0} from role {1}", Model.Activity.UserName, Model.Activity.RoleName]</em>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@using OrchardCore.Users.Workflows.ViewModels
@model RemoveUserRoleTaskViewModel

<div class="mb-3" asp-validation-class-for="UserName">
<label asp-for="UserName">@T["UserName"]</label>
<input type="text" asp-for="UserName" class="form-control code" />
<span asp-validation-for="UserName"></span>
<span class="hint">@T["The User to update. With Liquid support."]</span>
</div>

<div class="mb-3" asp-validation-class-for="RoleName">
<label asp-for="RoleName">@T["RoleName"]</label>
<input type="text" asp-for="RoleName" class="form-control code" />
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
<span asp-validation-for="RoleName"></span>
<span class="hint">@T["The Role to remove. With Liquid support."]</span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h4 class="card-title"><i class="fa-solid fa-user" aria-hidden="true"></i>@T["Remove user from role"]</h4>
<p>@T["Removes a user from a role."]</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@using OrchardCore.Workflows.ViewModels
@using OrchardCore.Workflows.Helpers
@using OrchardCore.Users.Workflows.Activities
@using OrchardCore.Users.Workflows.ViewModels
@model ActivityViewModel<SelectUsersInRoleTask>
<header>
<h4><i class="fa-solid fa-user" aria-hidden="true"></i>@Model.Activity.GetTitleOrDefault(() => T["Select users in role"])</h4>
</header>
<em>@T["Store users of role {0} to {1}", Model.Activity.RoleName, Model.Activity.PropertyName]</em>
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@using OrchardCore.Users.Workflows.ViewModels
@model SelectUsersInRoleTaskViewModel

<div class="mb-3" asp-validation-class-for="PropertyName">
<label asp-for="PropertyName">@T["PropertyName"]</label>
<input type="text" asp-for="PropertyName" class="form-control code" />
<span asp-validation-for="PropertyName"></span>
<span class="hint">@T["The PropertyName to save list. With Liquid support."]</span>
</div>

<div class="mb-3" asp-validation-class-for="RoleName">
<label asp-for="RoleName">@T["RoleName"]</label>
<input type="text" asp-for="RoleName" class="form-control code" />
<span asp-validation-for="RoleName"></span>
<span class="hint">@T["The Role to search for. With Liquid support."]</span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h4 class="card-title"><i class="fa-solid fa-user" aria-hidden="true"></i>@T["Select users in role"]</h4>
<p>@T["Select users in role."]</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Localization;
using OrchardCore.Users.Models;
using OrchardCore.Users.Services;
using OrchardCore.Workflows.Abstractions.Models;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Models;
using OrchardCore.Workflows.Services;

namespace OrchardCore.Users.Workflows.Activities
{
public class RemoveUserRoleTask : TaskActivity
{
private readonly UserManager<IUser> _userManager;
private readonly IUserService _userService;
private readonly IWorkflowExpressionEvaluator _expressionEvaluator;
protected readonly IStringLocalizer S;

public RemoveUserRoleTask(UserManager<IUser> userManager, IUserService userService, IWorkflowExpressionEvaluator expressionvaluator, IStringLocalizer<RemoveUserRoleTask> localizer)
{
_userManager = userManager;
_userService = userService;
_expressionEvaluator = expressionvaluator;
S = localizer;
}

public override string Name => nameof(RemoveUserRoleTask);

public override LocalizedString DisplayText => S["Remove User Role Task"];

public override LocalizedString Category => S["User"];

public WorkflowExpression<string> UserName
{
get => GetProperty(() => new WorkflowExpression<string>());
set => SetProperty(value);
}

public WorkflowExpression<string> RoleName
{
get => GetProperty(() => new WorkflowExpression<string>());
set => SetProperty(value);
}

public override IEnumerable<Outcome> GetPossibleOutcomes(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
return Outcomes(S["Done"], S["Failed"]);
}

public override async Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
var userName = await _expressionEvaluator.EvaluateAsync(UserName, workflowContext, null);
var roleName = await _expressionEvaluator.EvaluateAsync(RoleName, workflowContext, null);

var user = (User)await _userService.GetUserAsync(userName);

if (user != null)
{
if (user.RoleNames.Contains(roleName))
{
await _userManager.RemoveFromRoleAsync(user, roleName);
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}

return Outcomes("Done");
}
else
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
return Outcomes("Failed");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Localization;
using OrchardCore.Users.Models;
using OrchardCore.Users.Services;
using OrchardCore.Workflows.Abstractions.Models;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Models;
using OrchardCore.Workflows.Services;

namespace OrchardCore.Users.Workflows.Activities
{
public class SelectUsersInRoleTask : TaskActivity
{
private readonly UserManager<IUser> _userManager;
private readonly IUserService _userService;
private readonly IWorkflowExpressionEvaluator _expressionEvaluator;
protected readonly IStringLocalizer S;

public SelectUsersInRoleTask(UserManager<IUser> userManager, IUserService userService, IWorkflowExpressionEvaluator expressionvaluator, IStringLocalizer<SelectUsersInRoleTask> localizer)
{
_userManager = userManager;
_userService = userService;
_expressionEvaluator = expressionvaluator;
S = localizer;
}

public override string Name => nameof(SelectUsersInRoleTask);

public override LocalizedString DisplayText => S["Select Users in Role Task"];

public override LocalizedString Category => S["User"];

public WorkflowExpression<string> PropertyName
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
get => GetProperty(() => new WorkflowExpression<string>());
set => SetProperty(value);
}

public WorkflowExpression<string> RoleName
{
get => GetProperty(() => new WorkflowExpression<string>());
set => SetProperty(value);
}

public override IEnumerable<Outcome> GetPossibleOutcomes(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
return Outcomes(S["Done"], S["Empty"], S["Failed"]);
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}

public override async Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
var propName = await _expressionEvaluator.EvaluateAsync(PropertyName, workflowContext, null);
var roleName = await _expressionEvaluator.EvaluateAsync(RoleName, workflowContext, null);

if (!string.IsNullOrEmpty(propName) && !string.IsNullOrEmpty(roleName))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all you need here is something like this to get all users in a given role

var expression = await _expressionEvaluator.EvaluateAsync(RoleNames, workflowContext, null);
var roleNames = JsonSerializer.Deserialize<string[]>(expression ?? string.Empty);

var users = new List<IUser>();

foreach (var roleName in roleNames)
{
    users.AddRange(await _userManager.GetUsersInRoleAsync(roleName));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need email addresses. This is to supplement "Add Email Task" (Recipients, liquid)
OC-Submission-SelectUsers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why you need this. If you are removing a role, not sure why do you need the email. Maybe you need another task that would locate the user? Like write the user object into the workflow output, then email task would read the output and extract the email.

{
var usersInRole = await _userManager.GetUsersInRoleAsync(roleName);
if (usersInRole.Count > 0)
{
List<string> output;
if (propName.Contains("email", StringComparison.InvariantCultureIgnoreCase))
{
output = usersInRole.Select((u) => (u as User)?.Email).ToList();
}
else
{
output = usersInRole.Select(u => u.UserName).ToList();
}
workflowContext.Properties[propName] = string.Join(",", output);
return Outcomes("Done");
}
return Outcomes("Empty");
}
return Outcomes("Failed");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using OrchardCore.Workflows.Display;
using OrchardCore.Workflows.Models;
using OrchardCore.Users.Workflows.Activities;
using OrchardCore.Users.Workflows.ViewModels;

namespace OrchardCore.Users.Workflows.Drivers
{
public class RemoveUserRoleTaskDisplayDriver : ActivityDisplayDriver<RemoveUserRoleTask, RemoveUserRoleTaskViewModel>
{
protected override void EditActivity(RemoveUserRoleTask activity, RemoveUserRoleTaskViewModel model)
{
model.UserName = activity.UserName.Expression;
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
model.RoleName = activity.RoleName.Expression;
}

protected override void UpdateActivity(RemoveUserRoleTaskViewModel model, RemoveUserRoleTask activity)
{
activity.UserName = new WorkflowExpression<string>(model.UserName);
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
activity.RoleName = new WorkflowExpression<string>(model.RoleName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using OrchardCore.Users.Workflows.Activities;
using OrchardCore.Users.Workflows.ViewModels;
using OrchardCore.Workflows.Display;
using OrchardCore.Workflows.Models;

namespace OrchardCore.Users.Workflows.Drivers
{
public class SelectUsersInRoleTaskDisplayDriver : ActivityDisplayDriver<SelectUsersInRoleTask, SelectUsersInRoleTaskViewModel>
{
protected override void EditActivity(SelectUsersInRoleTask activity, SelectUsersInRoleTaskViewModel model)
{
model.PropertyName = activity.PropertyName.Expression;
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
model.RoleName = activity.RoleName.Expression;
}

protected override void UpdateActivity(SelectUsersInRoleTaskViewModel model, SelectUsersInRoleTask activity)
{
activity.PropertyName = new WorkflowExpression<string>(model.PropertyName);
activity.RoleName = new WorkflowExpression<string>(model.RoleName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public override void ConfigureServices(IServiceCollection services)
services.AddActivity<UserLoggedInEvent, UserLoggedInEventDisplayDriver>();
services.AddScoped<IUserEventHandler, UserEventHandler>();
services.AddActivity<AssignUserRoleTask, AssignUserRoleTaskDisplayDriver>();
services.AddActivity<RemoveUserRoleTask, RemoveUserRoleTaskDisplayDriver>();
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
services.AddActivity<SelectUsersInRoleTask, SelectUsersInRoleTaskDisplayDriver>();
services.AddActivity<ValidateUserTask, ValidateUserTaskDisplayDriver>();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;

namespace OrchardCore.Users.Workflows.ViewModels
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
{
public class RemoveUserRoleTaskViewModel
{
[Required]
public string UserName { get; set; }

[Required]
public string RoleName { get; set; }
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;

namespace OrchardCore.Users.Workflows.ViewModels
{
public class SelectUsersInRoleTaskViewModel
{
[Required]
public string PropertyName { get; set; }
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved

[Required]
public string RoleName { get; set; }
MikeAlhayek marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading