Prebid Server - Adding a Java Privacy Module

Overview

This document details how to make a ‘privacy module’ for PBS-Java. This type of module is not related to the main workflow modules. Rather, it’s a specialized type of module that

You will want to be familiar with the following background information:

Coding Standards

The module’s code style should correspond to the PBS-Java project code style.

Privacy Module Directory Layout

The source code of your privacy module must be inside packages:

// Privacy Module provider
org.prebid.server.activity.infrastructure.creator.privacy.YOUR_PRIVACY_MODULE_NAME

// Privacy Module implementation
org.prebid.server.activity.infrastructure.privacy.YOUR_PRIVACY_MODULE_NAME
    
// Account config for your Privacy Module (if presented as a single class, then a separate package isn’t required)
org.prebid.server.settings.model.activity.privacy.YOUR_PRIVACY_MODULE_NAME

Module Code

The quick start is to take a look in three places:

The ‘usnat’ code is documented on the website as usgen

Privacy Module interface

Among the Prebid server processing workflow, there are several ‘activities’ that require permission from the Activity Infrastructure to run.

The available activities that the Activity Infrastructure can control can be found in the corresponding enum: Activity.

Whenever a workflow asks permission to perform an activity, the configured rules will be processed. All rules implement the Rule interface. In accordance with this, every privacy module implements PrivacyModule interface, which inherits Rule interface, with methods should be implemented:

  • proceed(...) - returns one of the privacy module answers: ALLOW, DISALLOW, ABSTAIN.

Privacy Module example

public class MyPrivacyModule implements PrivacyModule {

    private final GppModel gppModel;
    private final int forbiddenSection;

    private MyPrivacyModule(GppModel gppModel, int forbiddenSection) {
        this.gppModel = gppModel;
        this.forbiddenSection = forbiddenSection;
    }

    @Override
    public Result proceed(ActivityInvocationPayload activityInvocationPayload) {
        return gppModel != null && gppModel.hasSection(forbiddenSection) 
                ? Result.DISALLOW 
                : Result.ABSTAIN;
    }
}

Privacy Module Creator Interface

The lifecycle of a privacy module is defined by PrivacyModuleCreator. Possible life cycles:

  • one for the server - if the creator always returns the same privacy module that was created when the server started
  • one for a period of time (cached) - (for example) if the creator will create a new privacy module every time the associated account is updated
  • one per request - if the creator always returns a new privacy module

PrivacyModuleCreator consists of methods that must be implemented:

  • qualifier() - returns privacy module qualifier.
  • from(...) - returns created privacy module.

Privacy Module Creator Example

public class MyPrivacyModuleCreator implements PrivacyModuleCreator {

    @Override
    public PrivacyModuleQualifier qualifier() {
        return PrivacyModuleQualifier.MY_PRIVACY_MODULE;
    }

    @Override
    public PrivacyModule from(PrivacyModuleCreationContext creationContext) {
        final MyPrivacyModuleConfig moduleConfig = moduleConfig(creationContext);
        final GppModel gppModel = creationContext.getGppContext().scope().getGppModel();

        final List<PrivacyModule> innerPrivacyModules = SetUtils.emptyIfNull(moduleConfig.getForbiddenSections())
                .stream()
                .filter(Objects::nonNull)
                .map(forbiddenSection -> new MyPrivacyModule(gppModel, forbiddenSection))
                .toList();

        return new AndPrivacyModules(innerPrivacyModules);
    }

    private static MyPrivacyModuleConfig moduleConfig(PrivacyModuleCreationContext creationContext) {
        return (MyPrivacyModuleConfig) creationContext.getPrivacyModuleConfig();
    }
}

Privacy Module Qualifier

PrivacyModuleQualifier is an enumeration containing all possible unique names of the privacy modules supported by this server instance.

Privacy Module Qualifier Example

public enum PrivacyModuleQualifier {

    // other qualifiers
    
    @JsonProperty(Names.MY_PRIVACY_MODULE)      // required when adding MY_PRIVACY_MODULE
    MY_PRIVACY_MODULE(Names.MY_PRIVACY_MODULE); // required when adding MY_PRIVACY_MODULE

    // fields and methods

    public static class Names {

        // other names
        
        public static final String MY_PRIVACY_MODULE = "privacy.my-module"; // required when adding MY_PRIVACY_MODULE
    }
}

Privacy Module Account Configuration

Any privacy module must be configured in the account configuration to affect Prebid server processing workflow.

When adding a new privacy module, it is important to create an appropriate configuration class. The configuration class must implement the AccountPrivacyModuleConfig interface, with methods should be implemented:

  • getCode() - returns privacy module qualifier.
  • enabled() - returns boolean. null or true means that this privacy module is ‘on’.

IMPORTANT. Because the Prebid server can merge account configurations from different locations, make sure:

deserializeFromJson(serializeToJson(config object)) == config object

Privacy Module Account Configuration Example

@Value(staticConstructor = "of")
public class MyPrivacyModuleConfig implements AccountPrivacyModuleConfig {

    @Accessors(fluent = true)
    Boolean enabled;

    Set<Integer> forbiddenSections;

    @Override
    public PrivacyModuleQualifier getCode() {
        return PrivacyModuleQualifier.MY_PRIVACY_MODULE;
    }
}
package org.prebid.server.settings.model.activity.privacy;


@JsonSubTypes({
        // other privacy modules
        
        @JsonSubTypes.Type(                                                 // relationship between configuration class and privacy module name
                value = MyPrivacyModuleConfig.class,                        // configuration class
                name = PrivacyModuleQualifier.Names.MY_PRIVACY_MODULE)})    // privacy module name
public sealed interface AccountPrivacyModuleConfig permits
        // other privacy modules
        
        MyPrivacyModuleConfig { // configuration class must be listed after 'permits' keyword 

    // methods
}

Privacy Module Bean Configuration

Privacy module beans must be inside the destined configuration class: ActivityInfrastructureConfiguration.PrivacyModuleCreatorConfiguration

Privacy Module Bean Configuration Example

If there is only one bean associated with the privacy module:

@Configuration
static class PrivacyModuleCreatorConfiguration {

    // other privacy modules
    
    @Bean
    MyPrivacyModuleCreator myPrivacyModuleCreator() {
        return new MyPrivacyModuleCreator();
    }
}

If there are multiple beans associated with the privacy module:

@Configuration
static class PrivacyModuleCreatorConfiguration {

    // other privacy modules
    
    @Configuration
    static class MyPrivacyModuleCreatorConfiguration {

        @Bean
        MyPrivacyModuleDependency myPrivacyModuleDependency() {
            return new MyPrivacyModuleDependency();
        }

        @Bean
        MyPrivacyModuleCreator myPrivacyModuleCreator(MyPrivacyModuleDependency myPrivacyModuleDependency) {
            return new MyPrivacyModuleCreator(myPrivacyModuleDependency);
        }
    }
}

Adding support for trace log

To be able to debug the Activity Infrastructure and be able to track interactions with your privacy module, it is recommended that your PrivacyModule implement the Loggable interface.

Loggable consists of methods that must be implemented:

  • asLogEntry(...) - returns JsonNode that can represent any desired structure to include in the trace log.

For example:

public class MyPrivacyModule implements PrivacyModule, Loggable {

    // privacy module code

    @Override
    public JsonNode asLogEntry(ObjectMapper mapper) {
        return TextNode.valueOf(
                "%s forbidding %d section.".formatted(
                        MyPrivacyModule.class.getSimpleName(),
                        forbiddenSection));
    }
}