Auto-Disable User Accounts?

Our company wants to implement a user account policy where the account automatically gets disabled after 3 months of inactivity.

Has anyone implemented something like this before? I was thinking about comparing the “Date Last Used” field in user account maintenance to the current date, but not sure how to go about getting a check to run on a regular basis, maybe a BPM?

You could just make the BAQ that looks up that data and set that to run into a BAQ report every morning. If you see users that need to be disabled, you will have to manually change them, but I am assuming you won’t be disabling users every day.

If you are really dealing with a huge company and hundreds of users, then you could do the same as above, and add a UBAQ to manage the BPM side. Add a few fields to a dashboard instead of a BAQ report. Then you could setup your dashboard so that you can click a user, and disable it with a button.

If you have functions, you could schedule that.

You could also do what @NateS said, but skip the dashboard and have the UBAQ BPM do the check and disabling automatically on the GetList.

2 Likes

So functions might be what we’re looking for, I tried setting a function up but I keep getting an error when updating the table.

Error 1

Exception executing library ‘MKF2022’ function ‘AutoDisableUserAct’:
System.NullReferenceException: Object reference not set to an instance of an object.
at Ice.Services.BO.UserFileSvc.KineticLayoutIdModified(UserFileRow biUserFileRow, UserFileRow userFileRow) in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.cs:line 1381
at Ice.Services.BO.UserFileSvc.IfUserHasAccessToDefaultLayout(String userId, UserFileRow biUserFileRow, UserFileRow userFileRow, Func4 hasSecurityAccessForLayout) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.cs:line 1346 at Ice.Services.BO.UserFileSvc.UserFileBeforeUpdate() in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.cs:line 1159 at Ice.Services.BO.UserFileSvc.OnRowEvent(DataTableEventType type, String tableName, IceRow row) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.Designer.cs:line 398 at Ice.TablesetBound3.<>c__DisplayClass128_1.b__2() in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 1280
at Ice.Services.Trace.TablesetProfilingCollector.DoRowEventTrace(String tableName, String methodName, Int32 rowCount, Action action) in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetProfilingCollector.cs:line 138
at Ice.TablesetBound3.UpdateRow(IceDataContext dataContext, Int32 tableNum, IIceTable table, IceRow updatedRow, IceRow originalRow, IColumnUncensor uncensor, TablesetProfilingCollector parentTraceCollector) in C:\_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 1273 at Ice.TablesetBound3.WriteTable(IceDataContext dataContext, Int32 tableIndex, IIceTable table, TablesetProfilingCollector parentTraceCollector) in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 980
at Ice.TablesetBound3.InnerUpdate[TUpdater](IceDataContext dataContext, TFullTableset tableset) in C:\_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 880 at Ice.Services.BO.UserFileSvc.Update(UserFileTableset& ds) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.Designer.cs:line 937 at Ice.Services.BO.UserFileSvcFacade.Update(UserFileTableset& ds) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFileSvcFacade.cs:line 1230 at EFx.MKF2022.Implementation.AutoDisableUserActImpl.<A003_InvokeBOMethodAction>b__8_0(UserFileSvcContract bo) at Epicor.Functions.FunctionBase3.CallService[TService](Action1 action) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Lib\Epicor.Functions.Core\FunctionBase.Plugins.cs:line 60 at EFx.MKF2022.Implementation.AutoDisableUserActImpl.A003_InvokeBOMethodAction() at EFx.MKF2022.Implementation.AutoDisableUserActImpl.RunStep(Int32 workflowStep) at Epicor.Functions.FunctionBase3.Run() in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Lib\Epicor.Functions.Core\FunctionBase.cs:line 90
at Epicor.Functions.FunctionBase3.Run(TInput input) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Lib\Epicor.Functions.Core\FunctionBase.cs:line 73 at EFx.MKF2022.Implementation.AutoDisableUserActImpl.AdapterRun(Object[] input) at Epicor.Functions.FunctionBase3.Epicor.Functions.IFunctionAdapter.Run(Object input) in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Lib\Epicor.Functions.Core\FunctionBase.Adapter.cs:line 18
at Ice.Internal.Task.ScheduledFunction.ExecuteFunction.RunFunction(IFunctionAdapter functionAdapter, Object parameters) in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Task\ScheduledFunction\ExecuteFunction.cs:line 128
at Ice.Internal.Task.ScheduledFunction.ExecuteFunction.RunProcess(Int64 instanceTaskNum, String outputFileName) in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Task\ScheduledFunction\ExecuteFunction.cs:line 57

