Search code examples
javapingfederate

TransactionalStateSupport doesn't save variables


Background Info:

  • Pingfederate v8.4.1.0 Standalone (in dev)
  • Pingfederate v8.4.1.0 clustered 1 console; 4 engines (in prod)
  • ReferenceID Adapter Version 1.3.1
  • Ubuntu 16.04

I'm currently implementing a custom Idp Adapter which basically wraps the ReferenceID Adapter and adds additional functionality. I'm using the TransactionalStateSupport to make the oauth-client-id (which is available as a query parameter during the first HTTP request) accessible throughout the whole transaction. Unfortunately this is not working out so well for me.

The Value doesn't seem to be stored:

String resumePath = (String)inParameters.get("com.pingidentity.adapter.input.parameter.resume.path");
TransactionalStateSupport txStateSupport = new TransactionalStateSupport(resumePath);

String name="foo";
String value="bar";
txStateSupport.setAttribute(name, value, req, resp);
String value2 = (String) txStateSupport.getAttribute(name, req, resp);
System.out.println("Value directly after storing: " + value2);
if(!value.equals(value2))
  System.out.println("**** STORAGE FAILED ****");

Output:

B3E085C450 : Message{partnerRole=null, entityId='null', msg={partnerEntityID=testclient, scope=openid email profile, com.pingidentity.adapter.input.parameter.tracking.id=tid:cBAmXoOGkUCQXCSjRJ4quIlV5DE, response_type=code, redirect_uri=http://localhost.dev, sessionid=D5ZaljlkoPc6Bdv5l37IiyQikCK, client_id=testclient, com.pingidentity.plugin.instanceid=asd}}
2018-06-20 14:10:47,191 tid:4zBU2dUsYh8O-IRvXFjX3WOylLc DEBUG [org.sourceid.servlet.HttpServletRespProxy] adding lazy cookie Cookie{PF=hashedValue:yF4bj2jqUXu6jvMw0rBOVDuATWs; path=/; maxAge=-1; domain=null} replacing null
2018-06-20 14:10:47,191 tid:4zBU2dUsYh8O-IRvXFjX3WOylLc DEBUG [org.sourceid.saml20.service.impl.localmemory.InterReqStateMgmtMapImpl] setAttr(oldKey: null, newKey: yF4bj2jqUXu6jvMw0rBOVDuATWs, name: foo||h6tCJ)
2018-06-20 14:10:47,191 tid:4zBU2dUsYh8O-IRvXFjX3WOylLc DEBUG [org.sourceid.saml20.service.impl.localmemory.InterReqStateMgmtMapImpl] setAttr: new size of attribute map=3
2018-06-20 14:10:47,192 tid:4zBU2dUsYh8O-IRvXFjX3WOylLc INFO  [SystemOut] Value directly after storing: null
2018-06-20 14:10:47,192 tid:4zBU2dUsYh8O-IRvXFjX3WOylLc INFO  [SystemOut] **** STORAGE FAILED ****

Does anyone have a solution to this, or an idea why this process fails?

Thank you in advance


Solution

  • This issue has been bugging me for quite some time as well. It occurred sometimes, sometimes not, and was not always reproducible. Now I finally tracked it down:

    It works if you store something in the transactional state before writing to the HTTP response. It fails if you first write to the HTTP response and then try to store something in the transactional state. If you store something in the transactional state before writing to the HTTP response, you can also write to the transactional state afterwards.

    Observations

    I observed that the issue is related to the length of the PF cookie: it works with a PF cookie that is 44 characters long, but it fails with a 22 character PF cookie (please note that the cookie length is configurable, but there are cookies of single and double length).

    It may also work without any PF request cookie (this is a rare case where I do not agree with @Hans Z. and @Andrew K. ;-)). But if it works, PingFederate sets a 44-character PF cookie in the response.

    So the remaining question is: when/why does PingFederate set a 44-character PF cookie?

    Background

    PingFederate maintains an internal sessionID for every request. The sessionID corresponds to the value of the PF cookie. If no PF cookie is sent initially, a new sessionID is created. So you do not need a PF cookie in the request to use the transactional state.

    The initial sessionID is a short (22 characters) sessionID, which is not suitable to store values in the transactional state. If you have a short sessionID and try to store something in the transactional state, PingFederate extends the sessionID to 44 characters. Therefore you will see a 44 character PF response cookie if it works.

    This process of extending the sessionID fails if the response is already committed. If you do anything with the response like response.getWriter() or response.sendRedirect(), PingFederate will “flush the cookies”. That means it actually adds the cached “lazy” cookies to the HTTP response object, as seen in this log entry:

    DEBUG [org.sourceid.servlet.HttpServletRespProxy] flush cookies: adding Cookie{PF=hashedValue:dkxuW0rImM-votyz33kWkGGbj30; path=/; maxAge=-1; domain=null}
    

    Now that the cookies have been written, the sessionID cannot be extended anymore (otherwise it would be out of sync with what the browser receives), and the transactional state does not work.

    IMHO this is a bug in the PingFederate SDK. At least it is a limitation that should be documented.

    Workaround

    Refactor your code to use the transactional state before writing to the HTTP response. If that is not possible, just insert a line to store a dummy attribute in the transactional state before writing to the HTTP response.

    I have tested this with a standalone PF 9.3.0 instance.

    Side Note

    In regards to the original problem:

    ... to make the oauth-client-id (which is available as a query parameter during the first HTTP request) accessible throughout the whole transaction.

    Since PF 9.2 you can use “tracked HTTP parameters” to make request parameters available throughout the entire transaction. For more details, see the release notes and the SDK documentation.