Company Change Approaches on the Server

Bart awesome!

Let me know if you try this in a BPM =) post a boilerplate code

Is there anyway to temporary use a different UserID? We have a case where we would like to go to a Parent Company and toggle a field, but we don’t want the Users to have access to that Parent Company. I plan to use a “Service Account User” for the action.

Could I do something like:
CallContext.TemporarySessionCreator.SetCompanyID(CompanyID).SetPlantID(PlantID).SetUserID(UserID).SetEmployeeID(EmployeeID).Create()

2nd Question: What is the difference between creating a TemporarySession vs using Session.SetCompany(“ABC”)

Haso,

I wonder if this wouldn’t be a good use of a REST call? You would use just a little time to log in via your Service Account User, make the update, confirm it, and exit quickly. It would be out-of-band of the current security context and keep things nice and clean.

Mark W.

Definitely it would be, just for the time being looking for a solution until we enable REST and test it and so on. It involves Infrastructure Team to allow it and more.

I can try to find out notes on why userid change was a no no. Or at least a HUGE impact on perf since you basically have to stand up a new Operation (e.g. Server Call). But in general, you need to make another server call so didn’t bother in the few scenarios we had - just use the alternate credentials from the client.

A Temp session lasts the life of the server call or shorter - ideally the span on what inside a for loop as you iterate different companies, plants, etc.
foreach(var company in companyList)
{
using (CallContext.TemporarySessionCreator.SetCompanyID(company).Create())
{
DoSomething(company);
}
}

Calling Session.SetCompany changes the default Company forever - or until the users clicks on a new Company, etc. If you think about ‘sagas’ in the transaction world you’ll get a hint of what I mean. Session is the breadcrumb trail of all those little context settings about current company, plant, site, workstation, currency, language, etc etc.
Temp does a ‘temporary’ hijacking of that saga to make a quick change in context of something else. Think Admin cleanup type functions where you are iterating over all the companies in a group.

2 Likes

Works without a problem for me, thus far I haven’t discovered any issues.

BPM Sample Code (Toggling between 3 Child Companies in foreach loop, using it on 2 Methods, fast):

using (CallContext.Current.TemporarySessionCreator.SetCompanyID(childCompanyID).Create())
{
	using (var PartService = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.PartSvcContract>(Db))
	{
		PartTableset PartDataSet = new PartTableset();
		PartDataSet = PartService.GetByID(ipPartNum);
		PartService.GetNewPartRev(ref PartDataSet, ipPartNum, "");
		var ttPartRevRow = PartDataSet.PartRev.Where(r => r.Added()).FirstOrDefault();
		ttPartRevRow["PartNum"] = ParentPartRev.PartNum;
		ttPartRevRow["RevisionNum"] = ParentPartRev.RevisionNum;
		ttPartRevRow["RevDescription"] = ParentPartRev.RevDescription;
		ttPartRevRow["RevShortDesc"] = ParentPartRev.RevShortDesc;
		ttPartRevRow["EffectiveDate"] = ParentPartRev.EffectiveDate;
		ttPartRevRow["smDrawRev_c"] = ParentPartRev.smDrawRev_c;
		ttPartRevRow["smECOGroupId_c"] = ParentPartRev.smECOGroupID_c;
		ttPartRevRow["DrawNum"] = ParentPartRev.DrawNum;
		ttPartRevRow["Plant"] = DefaultSite;
		PartService.Update(ref PartDataSet);
	}
}

Several 100 lines of code later, another one.

