Customizing User Management

Topics: Customizing Orchard
Apr 13, 2015 at 7:28 PM
Edited Apr 13, 2015 at 7:29 PM
I've created a separate admin area for managing users that will allow users with the ManageTenantUsers permission to manage users. I basically just copied the admin controller from the users module. This new area allows me to remove administrators from the list of users to manage and it removes the ability to assign the administrator role to a user. This way our site admins can manage their users without being able to see our accounts or create users with the administrator permission. I've run into a couple problems in trying to work around the UserRolesPart in a way that will allow me to hide the administrator role from the users. What I ended up doing was remove the editor for the UserRolesPart from the editor being built in the admin controller and then added in my own editor for the UserRolesPart.
public ActionResult Create()
        {
            if (!Services.Authorizer.Authorize(Permissions.ManageTenantUsers, T("Not authorized to manage users")))
                return new HttpUnauthorizedResult();

            var user = Services.ContentManager.New<IUser>("User");
            var editor = Shape.EditorTemplate(TemplateName: "Parts/User.Create", Model: new UserCreateViewModel(), Prefix: null);            

            var roles = _roleService.GetRoles().Where(x => !x.Name.Contains("Admin")).Where(x => !x.Name.Contains("admin")).Select(x => new UserRoleEntry //New: create a collection of the current system roles using the UserRoleEntry ViewModel (removing admin accounts)
            {
                RoleId = x.Id,
                Name = x.Name,
                Granted = user.ContentItem.Get<UserRolesPart>().Roles.Contains(x.Name)
            });

            var rolesModel = new UserRolesViewModel //New: set up the ViewModel to be passed to the user roles editor
            {
                User = user,
                UserRoles = user.ContentItem.Get<UserRolesPart>(),
                Roles = roles.ToList(),
            };

            var roleEditor = Shape.EditorTemplate(TemplateName: "Parts/User.Roles", Model: rolesModel, Prefix: null); //New: create the editor for the user roles
            
            editor.Metadata.Position = "2";
            var model = Services.ContentManager.BuildEditor(user);

            model.Content.Items.RemoveAt(0); //New: remove the current editor for user roles
            
            model.Content.Add(editor);

            model.Content.Add(roleEditor); //New: add the new editor for the user roles to the model

            return View(model);
        }
This seems to be working as when I get to the action that handles the post I can add a UserRolesViewModel parameter to the action and it contains the collection of UserRoleEntry updated to whatever the user selected in the editor (selected roles get their Granted bool set to true).

The problem is how to actually add the newly granted roles to the user. I tried copying the method used in the UserRolesPart driver which gets the user roles repository and adds entries for the roles to be added to the user. This did add the roles to the user but when the action reached this line of code it removed any roles that had been added.
var model = Services.ContentManager.UpdateEditor(user, this);
The new roles get removed because this line tries to update the UserRolesPart with this action. This action doesn't actually update the model in the UserRolesPart driver, which means the list of roles is empty so the driver removes all the roles I just added.
protected override DriverResult Editor(UserRolesPart userRolesPart, IUpdateModel updater, dynamic shapeHelper) {
            // don't apply editor without apply roles permission
            if (!_authorizationService.TryCheckAccess(Permissions.AssignRoles, _authenticationService.GetAuthenticatedUser(), userRolesPart))
                return null;

            var model = BuildEditorViewModel(userRolesPart);
            if (updater.TryUpdateModel(model, Prefix, null, null)) { //This doesn't get updated!!!
                var currentUserRoleRecords = _userRolesRepository.Fetch(x => x.UserId == model.User.Id);
                var currentRoleRecords = currentUserRoleRecords.Select(x => x.Role);
                var targetRoleRecords = model.Roles.Where(x => x.Granted).Select(x => _roleService.GetRole(x.RoleId));
                foreach (var addingRole in targetRoleRecords.Where(x => !currentRoleRecords.Contains(x))) {
                    _notifier.Warning(T("Adding role {0} to user {1}", addingRole.Name, userRolesPart.As<IUser>().UserName));
                    _userRolesRepository.Create(new UserRolesPartRecord { UserId = model.User.Id, Role = addingRole });
                }
                foreach (var removingRole in currentUserRoleRecords.Where(x => !targetRoleRecords.Contains(x.Role))) {
                    _notifier.Warning(T("Removing role {0} from user {1}", removingRole.Role.Name, userRolesPart.As<IUser>().UserName));
                    _userRolesRepository.Delete(removingRole);
                }
            }
            return ContentShape("Parts_Roles_UserRoles_Edit",
                                () => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix));
        }
Is there a way to somehow change the CreatePost action to include the updated UserRolesViewModel I passed into it so it will update that when the UserRolesPart driver attempts to update its model using the CreatePost action?

If this can't be done I was thinking about removing the line of code in my controller that calls the UpdateEditor method on the user and the action. That way it won't try to update and thus won't remove the roles I just added. This option may cause problems down the road (I don't know) so I was trying to avoid it. Thank you.