Streamlining Content Hub User Management with the RemoveAdminUsers Script


As organizations grow and evolve, managing user access to digital assets becomes increasingly complex. In order to ensure data integrity and maintain security, it's crucial to keep track of which users have admin privileges and regulate access accordingly. To assist with this task, we've developed a powerful script called RemoveAdminUsers, designed to manage admin user access in Content Hub. In this blog post, we'll delve into the technical aspects of the RemoveAdminUsers script, discuss its functionality, and provide a comprehensive guide on how to use it.


Overview of the RemoveAdminUsers Script

The RemoveAdminUsers script is a versatile tool that helps organizations manage admin users in Content Hub more efficiently. The script offers several key features:

  • Disables admin users instead of removing them, ensuring a smooth audit process.
  • Generates a report for better management of admin users.
  • Streamlines the process of disabling admin users.

Here's a step-by-step breakdown of the RemoveAdminUsers script and how it works.

Step 1: Reading Email Addresses from a CSV File

The script starts by reading a list of email addresses from a CSV file, which serves as the input for determining which admin users should be disabled. This can easily be customized according to your organization's needs.


Step 2: Retrieving User Information Asynchronously

Next, the script asynchronously retrieves user information from Content Hub. It queries user profiles and processes them in parallel, which significantly speeds up the task. During this step, the script logs information about each user fetched, allowing you to monitor its progress.


Step 3: Disabling Admin Users

Once the user information has been fetched, the script proceeds to disable the admin users whose email addresses match those in the input CSV file. It's important to note that system admin users (such as certain Sitecore accounts) will not be disabled, ensuring that core system functionalities remain intact.


Step 4: Generating Reports

After disabling the relevant admin users, the script generates a report containing users that could not be disabled because they are system admin users. This list helps you keep track of any special accounts that require manual intervention.


using Dasync.Collections;
using ManyConsole;
using Microsoft.Extensions.Logging;
using Sitecore.CH.Base.Services;
using Stylelabs.M.Base.Querying;
using Stylelabs.M.Framework.Essentials.LoadConfigurations;
using Stylelabs.M.Framework.Essentials.LoadOptions;
using Stylelabs.M.Sdk.Contracts.Base;
using Stylelabs.M.Sdk.Contracts.Querying;
using Stylelabs.M.Sdk.WebClient;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using static Sitecore.CH.Implementation.CommandLine.Commands.SendEmailToAllUsers;
using static Sitecore.CH.Implementation.Constants.User;

namespace Sitecore.CH.Implementation.CommandLine.Commands
{
public class RemoveAdminUsers : ConsoleCommand
{
private readonly IWebMClient _client;
private readonly ILogger<RemoveAdminUsers> _logger;
private const int Success = 0;
private const string UserToUserProfile = "UserToUserProfile";
private const string UserNameValidCharactersPattern = "^[a-zA-Z0-9]*$";

private const string CsvFilePath = @"d:\\temp\\\prod\\q1_prod_-SuperUsersToValidate_Reviewed_26042023.csv";
private const string failedUsersCsvFilePath = @"d:\\temp\\prod\\failed_users.csv";
private const string reasonForRestriction = "User account has been temporarily disabled for auditing purposes";

public RemoveAdminUsers(IMClientFactory mClientFactory, ILogger<RemoveAdminUsers> logger)
{
IsCommand("RemoveAdminUsers", "Remove Users By Email");
this._client = mClientFactory.Client;
this._logger = logger;
}

public override int Run(string[] remainingArguments)
{
ExecuteAsync().Wait();
return Success;
}

private async Task ExecuteAsync()
{
IEnumerable<string> emailsToRemove = ReadEmailsFromCsv(CsvFilePath);
IEnumerable<UserInfo> users = await GetUserInfoAsync();
await RemoveAdminUsersAsync(users, emailsToRemove);
}

private IEnumerable<string> ReadEmailsFromCsv(string csvFilePath)
{
List<string> emails = new List<string>();

using (var reader = new StreamReader(csvFilePath))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');

if (values.Length > 1)
{
emails.Add(values[1].Trim());
}
}
}

return emails;
}

