You are viewing limited content. For full access, please sign in.

Question

Question

Synchronizing users between an AD forest or IdP and repository (Administration Console)

asked on May 19

I am running into an issue in which I need to pull users either from an AD Forest or from an IdP and then create those trustee's within Laserfiche's ecosystem. This has spurred up as with our security model we have designed, we are needing to implement the use of security tags to manage security within Laserfiche. This being the case, we have found that we need to synchronize users and groups from an AD and then respect their memberships.

 

Our environment is clustered with all Laserfiche components that support clustering through MSFC clustered via that and others load balanced via AWS. Each component is version 11, except for LFDS which is running version 12 for Graph API support.

 

We have tried different approaches using the LicenseManagerObjects.dll that is packaged with Laserfiche and pulling the users from LFDS works flawlessly, including respecting the memberships of groups defined in LFDS (manually created).

 

Within LFDS we have found that AD sync does NOT synchronize groups, rather only their members and respects licensing via their memberships. This poses an issue in which we can synchronize all of the users, and then respect memberships of groups defined in LFDS but NOT their actual AD groups which will be the group used when managing the security within Laserfiche.

 

I have attached the Workflow that we are currently using which has been able to manage the user side of the import.

 

namespace WorkflowActivity.Scripting.SDKScript
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Data.SqlClient;
    using System.Text;
    using Laserfiche.RepositoryAccess;
    using Laserfiche.LicenseManager.LMO;
    using System.Linq;


    /// <summary>
    /// Provides one or more methods that can be run when the workflow scripting activity is performed.
    /// </summary>
    public class Script1 : RAScriptClass110
    {
        /// <summary>
        /// This method is run when the activity is performed.
        /// </summary>
        protected override void Execute()
        {
            // Initialize the repository session
            string repositoryName = "";
            string username = "";
            string password = "";
            using (Session session = new Session())
            {
                try
                {

                    WorkflowApi.TrackInformation("Connecting to repository...");
                    // Connect to the repository
                    RepositoryRegistration repository = new RepositoryRegistration(repositoryName, repositoryName);
                    session.Connect(repository);
                    session.LogIn(username, password);
                    WorkflowApi.TrackInformation("Connected to repository.");

                    Laserfiche.RepositoryAccess.Server server = new Laserfiche.RepositoryAccess.Server(session);

                    WorkflowApi.TrackInformation("Server connected.");

                    // Get the repository

                    Repository repo = server.GetRepository(repository);
                    WorkflowApi.TrackInformation("Repository: " + repo.RepositoryPath);

                    string serverName = "";
                    string realmName = "";
                    bool useSSL = true;

                    Laserfiche.LicenseManager.LMO.Server lmoServer = Laserfiche.LicenseManager.LMO.Server.Connect(serverName, useSSL);
                    WorkflowApi.TrackInformation("Connected to LMO server.");
                    // Get the license information
                    Database lmoDatabase = lmoServer.GetDatabaseByRealmName(realmName);
                    WorkflowApi.TrackInformation("Database: " + lmoDatabase.Name);

                    SessionTokenParameters sessionTokenParameters = new SessionTokenParameters();
                    lmoDatabase.Login(sessionTokenParameters);
                    WorkflowApi.TrackInformation("Logged in to LMO database.");

                    List<User> allUsers = lmoDatabase.GetAllUsersEx(0, 0);
                    WorkflowApi.TrackInformation("Total users: " + allUsers.Count.ToString());
                    List<Group> allGroups = lmoDatabase.GetAllGroups();
                    WorkflowApi.TrackInformation("Total groups: " + allGroups.Count.ToString());

                    // Create users and groups in the repository
                    CreateRepositoryObjects(allUsers, allGroups, RASession);
                }
                catch (Exception e)
                {
                    WorkflowApi.TrackInformation("Fatal failure: " + e.ToString());
                    //session.Close();
                }

                //session.Close();
            }
        }

        private void CreateRepositoryObjects(List<User> allUsers, List<Group> allGroups, Session session)
        {
            //WorkflowApi.TrackInformation("Creating " + allUsers.Count + " users in the repository...");
            foreach (var user in allUsers)
            {
                try
                {
                    Dictionary<String, TrusteeInfo> winTrustees = new Dictionary<String, TrusteeInfo>();
                    // Determine account type and create user accordingly
                    if (user is Laserfiche.LicenseManager.LMO.ActiveDirectoryUser) // Likely a Windows account
                    {
                        // create the windows account
                        TrusteeInfo trustee = new TrusteeInfo();

                        // set the feature rights and set it to read only
                        trustee.FeatureRights = FeatureRights.Scan | FeatureRights.Search | FeatureRights.Print | FeatureRights.Export;
                        Trustee.SetInfo(user.Name, trustee, session);

                        // grant logon access to the windows account (Trust)
                        AccountReference ar = new AccountReference(user.Name, session);
                        Repository.GrantLogOnAccess(ar, session);

                        // add to win trustees
                        winTrustees.Add(string.Join(" ", user.DisplayName.ToLower().Split(' ')), trustee);
                    }
                    if (user is Laserfiche.LicenseManager.LMO.SamlUser)
                    {
                        // Create Laserfiche Directory Account
                        // create the laserfiche directory account
                        TrusteeInfo trustee = new TrusteeInfo();
                        trustee.FeatureRights = FeatureRights.Scan | FeatureRights.Search | FeatureRights.Print | FeatureRights.Export;
                        Trustee.SetInfo(user.Name, trustee, session);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Failed to create user " + user.Name + ": " + ex.Message);
                }
            }

            //WorkflowApi.TrackInformation("Creating " + allGroups.Count + " groups in the repository...");
            foreach (Group grp in allGroups)
            {
                try
                {
                    // Create Laserfiche Directory Account NOT Laserfiche "Repository Account"
                    WorkflowApi.TrackInformation(grp.Name);
                    // create the laserfiche directory account
                    TrusteeInfo trustee = new TrusteeInfo();
                    trustee.FeatureRights = FeatureRights.Scan | FeatureRights.Search | FeatureRights.Print | FeatureRights.Export;
                    Trustee.SetInfo(grp.Name, trustee, session);
                    //WorkflowApi.TrackInformation(trustee.LfdsName);
                    // grant logon access to the laserfiche directory account (Trust)

                    AccountReference ar = new AccountReference(grp.Name, session);
                    Repository.GrantLogOnAccess(ar, session);
                    // verify user created
                    WorkflowApi.TrackInformation("Created group: " + grp.Name);
                }
                catch (Exception ex)
                {
                    WorkflowApi.TrackInformation("Failed to create group " + grp.Name + ": " + ex.Message);
                }
            }

            WorkflowApi.TrackInformation("Assigning users to groups explicitly...");
            Dictionary<string, List<Group>> allUserGroups = new Dictionary<string, List<Group>>();

            // Build user-to-group mapping
            foreach (var group in allGroups)
            {
                var groupMembers = group.GetMembers();
                foreach (var trusteeSID in groupMembers.Keys)
                {
                    if (!allUserGroups.ContainsKey(trusteeSID))
                        allUserGroups[trusteeSID] = new List<Group>();

                    allUserGroups[trusteeSID].Add(group);
                }
            }

            // Assign users to their respective groups
            foreach (var user in allUsers)
            {
                if (!allUserGroups.ContainsKey(user.SIDString))
                {
                    WorkflowApi.TrackInformation("User " + user.Name + " does not belong to any group.");
                    continue;
                }
                WorkflowApi.TrackInformation("Assigning " + user.Name + "to groups...");

                foreach (var group in allUserGroups[user.SIDString])
                {
                    WorkflowApi.TrackInformation("Adding " + user.Name + " to group " + group.Name + "...");
                    try
                    {
                        Dictionary<string, string> currentMembers = group.GetMembers();
                        List<string> newMembers = currentMembers.Keys.ToList();

                        if (!newMembers.Contains(user.SIDString))
                        {
                            WorkflowApi.TrackInformation("Adding " + user.Name + " to " + group.Name);
                            newMembers.Add(user.SIDString);
                            group.SetMembers(newMembers);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Failed to add user " + user.Name + " to group " + group.Name + ": " + ex.Message);
                    }
                }
            }
        }
    }
}

 

Has anyone ever attempted something similar to this, and if so what were the results and do you have guidance on achieving this?

0 0

Answer

SELECTED ANSWER
replied on July 29

Hello all,

 

Just now circling back to this Answers article and wanted to report success with getting this workflow/idea to work. In the original Workflow I only leveraged the LMO library which allowed me to synchronize the members that LFDS was seeing, but in testing I found that LFDS only really syncs the users of groups, not groups themselves. I opted to go the same route as the most recent LFDS version 12.0 and integrate my Workflow with Microsoft Graph API to pull information directly from the IdP. After setting up references I wound up with this Workflow to pull users and groups from what LFDS sees and then what groups are available on the IdP.

 

namespace WorkflowActivity.Scripting.SDKScript
{
    using Laserfiche.LicenseManager.LMO;
    using Laserfiche.RepositoryAccess;
    using LFSO82Lib;
    using Microsoft.Graph;
    using Microsoft.Identity.Client;
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Net.Http.Headers;
    using System.Text;

    /// <summary>
    /// Provides one or more methods that can be run when the workflow scripting activity is performed.
    /// </summary>
    public class Script1 : RAScriptClass110
    {
        protected override void Execute()
        {
            // Initialize the repository session
            string repositoryName = GetTokenValue("repository_name").ToString();
            using (Session session = RASession)
            {
                try
                {
                    WorkflowApi.TrackInformation("Connecting to repository...");
                    // Connect to the repository
                    RepositoryRegistration repository = new RepositoryRegistration(GetTokenValue("server_name").ToString(), repositoryName);
                    //                    session.Connect(repository);
                    //                    session.LogIn(username, password)
                    WorkflowApi.TrackInformation("Connected to repository.");

                    Laserfiche.RepositoryAccess.Server server = new Laserfiche.RepositoryAccess.Server(session);

                    WorkflowApi.TrackInformation("Server connected.");

                    // Get the repository
                    //Repository repo = server.GetRepository(repository);
                    //WorkflowApi.TrackInformation("Repository: " + repo.RepositoryPath);

                    string serverName = GetTokenValue("server_name").ToString();
                    string realmName = GetTokenValue("realm_name").ToString();
                    bool useSSL = true;

                    Laserfiche.LicenseManager.LMO.Server lmoServer = Laserfiche.LicenseManager.LMO.Server.Connect(serverName, useSSL);
                    WorkflowApi.TrackInformation("Connected to LMO server.");
                    // Get the license information
                    Database lmoDatabase = lmoServer.GetDatabaseByRealmName(realmName);
                    WorkflowApi.TrackInformation("Database: " + lmoDatabase.Name);

                    SessionTokenParameters sessionTokenParameters = new SessionTokenParameters();
                    sessionTokenParameters.Username = GetTokenValue("username").ToString();
                    sessionTokenParameters.Password = GetTokenValue("password").ToString();
                    lmoDatabase.Login(sessionTokenParameters);
                    WorkflowApi.TrackInformation("Logged in to LMO database.");

                    List<Laserfiche.LicenseManager.LMO.User> allUsers = lmoDatabase.GetAllUsersEx(0, 0);
                    WorkflowApi.TrackInformation("Total users from LFDS: " + allUsers.Count.ToString());
                    List<Laserfiche.LicenseManager.LMO.Group> allGroups = lmoDatabase.GetAllGroups();
                    WorkflowApi.TrackInformation("Total groups from LFDS: " + allGroups.Count.ToString());

                    // Create users and assign them to groups (LFDS)
                    CreateRepositoryObjects(allUsers, allGroups, session);
                    // Users from AD exist now, but groups do not exist yet
                    // Entra ID groups are not created in LFDS, so we need to create them manually
                    // Create groups and assign them to users (EN)
                    WorkflowApi.TrackInformation("Done with LFDS groups & users...");
                    // Get groups and members from Entra ID
                    var adGroups = GetAllGroups();

                    // Map these groups and users to your repository creation logic
                    foreach (var group in adGroups)
                    {
                        WorkflowApi.TrackInformation("Processing: " + group);
                        // Use domain-qualified group name for AD groups
                        string domainQualifiedGroup = GetTokenValue("domain") + "\\" + group;

                        // Check if the group already exists in LFDS
                        var lfGroup = allGroups.FirstOrDefault(g => g.Name.Equals(domainQualifiedGroup, StringComparison.OrdinalIgnoreCase));
                        if (lfGroup == null)
                        {
                            WorkflowApi.TrackInformation("Attempting to create group from AD: " + domainQualifiedGroup);
                            try
                            {
                                TrusteeInfo trustee = new TrusteeInfo();
                                Trustee.SetInfo(domainQualifiedGroup, trustee, session);

                                AccountReference ar = new AccountReference(domainQualifiedGroup, session);
                                Repository.GrantLogOnAccess(ar, session);
                            }
                            catch (Exception ex)
                            {
                                WorkflowApi.TrackInformation("Failed to create group " + domainQualifiedGroup + ": " + ex.Message);
                            }
                        }
                        else
                        {
                            WorkflowApi.TrackInformation("AD group already exists under LFDS: " + domainQualifiedGroup);
                        }
                    }

                }
                catch (Exception e)
                {
                    WorkflowApi.TrackInformation("Fatal failure: " + e.ToString());
                    session.Close();
                }

                session.Close();
            }
        }

        // Replace your GetGraphClient() method with the following:

        private GraphServiceClient GetGraphClient()
        {
            var clientId = GetTokenValue("client_id").ToString();
            var tenantId = GetTokenValue("tenant_id").ToString();
            var clientSecret = GetTokenValue("client_secret").ToString();

            var confidentialClient = ConfidentialClientApplicationBuilder
                .Create(clientId)
                .WithTenantId(tenantId)
                .WithClientSecret(clientSecret)
                .Build();

            // Use synchronous token acquisition for compatibility with Workflow scripts
            var authProvider = new DelegateAuthenticationProvider((requestMessage) =>
            {
                var result = confidentialClient
                    .AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
                    .ExecuteAsync()
                    .GetAwaiter()
                    .GetResult();

                requestMessage.Headers.Authorization =
                    new AuthenticationHeaderValue("Bearer", result.AccessToken);

                return System.Threading.Tasks.Task.CompletedTask;
            });

            return new GraphServiceClient(authProvider);
        }

        public List<string> GetAllGroups()
        {
            var graphClient = GetGraphClient();
            var groups = new List<string>();

            // Start with the first page, requesting up to 999 groups per page
            var groupPage = graphClient.Groups
                .Request()
                .Select("id,displayName")
                .Top(999)
                .GetAsync().Result;
            WorkflowApi.TrackInformation("Processing groups: " + groupPage.Count.ToString());
            do
            {
                foreach (Microsoft.Graph.Group group in groupPage.CurrentPage)
                {
                    groups.Add(group.DisplayName);
                }

                groupPage = groupPage.NextPageRequest != null
                    ? groupPage.NextPageRequest.GetAsync().Result
                    : null;
            } while (groupPage != null);
            WorkflowApi.TrackInformation("Processing ad groups finished...");
            return groups;
        }

        private void CreateRepositoryObjects(List<Laserfiche.LicenseManager.LMO.User> allUsers, List<Laserfiche.LicenseManager.LMO.Group> allGroups, Session session)
        {
            foreach (var user in allUsers)
            {
                try
                {
                    Dictionary<String, TrusteeInfo> winTrustees = new Dictionary<String, TrusteeInfo>();
                    if (user is Laserfiche.LicenseManager.LMO.ActiveDirectoryUser)
                    {
                        TrusteeInfo trustee = new TrusteeInfo();
                        Trustee.SetInfo(user.Name, trustee, session);
                        AccountReference ar = new AccountReference(user.Name, session);
                        winTrustees.Add(string.Join(" ", user.DisplayName.ToLower().Split(' ')), trustee);
                    }
                    if (user is Laserfiche.LicenseManager.LMO.SamlUser)
                    {
                        TrusteeInfo trustee = new TrusteeInfo();
                        Trustee.SetInfo(user.Name, trustee, session);
                    }
                }
                catch (Exception ex)
                {
                    WorkflowApi.TrackInformation("Failed to create user " + user.Name + ": " + ex.Message);
                }
            }

            foreach (var group in allGroups)
            {
                try
                {
                    // Create Laserfiche Directory Account NOT Laserfiche "Repository Account"
                    //WorkflowApi.TrackInformation("Creating group: " + group.Name);
                    // create the laserfiche directory account
                    TrusteeInfo trustee = new TrusteeInfo();
                    //trustee.FeatureRights = FeatureRights.Scan | FeatureRights.Search | FeatureRights.Print | FeatureRights.Export;
                    Trustee.SetInfo(group.Name, trustee, session);
                    // grant logon access to the laserfiche directory account (Trust)
                    AccountReference ar = new AccountReference(group.Name, session);
                    //Repository.GrantLogOnAccess(ar, session);
                    // verify user created
                    //WorkflowApi.TrackInformation("Created group: " + group.Name);
                }
                catch (Exception ex)
                {
                    //WorkflowApi.TrackInformation("Failed to create group " + group.Name + ": " + ex.Message);
                }
            }
        }
    }
}

 

0 0

Replies

You are not allowed to reply in this post.
You are not allowed to follow up in this post.

Sign in to reply to this post.