Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return multiple IDs from same userId submodule #8429

Closed
Nick-Merkle opened this issue May 18, 2022 · 9 comments · Fixed by #8499
Closed

Return multiple IDs from same userId submodule #8429

Nick-Merkle opened this issue May 18, 2022 · 9 comments · Fixed by #8499
Assignees

Comments

@Nick-Merkle
Copy link
Contributor

Overview

We are looking for a way to have our User ID module return different IDs per SSP to consume. The current configuration appears to be limited to a single ID to use by anyone. For our use case each SSP we work with has their own ID to identify users so having a single ID for anyone to use is not possible.

Issue

Each userId submodule is limited to a single configuration, but we are looking for a way to support multiple configurations within our submodule.

Use Case

A publisher would configure to use like this:

pbjs.setConfig({
        "debug": true,
        "userSync": {
          "userIds": [
            {
              "name": "merkleId",
              "params": {
                "sv_pubid":  "12562",
                "ssp_ids": ["527404512","534404531"]
              },
              "storage": {
                "type": "html5",
                "name": "merkleIds",
                "expires": 30
              }
            }
          ],
          "syncDelay": 5000,
          "auctionDelay": 1000
        }
      });

Note: params.ssp_ids would correspond to their own source and id.

Example EID data

The sub-module would construct data similar to the example below. We constructed this data similar to the examples used by pubProvidedId, but it can be modified to fit into something more universally acceptable for Prebid.js.

{
    "user": {
      "ext": {
          "eids": [
              {
                  "source": "ssp1.merkleinc.com",
                  "uids": [
                      {
                          "atype": 3,
                          "id": "unique-id-for-ssp1",
                          "ext": {
                              "enc": 1,
                              "keyId": 16,
                              "idName": "pamId"
                          }
                      }
                  ]
              },
              {
                  "source": "ssp2.merkleinc.com",
                  "uids": [
                      {
                          "atype": 3,
                          "id": "unique-id-for-spp2",
                          "ext": {
                              "enc": 1,
                              "idName": "pamId",
                              "third": 4
                          }
                      }
                  ]
              }
          ]
      }
  }
}

Possible Solution

The example below illustrates one possible way to handle multiple source configs.

File: src/modules/userId/eids.js

export function createEidsArray(bidRequestUserId) {
  let eids = [];

  for (const subModuleKey in bidRequestUserId) {
    if (bidRequestUserId.hasOwnProperty(subModuleKey)) {
      if (subModuleKey === 'pubProvidedId') {
        eids = eids.concat(bidRequestUserId['pubProvidedId']);
      } 

      // ************************************************************

      // @NOTE: A more generic flag is required without hard coding subModuleKey
      else if (subModuleKey === 'merkleId') {

        // #1 - Loop through each submodule request and create EID?
        for (const userIdRequest in bidRequestUserId[subModuleKey]) {
          const eid = createEidObject(userIdRequest, subModuleKey);

          if (eid) {
            eids.push(eid);
          }
        } 
        
        // #2 - Assume object is already constructed and pass it along
        eids = eids.concat(bidRequestUserId['merkleIds']);

        // ************************************************************

      } else {
        const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey);
        if (eid) {
          eids.push(eid);
        }
      }
    }
  }
  return eids;
}

Sample Test Case

Below is just one example of how something like the above could be implemented into Prebid.js.

Example test/spec/modules/eids_spec.js

it('Config supports an array', function() {
    
    const userId = {
      merkleIds: [
        {
          'source': 'ssp1.merkleinc.com',
          'uids': [
            {
              'atype': 3,
              'id': 'some-random-id-value',
              'ext': {
                'sspid': '534404531',
                'enc': 1,
                'keyId': 16,
                'idName': 'pamId'
              }
            }
          ]
        },
        {
          'source': 'ssp2.merkleinc.com',
          'uids': [
            {
              'atype': 3,
              'id': 'another-random-id-value',
              'ext': {
                'enc': 1,
                'idName': 'pamId',
                'third': 4
              }
            }
          ]
        }
      ]
    };
    const newEids = createEidsArray(userId);
    expect(newEids.length).to.equal(2);
    expect(newEids[0]).to.deep.equal({
      source: 'ssp1.merkleinc.com',
      uids: [{id: 'some-random-id-value',
        atype: 3,
        ext: {
          sspid: '534404531',
          enc: 1,
          keyId: 16,
          idName: 'pamId'
        }
      }]
    });
    expect(newEids[1]).to.deep.equal({
      source: 'ssp2.merkleinc.com',
      uids: [{id: 'another-random-id-value',
        atype: 3,
        ext: {
          third: 4,
          enc: 1,
          idName: 'pamId'
        }
      }]
    });
  });
@patmmccann
Copy link
Collaborator