using (CallContext.Current.TemporarySessionCreator.SetCompanyID(childCompanyID).Create())
{
	using (var EngWkBenchService = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.EngWorkBenchSvcContract>(Db))
	{
		EngWorkBenchTableset EngWorkBenchDataSet = new EngWorkBenchTableset();
		EngWkBenchService.GetNewECOGroup(ref EngWorkBenchDataSet);
		var ttECOGroupRow = EngWorkBenchDataSet.ECOGroup.Where(r => r.Added()).FirstOrDefault();
		ttECOGroupRow["GroupID"] = ECOGroupInParentCompany.GroupID;
		ttECOGroupRow["Description"] = ECOGroupInParentCompany.Description;
		ttECOGroupRow["EffectiveDate"] =ECOGroupInParentCompany.EffectiveDate;
		ttECOGroupRow["CreatedBy"] = ECOGroupInParentCompany.CreatedBy;
		ttECOGroupRow["smECOGroupID_c"] = ECOGroupInParentCompany.smECOGroupID_c;
		ttECOGroupRow["TaskSetID"] = ECOGroupInParentCompany.TaskSetID;
		ttECOGroupRow["WFGroupID"] = ECOGroupInParentCompany.WFGroupID;
		EngWkBenchService.Update(ref EngWorkBenchDataSet);
	}
}
4 Likes

UPDATE: We have been using this now in Production for about 1 month. It is a Sub-Process to a bigger more complex Service Connect process. We processed an XML that took 61 hours to complete (BOMs, Parts, ECOs) the code was hammered for hours and hours. It worked flawless, fast and without any exceptions.

4 Likes

Is it possible to use this code in form customization? So, how to get data from another company if we are using adapter from client customization?

No, this code cannot be used in a customization

No, this is for server side iterating sessions.

On the client side you just set a variable on the Session Object and it does the magic for you.

1 Like

Thanks @josecgomez and @Bart_Elia for your answers.

@Bart_Elia could you elaborate more in this “set a variable on the Session Object” in client side?

Damn, it’s that easy?I assumed that variable was read only this whole time.

LOL nope, client side Core.Session is magically (And not thread safe cough)

@Bart_Elia Quick Question:

Let’s say I have a Cross-Company BAQ and the User does not have access to ABC Company. Is there a safe way to Impersonate the Adapter to execute as a different user.

This here works, a little slow - just poking around; but not sure what the consequences are and if there is a safer / recommended way to just execute this BAQ as a diff user (Service Account).

There is also a Session Clone Method so I could Clone this.oTrans.Session and pass in a cloned/modified one to the Adapter so the original this.oTrans is untouched, and use MyClonedSession.SetUser(“ServiceAccount”, “pw”);

The Dynamic BO also has some code that does:

queryRequest.Session["ImpersonatedUserName"] = this.Session.UserID; // Perhaps one can use this?

What about doing an “Updatable” BAQ and in the GetList() post-processing go fetch the stuff you need using the DB. object. This doesn’t require a new session.

Pain is that its a complex BAQ that has PIVOT, Joins, Sub-Queries etc… and it Crosses Companies for certain Data - its a mixture, but if the User doesn’t have access to ABC Company, the result is Empty.

Only other thing I can think of is use the DynamicQuery BO Instead and create a New Session in a using block.

Does this need to be for any user or only certain scenarios?

Why I am asking is there is the ‘On Behalf Of’ impersonation for delegated execution by a service account on behalf of other users. For example, the Task agent ‘user’ with impersonation rights. This allows for a service account running tasks to generate a report on behalf of any user. It’s also used by ECC and a few other server to server scenarios.

Well the users executing this one would be regular Employees with no access to Corporate Company and If I could execute it as a Service Account (Fixed) that would be good, heck perhaps even use the Print Account since that one has access to all Co’s. It’s not On Behalf Of, but rather “As User” that I am looking for.

Similar to the BAQ Version:

**CallContext.TemporarySessionCreator.SetUserID("CyberBob").Create()**

Looking for a way to force a User OR have the Cross-Company BAQ ignore the User’s Company Access and force it to include a specific company, no matter what in its query. Perhaps a Temp Session in the UI?

That level of full User spoofing is not something we have due to the security concerns - that’s a pretty quick slope if you can change users with a single line of code and have no rights delegation visible that an admin can control.

1 Like