Error 2

Program Ice.Services.Lib.RunTask raised an unexpected exception with the following message: RunTask:
System.NullReferenceException: Object reference not set to an instance of an object.
at Ice.Services.BO.UserFileSvc.KineticLayoutIdModified(UserFileRow biUserFileRow, UserFileRow userFileRow) in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.cs:line 1381
at Ice.Services.BO.UserFileSvc.IfUserHasAccessToDefaultLayout(String userId, UserFileRow biUserFileRow, UserFileRow userFileRow, Func4 hasSecurityAccessForLayout) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.cs:line 1346 at Ice.Services.BO.UserFileSvc.UserFileBeforeUpdate() in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.cs:line 1159 at Ice.Services.BO.UserFileSvc.OnRowEvent(DataTableEventType type, String tableName, IceRow row) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.Designer.cs:line 398 at Ice.TablesetBound3.<>c__DisplayClass128_1.b__2() in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 1280
at Ice.Services.Trace.TablesetProfilingCollector.DoRowEventTrace(String tableName, String methodName, Int32 rowCount, Action action) in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetProfilingCollector.cs:line 138
at Ice.TablesetBound3.UpdateRow(IceDataContext dataContext, Int32 tableNum, IIceTable table, IceRow updatedRow, IceRow originalRow, IColumnUncensor uncensor, TablesetProfilingCollector parentTraceCollector) in C:\_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 1273 at Ice.TablesetBound3.WriteTable(IceDataContext dataContext, Int32 tableIndex, IIceTable table, TablesetProfilingCollector parentTraceCollector) in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 980
at Ice.TablesetBound3.InnerUpdate[TUpdater](IceDataContext dataContext, TFullTableset tableset) in C:\_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Services\TablesetBound.cs:line 880 at Ice.Services.BO.UserFileSvc.Update(UserFileTableset& ds) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFile.Designer.cs:line 937 at Ice.Services.BO.UserFileSvcFacade.Update(UserFileTableset& ds) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Services\BO\UserFile\UserFileSvcFacade.cs:line 1230 at EFx.MKF2022.Implementation.AutoDisableUserActImpl.<A003_InvokeBOMethodAction>b__8_0(UserFileSvcContract bo) at Epicor.Functions.FunctionBase3.CallService[TService](Action1 action) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Lib\Epicor.Functions.Core\FunctionBase.Plugins.cs:line 60 at EFx.MKF2022.Implementation.AutoDisableUserActImpl.A003_InvokeBOMethodAction() at EFx.MKF2022.Implementation.AutoDisableUserActImpl.RunStep(Int32 workflowStep) at Epicor.Functions.FunctionBase3.Run() in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Lib\Epicor.Functions.Core\FunctionBase.cs:line 90
at Epicor.Functions.FunctionBase3.Run(TInput input) in C:\_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Lib\Epicor.Functions.Core\FunctionBase.cs:line 73 at EFx.MKF2022.Implementation.AutoDisableUserActImpl.AdapterRun(Object[] input) at Epicor.Functions.FunctionBase3.Epicor.Functions.IFunctionAdapter.Run(Object input) in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Lib\Epicor.Functions.Core\FunctionBase.Adapter.cs:line 18
at Ice.Internal.Task.ScheduledFunction.ExecuteFunction.RunFunction(IFunctionAdapter functionAdapter, Object parameters) in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Task\ScheduledFunction\ExecuteFunction.cs:line 128
at Ice.Internal.Task.ScheduledFunction.ExecuteFunction.RunProcess(Int64 instanceTaskNum, String outputFileName) in C:_Releases\ICE\RL10.2.600.0FW\Source\Server\Internal\Task\ScheduledFunction\ExecuteFunction.cs:line 71
at Ice.Core.TaskBase`1.StartProcess(Int64 instanceTaskNum, String outputFileName) in C:_Releases\ICE\UD10.2.600.30FW\Source\Server\Internal\Lib\TaskLib\TaskBase\TaskBase.cs:line 80
at Ice.Hosting.TaskCaller.InnerExecuteTask(IceDataContext newContext) in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Hosting\TaskCaller\TaskCaller.cs:line 112
at Ice.Hosting.TaskCaller.ExecuteTask() in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Hosting\TaskCaller\TaskCaller.cs:line 59
at Ice.Hosting.TaskCaller.ExecuteTask(IceDataContext dataContext, Boolean suppressTransaction) in C:_Releases\ICE\UD10.2.600.30FW\Source\Framework\Epicor.Ice\Hosting\TaskCaller\TaskCaller.cs:line 39
at Ice.Services.Lib.RunTaskSvc.<>c__DisplayClass29_2.b__1() in C:_Releases\ICE\UD10.2.600.30FW\Source\Server\Services\Lib\RunTask\RunTask.cs:line 450
at Ice.Lib.RunTask.BpmFriendlyTaskLauncher.Run(String sessionIdPrefix, IceContext db, Action taskRunner) in C:_Releases\ICE\UD10.2.600.30FW\Source\Server\Services\Lib\RunTask\BpmFriendlyTaskLauncher.cs:line 63
at Ice.Services.Lib.RunTaskSvc.InnerRunTask(Int64 ipTaskNum, Boolean suppressTransaction) in C:_Releases\ICE\UD10.2.600.30FW\Source\Server\Services\Lib\RunTask\RunTask.cs:line 447

This is my function layout, I’m only using Ice.UserFile directives:
image

GetRows Parameters:

Query Setup:

Table Mapping:

When I debug by sending myself the list via email (info messages don’t seem to work when scheduling the function) the list gets generated, but when I add in the Update method it errors out with the above error.

How is this function run? Does the user have permissions to modify the list?

In the function without the update, are there any null values? You might want to recreate the query in a baq and see if any of the data looks funky.

I’m running it via “Schedule Epicor Function”, I have Security Manager access and the 3 Function Developer group permissions, and I only have one field in the “Update table by Query” block

Running the same query in a BAQ doesn’t show any null values for UserID, I filtered for UserDisabled = false on the GetRows where clause. Then on the “Update table by Query” I set UserDisabled = true and RowMod = U only for rows that have a “LastDate” greater than 3 months ago.

Then that function is running in the context of the system agent, which won’t have permissions.

I think you will need a helper function to call this function with proper credentials.

Would you happen to have any examples of this? Does it require a custom code block?

I tried running it under the manager account but it doesn’t seem like this account can run or create epicor functions

EDIT: manager account can run and create, just had to add permissions

I would think it would require a custom code block.

I’ll explain the process, and if I have some time in a bit, I can see if I can work
up a skeleton.

You’d create two functions. One that does the work, and one to call the other with
custom credentials. I would restrict the runner one down to internal only, no db access etc.

In the runner function, you would call the work function via REST with credentials of a user
capable of making these changes. You might want to make a services type account for
this if you don’t have one already.

I think there might be a way to call the other function internally and use different creds,
but I’ll have to look. I think someone mentioned something like that.

1 Like

Okay here is the example, but I don’t think you’ll need it.

The scheduled functions I submitted ran under my user Account, so depending on use case,
you may not need this.

//Calling A Function From A Function Using Rest With Different Credentials

  string user = "Big Joe";
  string pass = "Password1";

  byte[] encoded_userPassBytes = System.Text.Encoding.UTF8.GetBytes($"{user}:{pass}");
  string encoded_userPass = System.Convert.ToBase64String(encoded_userPassBytes);

  string API_Key_Label = "x-api-key";
  string API_Key = "iLiKeBiGbUtTsAnDiCaNnOtLiE";
  
  string thisLibraryName = ThisLib.ToString().Split(".")[1];
  string functionToCall = "function-5";
  
  string serverUrl = $"{Session.AppServerURL}/api/v2/efx/{Session.CompanyID}/{thisLibraryName}/{functionToCall}";
  
  var client = new RestClient(serverUrl);
  
  string content = String.Empty;
  
  var request = new RestRequest();

    request.AddHeader("Accept", "*/*");
    request.AddHeader(API_Key_Label, API_Key);
    request.AddHeader("Authorization", $"Basic {encoded_userPass}");
    
    request.Method = Method.POST;
    
    request.AddParameter("application/json", content, ParameterType.RequestBody);
  
  var response = client.Execute(request);

I ran into this same exact issue. I was able to resolve it by calling UpdateExt instead of Update. I’ve got this code block to populate the UpdExtUserFileTableSet just prior to the UpdateExt Widget.

var ufr = new UserFileRow();
ufr.UserID = userFileListTs.UserFileList[0].UserID; 
//I run GetList prior to this to translate AD/SAM account passed into REST by our offboarding system to Epicor UserID.
//So there is a UserFileListTableSet with 1 row in it.
ufr.UserDisabled = true;
ufr.RowMod = "U";
userExtTS.UserFile.Add(ufr);
1 Like

I am having trouble getting this to work with the UpdateExt method, I think it is because the query is based on the GetRows method which returns a a tableset with type “UserFileTableset”, while the UpdateExt method requires a dataset with type “UpdExtUserFileTableset”.

You can probably get what you need from GetList, or a direct Db query (much faster because you are not returning the whole dataset.)

Then use a .Select() to map to the UserFile rows for UpdExtUserFileTableset

I was hoping I would be able to accomplish this using the widgets. We usually pass anything with C# code to our consultants.

Edit: I should probably clarify, if we can’t come up with some basic C# code, then we pass it to our consultants…

2 Likes

They’re basically the same thing. You can probably buffercopy the row from the UserFileTableset to the UpdExtUserFileTableset and it will work. If not, as I showed above, you just need the UserID. Then set the UserDisabled and RowMod fields and you’re done.

Okay, I’ve got it working without an error but it only updates the first row…is that because the custom code block doesn’t loop through the rest of the rows? I thought adding that first row basically “prepped” the table so that the UpdateExt could then run for all rows in the tableset.

I’d need to see your code to be sure, but you are you using a loop? Adding a row is just adding a row. The tableset is an object that exists to contain row objects. If you want to add more than one row object, you have to iterate through your list of users who need a row added.

Nope, looping in widgets can be done, but I wouldn’t recommend it.

2 Likes

It’s the same code you posted in August 2023:

var ufr = new UserFileRow();
ufr.UserID = userFileListTs.UserFileList[0].UserID; 
//I run GetList prior to this to translate AD/SAM account passed into REST by our offboarding system to Epicor UserID.
//So there is a UserFileListTableSet with 1 row in it.
ufr.UserDisabled = true;
ufr.RowMod = "U";
userExtTS.UserFile.Add(ufr);

These are my parameters for UpdateExt:

My snippet was meant to be used in a system that was only going to send one term request at a time. For multiple users, you gotta loop it. Something like this (note: I have not tested this).

foreach(var fileListRow in userFileListTs.UserFileList)
{
    var ufr = new UserFileRow();
    ufr.UserID = fileListRow.UserID; 
    ufr.UserDisabled = true;
    ufr.RowMod = "U";
    userExtTS.UserFile.Add(ufr);
}
2 Likes