private async Task RemoveAdminUsersAsync(IEnumerable<UserInfo> users, IEnumerable<string> emailsToRemove)
{
var usersToRemove = users.Where(user => emailsToRemove.Contains(user.Email));
var failedUsers = new List<FailedUser>();

foreach (var user in usersToRemove)
{
try
{
IEntity userAccount = await _client.Users.GetUserAsync(user.UserName, new EntityLoadConfiguration
{
PropertyLoadOption = new PropertyLoadOption(Constants.User.Properties.IsRestricted, Constants.User.Properties.ReasonForRestriction)
});
                    //Make sure at least someone can still access it!
if (user.UserName != "MAIN_ADMIN_ACCOUNT")
{
userAccount.SetPropertyValue(Constants.User.Properties.IsRestricted, true);
userAccount.SetPropertyValue(Constants.User.Properties.ReasonForRestriction, reasonForRestriction);

await _client.Entities.SaveAsync(userAccount);
_logger.LogInformation($"User {user.UserName} - ({user.Email}) has been disabled.");
}
}
catch (Exception ex)
{
_logger.LogError($"Error removing user {user.UserName} ({user.Email}): {ex.Message}");
failedUsers.Add(new FailedUser { UserName = user.UserName, Email = user.Email, ErrorMessage = ex.Message });
}
}

// Write failed users to CSV file
if (failedUsers.Any())
{
using (var writer = new StreamWriter(failedUsersCsvFilePath))
{
writer.WriteLine("UserName,Email,ErrorMessage");

foreach (var user in failedUsers)
{
writer.WriteLine($"{user.UserName},{user.Email},{user.ErrorMessage}");
}
}
}
}

private async Task<IEnumerable<UserInfo>> GetUserInfoAsync()
{
Query userProfilesQuery = Query.CreateQuery(q =>
q.Where(e => e.DefinitionName == Constants.UserProfile.DefinitionName)
.OrderBy(e => e.CreatedOn).Take(2000));

IEntityQueryResult userProfilesQueryResult = await _client.Querying.QueryAsync(userProfilesQuery, new EntityLoadConfiguration
{
PropertyLoadOption = new PropertyLoadOption(Constants.UserProfile.Username, Constants.UserProfile.Email),
RelationLoadOption = new RelationLoadOption(UserToUserProfile)
});

ConcurrentBag<UserInfo> userInfoItems = new ConcurrentBag<UserInfo>();
int counter = 1;

// Process the user profiles asynchronously
await userProfilesQueryResult.Items.ParallelForEachAsync(entity =>
{
try
{
UserInfo userInfo = new UserInfo();
userInfo.UserName = entity.GetPropertyValue<string>(Constants.UserProfile.Username);
userInfo.Email = entity.GetPropertyValue<string>(Constants.UserProfile.Email);
userInfo.Id = entity.GetRelation<IChildToOneParentRelation>(UserToUserProfile).Parent.Value;

if (!string.IsNullOrWhiteSpace(userInfo.UserName) && !string.IsNullOrWhiteSpace(userInfo.Email))
{
userInfoItems.Add(userInfo);
_logger.LogInformation($"{Interlocked.Increment(ref counter)} - Fetching {userInfo.UserName} {userInfo.Email}");
}
}
catch (Exception ex)
{
_logger.LogError($"{Interlocked.Increment(ref counter)} - Error {ex.Message}");
}

return Task.CompletedTask;

}, maxDegreeOfParallelism: 1);

return userInfoItems;
}

public class FailedUser
{
public string UserName { get; set; }
public string Email { get; set; }
public string ErrorMessage { get; set; }
}
}
}


Conclusion:

The RemoveAdminUsers script is an invaluable tool for organizations that rely on Content Hub to manage digital assets. By automating the process of disabling admin users and generating insightful reports, the script simplifies user management and enhances overall security. We encourage you to leverage the power of RemoveAdminUsers to streamline your organization's user management processes and maintain a secure Content Hub environment.

Comments