why not put them all in the ext "user": { "ext": { "eids": [ { "source": "merkleinc.com", "uids": [ { "atype": 3, "id": "filler-value", "ext": { "enc": 1, "id-p1": "partner1id", "id-p2": "partner2id", "id-p3": "partner3id", "id-p4": "partner4id", "idName": "pamId" } } ] },

@patmmccann
Copy link
Collaborator

or whhy not just have a single value and multiple decryption keys, one you give each ssp

https://crypto.stackexchange.com/questions/39397/one-encryption-many-decryption-keys

@patmmccann
Copy link
Collaborator

sorry last question, it seems like you already coded up a solution, why not just submit it?

@jdwieland8282 jdwieland8282 self-assigned this May 18, 2022
@Nick-Merkle
Copy link
Contributor Author

why not put them all in the ext "user": { "ext": { "eids": [ { "source": "merkleinc.com", "uids": [ { "atype": 3, "id": "filler-value", "ext": { "enc": 1, "id-p1": "partner1id", "id-p2": "partner2id", "id-p3": "partner3id", "id-p4": "partner4id", "idName": "pamId" } } ] },

I don't think we can assume the ext attributes will be the same across partners.

or whhy not just have a single value and multiple decryption keys, one you give each ssp

https://crypto.stackexchange.com/questions/39397/one-encryption-many-decryption-keys

We want to use different IDs so SSPs can only decrypt their own ID and not IDs to other SSPs. This is a business rule already set in place.

sorry last question, it seems like you already coded up a solution, why not just submit it?

I can submit it for review. The solution is not elegant and is coded specific to our own submodule by using the following condition: else if (subModuleKey === 'merkleIds') inside a core file, userId/eids.js. If there is a cleaner more elegant solution, one that could be reused should other submodules have a similar need, I am open for discussion.

@Nick-Merkle
Copy link
Contributor Author

By design, the Merkury platform has siloed identity spaces per SSP and sometimes per-Publisher within the SSP, though that use-case seems to be fading. An individual will have n-number of unique identifiers based on the number of SSPs configured by the Publisher in Prebid.

The Merkury module needs to return all of the possible identifiers for an individual, tied to a specific SSP and its unique configuration including per-SSP encryption.

@jdwieland8282
Copy link
Member

We have in eids.uids.ext.stype current valid stype values are:

  • ppuid
  • DMP
  • Other

We could add a value something like "multiple" or "multiple-encrypted", then have your if else statement key in on that value?

@Nick-Merkle
Copy link
Contributor Author

Nick-Merkle commented May 23, 2022

I have some ideas here... I'm thinking about maintaining the current USER_IDS_CONFIG config and checking if the bidRequestUserId passed in is an array, if it is the code will loop through each element and build the EID object based on the sub-module's config as shown below:

File: modules/userId/eids.js


export function createEidsArray(bidRequestUserId) {
  let eids = [];

  for (const subModuleKey in bidRequestUserId) {
    if (bidRequestUserId.hasOwnProperty(subModuleKey)) {
      if (subModuleKey === 'pubProvidedId') {
        eids = eids.concat(bidRequestUserId['pubProvidedId']);
      } else if (Array.isArray(bidRequestUserId[subModuleKey])) {

        bidRequestUserId[subModuleKey].forEach((config, index, arr)=> {
          const eid = createEidObject(config, subModuleKey);
          if (eid) {
            eids.push(eid);
          }
        })
        
      } else {
        const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey);
        if (eid) {
          eids.push(eid);
        }
      }
    }
  }
  return eids;
}

Expanding on your idea of having eids.uids.ext.stype I will include a flag eids.uids.ext.ssp to distinguish between each SSP which SSPs can use to find their appropriate ID.

Example eids array.

{
    "source": "merkleinc.com",
    "uids": [
        {
            "atype": 3,
            "id": "id-1",
            "ext": {
                "ssp": "magnite",
                "enc": 1,
                "idName": "pamId",
                "keyID": 4
            }
        },
        {
          "atype": 3,
          "id": "id-2",
          "ext": {
              "ssp": "indexechange",
              "enc": 1,
              "idName": "ppide",
              "keyID": 16
          }
        }
    ]
  }

I think this approach is more in line with how Prebid is already configured to work and structured. I'm going to run this through the test suite and see what breaks and if all goes well I'll submit a PR for review.

@patmmccann
Copy link
Collaborator

Thanks for the contribution!

@Nick-Merkle
Copy link
Contributor Author

I made some additional changes to construct the eids array for multiple sources as follows:

[
  {
    "source": "index.merkleinc.com",
    "uids": [
      {
        "atype": 3,
        "id": "id-2",
        "ext": {
            "ssp": "indexechange",
            "enc": 1,
            "idName": "ppide",
            "keyID": 16
        }
      }
    ]
  },
  {
    "source": "magnite.merkleinc.com",
    "uids": [
      {
        "atype": 3,
        "id": "id-1",
        "ext": {
            "ssp": "magnite",
            "enc": 1,
            "idName": "pamId",
            "keyID": 4
        }
      }
    ]
  }
]

To make use of this I made changes so the source config can support a function eid.source = isFn(conf['getSource']) ? conf['getSource'](userIdData) : conf['source'];.

Example config:

export const USER_IDS_CONFIG = {
...
'merkleId': {
    atype: 3,
    getSource: function(data) {
      if (data?.ext?.ssp) {
        return `${data.ext.ssp}.merkleinc.com`
      }
      return 'merkleinc.com'
    },
    getValue: function(data) {
      return data.id;
    },
    getUidExt: function(data) {
      if (data.keyID) {
        return {
          keyID: data.keyID
        }
      }
      if (data.ext) {
        return data.ext;
      }
    }
  },
...
};

I ran it through testing and everything appears to be working with over 80% code coverage. I am preparing a PR for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging a pull request may close this issue.

3 participants