RestClient and SSO, is it supposed to work?

Because it’s not (working)… Customer is on Epicor SaaS, I have an Epicor Function that I am calling from a client customization with the help of RestClientBuilder. Works fine using a normal username and password session, but craps its pants with a NullReferenceException when logged in with Azure login through SSO…

Is this a known bug?

You need to determine if the error is with the system or something you are referencing in your code that is not available when logged in via sso.

Make a very simple function that can only succeed.

Run it when logged in via sso and see if it works.

1 Like

How does you code look like and what is full error stack?

2 Likes

Code is pretty basic, error happens when calling RestClientBuilder.Build(), it’s a NullReferenceException in the Build() method, with no inner exception. Same code works fine in both Kinetic and Classic when using a normal non-SSO login. Since the only external object I am passing the RestClientBuilder is the Session object, I am guessing something is missing in the session object… This is what makes this so difficult to diagnose, I have no additionnal information…

I can post the actual code on monday, but it’s really not going to tell you any more than this…

It can’t come from my code, because by then my code isn’t even in play, the error occurs as I am trying to build the RestClient… Only two objects is the RestClientBuilder, and the Ice.Core.Session object…

Well then technically that is still your error, but just not your fault :smile:

Serialize your Session objects from each and compare them side by side. We can start there.

What version? RestClientBuilder was super busted for SSO in 10.2.700-2021.2.x

1 Like

It’s ont he Epicor Cloud, so I am guessing the latest 2022.x…

Could you provide some code, and a comparison of the different Session objects side by side?
(Private Data obfuscated of course)

Here’s the code:

var restApiKey = "MYAPIKEY";
var s = (Ice.Core.Session)oTrans.Session;				
var restClient = new RestClientBuilder().SetDefaultApiKey(restApiKey)
                                        .UseSession(s)
                                        .Build();

I can’t serialize the session object, somehow both JsonSerializer and XmlSerializer just take 2-3 minutes and then crap out with no error, probably due to recursion issues… Which properties do you want to see?

I forgot about that. I’m not at work at the moment but I’ll give you a function to do it single level that I use for debugging.

i don’t know what I want to see, but maybe it will reveal important differences.

SSO in smart client always meant Windows auth only, not Azure or IDP.
In this topic it is mentioned in different context. Did you set any property or anything specifically with SSO name in it?

1 Like

Sorry for the delay, Support is taking care of it… Apparently they had to adjust something in the server config, as well as modify something in the client .sysconfig… I will test their solution this afternoon, will update later…

So, support was as useless as usual, they did nothing but point me to the AuthenticationMode in the client .sysconfig. Which was pointless. It sill doesn’t work. And after decompiling Epicor source it cannot work.

private static bool DetermineIsSingleSignOn()
{
	return Authentication.IsWindows(AppSettingsHandler.AuthenticationMode);
}

That function only checks Authentication.IsWindows() and not Authentication.IsAzureAD()… So it’s never gonna work with Azure AD.

I wish support could have maybe tested it and told me this a week ago…

Interesting thread…

@HLalumiere I am kinda lost on this. Are you calling a function from a form customization within the smart client?

Yes…

Kinetic form?

Run your session objects through this, and let’s see it.
This is only single level and very simple.
May or may not let us know what we need to see.

  //For BPM, Function, etc
  //Single Level Object Walker
  Func<object, int, string> GetObjectProperties = (_obj, padding) =>
  {

    string _nl = Environment.NewLine;
    //string tab = "\t";

    string retValue = "";
    foreach (var prop in _obj.GetType().GetProperties())
    {
      try
      {
        retValue += prop.Name.PadRight(padding);

        try
        {
          retValue += prop.GetValue(_obj, null).ToString();
        }
        catch {}
        
      }
      catch {}
      retValue += _nl;
    }
    return retValue;
  };



  //For Classic UI Customization
  //Single Level Object Walker
  string GetObjectProperties(object _obj, int padding = 30)
  {

    string _nl = Environment.NewLine;
    //string tab = "\t";

    string retValue = "";
    foreach (var prop in _obj.GetType().GetProperties())
    {
      try
      {
        retValue += prop.Name.PadRight(padding);

        try
        {
          retValue += prop.GetValue(_obj, null).ToString();
        }
        catch {}
        
      }
      catch {}
      retValue += _nl;
    }
    return retValue;
  }

We use Azure AD and this is what we use to generate proper Auth

using System.Reflection;

public bool SetupEpicorRest(string apiKey)
    {
        try
        {
            
            string username,password, token;
			EpicorRest.Company = session.CompanyID;

			Assembly sm = Assembly.LoadFrom("Epicor.ServiceModel.dll");  // Kinetic Uplift
			object authClass = sm.CreateInstance("Epicor.ServiceModel.Wcf.Security.AuthenticationWcf");  // Kinetic Uplift
			Type smType = authClass.GetType();  // Kinetic Uplift
			BindingFlags bf = BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Public;  // Kinetic Uplift
			MethodInfo mi = smType.GetMethod("IsWindows", bf);  // Kinetic Uplift
			object[] invParams = new object[1]{AppSettingsHandler.AuthenticationMode};  // Kinetic Uplift
			bool isWindows = (bool)mi.Invoke(authClass,invParams);  // Kinetic Uplift
			

            var accessTokenFunc = typeof(Ice.Core.Session).GetProperty("GetAccessTokenFunc", BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Public).GetValue(session) as Func<bool,string>;
            if(accessTokenFunc !=null)
            {
                token = accessTokenFunc(false); //Gets Bearer Token
            }
			else if(isWindows) // Kinetic Uplift
            {
                username = Environment.UserName; //Uses Windows Auth
                password = "";
            }
            else
            {
				// Pulls username and password
                object clientCreds = typeof(Ice.Core.Session).GetProperty("ClientCredentials", BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Public).GetValue(session);
                if(clientCreds!=null)
                {
                    object clientCredsUname = clientCreds.GetType().GetProperty("UserName").GetValue(clientCreds);
                    username =clientCredsUname.GetType().GetProperty("UserName").GetValue(clientCredsUname) as String;
                    password =clientCredsUname.GetType().GetProperty("Password").GetValue(clientCredsUname) as String;
                }
            }
            Console.WriteLine($"user: {username} pwd:{password} token:{token}");
            return true;
        }
        catch(Exception ex)
        {
            return false;
        }
    }
5 Likes