|
| 1 | +--- |
| 2 | +layout: page_v2 |
| 3 | +sidebarType: 5 |
| 4 | +title: Prebid Server | Developers | Adding a Java Privacy Module |
| 5 | +--- |
| 6 | + |
| 7 | +# Prebid Server - Adding a Java Privacy Module |
| 8 | +{: .no_toc} |
| 9 | + |
| 10 | +* TOC |
| 11 | +{:toc } |
| 12 | + |
| 13 | +## Overview |
| 14 | + |
| 15 | +This document details how to make a 'privacy module' for PBS-Java. This type of |
| 16 | +module is not related to the [main workflow modules](/prebid-server/pbs-modules/). Rather, it's a specialized type of module that |
| 17 | + |
| 18 | +You will want to be familiar with the following background information: |
| 19 | + |
| 20 | +* [Privacy Modules](/prebid-server/developers/add-a-privacy-module.html) |
| 21 | +* [Prebid Server Activity Controls](/prebid-server/features/pbs-activitycontrols.html) |
| 22 | + |
| 23 | +## Coding Standards |
| 24 | + |
| 25 | +The module’s code style should correspond to |
| 26 | +the [PBS-Java project code style](https://github.com/prebid/prebid-server-java/blob/master/docs/developers/code-style.md). |
| 27 | + |
| 28 | +### Privacy Module Directory Layout |
| 29 | + |
| 30 | +The source code of your privacy module must be inside packages: |
| 31 | + |
| 32 | +```text |
| 33 | +// Privacy Module provider |
| 34 | +org.prebid.server.activity.infrastructure.creator.privacy.YOUR_PRIVACY_MODULE_NAME |
| 35 | +
|
| 36 | +// Privacy Module implementation |
| 37 | +org.prebid.server.activity.infrastructure.privacy.YOUR_PRIVACY_MODULE_NAME |
| 38 | + |
| 39 | +// Account config for your Privacy Module (if presented as a single class, then a separate package isn’t required) |
| 40 | +org.prebid.server.settings.model.activity.privacy.YOUR_PRIVACY_MODULE_NAME |
| 41 | +``` |
| 42 | + |
| 43 | +### Module Code |
| 44 | + |
| 45 | +The quick start is to take a look in three places: |
| 46 | + |
| 47 | +* the [USNat Privacy Module](https://github.com/prebid/prebid-server-java/tree/master/src/main/java/org/prebid/server/activity/infrastructure/privacy/usnat) |
| 48 | +* the [USNat Privacy Module creator](https://github.com/prebid/prebid-server-java/tree/master/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/usnat) |
| 49 | +* the [account config for USNat Privacy Module](https://github.com/prebid/prebid-server-java/blob/master/src/main/java/org/prebid/server/settings/model/activity/privacy/AccountUSNatModuleConfig.java) |
| 50 | + |
| 51 | +{: .alert.alert-info :} |
| 52 | +The 'usnat' code is documented on the website as [usgen](/prebid-server/features/pbs-usgen.html) |
| 53 | + |
| 54 | +## Privacy Module interface |
| 55 | + |
| 56 | +Among the Prebid server processing workflow, there are several 'activities' that require permission from the Activity |
| 57 | +Infrastructure to run. |
| 58 | + |
| 59 | +The available activities that the Activity Infrastructure can control can be found in the corresponding |
| 60 | +enum: [Activity](https://github.com/prebid/prebid-server-java/blob/master/src/main/java/org/prebid/server/activity/Activity.java). |
| 61 | + |
| 62 | +Whenever a workflow asks permission to perform an activity, the configured rules will be processed. All rules implement |
| 63 | +the `Rule` interface. In accordance with this, every privacy module implements `PrivacyModule` interface, which |
| 64 | +inherits `Rule` interface, with methods should be implemented: |
| 65 | + |
| 66 | +* `proceed(...)` - returns one of the privacy module answers: `ALLOW`, `DISALLOW`, `ABSTAIN`. |
| 67 | + |
| 68 | +### Privacy Module example |
| 69 | + |
| 70 | +```java |
| 71 | +public class MyPrivacyModule implements PrivacyModule { |
| 72 | + |
| 73 | + private final GppModel gppModel; |
| 74 | + private final int forbiddenSection; |
| 75 | + |
| 76 | + private MyPrivacyModule(GppModel gppModel, int forbiddenSection) { |
| 77 | + this.gppModel = gppModel; |
| 78 | + this.forbiddenSection = forbiddenSection; |
| 79 | + } |
| 80 | + |
| 81 | + @Override |
| 82 | + public Result proceed(ActivityInvocationPayload activityInvocationPayload) { |
| 83 | + return gppModel != null && gppModel.hasSection(forbiddenSection) |
| 84 | + ? Result.DISALLOW |
| 85 | + : Result.ABSTAIN; |
| 86 | + } |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +## Privacy Module Creator Interface |
| 91 | + |
| 92 | +The lifecycle of a privacy module is defined by `PrivacyModuleCreator`. Possible life cycles: |
| 93 | + |
| 94 | +* one for the server - if the creator always returns the same privacy module that was created when the server started |
| 95 | +* one for a period of time (cached) - (for example) if the creator will create a new privacy module every time the |
| 96 | + associated account is updated |
| 97 | +* one per request - if the creator always returns a new privacy module |
| 98 | + |
| 99 | +`PrivacyModuleCreator` consists of methods that must be implemented: |
| 100 | + |
| 101 | +* `qualifier()` - returns privacy module qualifier. |
| 102 | +* `from(...)` - returns created privacy module. |
| 103 | + |
| 104 | +### Privacy Module Creator Example |
| 105 | + |
| 106 | +```java |
| 107 | +public class MyPrivacyModuleCreator implements PrivacyModuleCreator { |
| 108 | + |
| 109 | + @Override |
| 110 | + public PrivacyModuleQualifier qualifier() { |
| 111 | + return PrivacyModuleQualifier.MY_PRIVACY_MODULE; |
| 112 | + } |
| 113 | + |
| 114 | + @Override |
| 115 | + public PrivacyModule from(PrivacyModuleCreationContext creationContext) { |
| 116 | + final MyPrivacyModuleConfig moduleConfig = moduleConfig(creationContext); |
| 117 | + final GppModel gppModel = creationContext.getGppContext().scope().getGppModel(); |
| 118 | + |
| 119 | + final List<PrivacyModule> innerPrivacyModules = SetUtils.emptyIfNull(moduleConfig.getForbiddenSections()) |
| 120 | + .stream() |
| 121 | + .filter(Objects::nonNull) |
| 122 | + .map(forbiddenSection -> new MyPrivacyModule(gppModel, forbiddenSection)) |
| 123 | + .toList(); |
| 124 | + |
| 125 | + return new AndPrivacyModules(innerPrivacyModules); |
| 126 | + } |
| 127 | + |
| 128 | + private static MyPrivacyModuleConfig moduleConfig(PrivacyModuleCreationContext creationContext) { |
| 129 | + return (MyPrivacyModuleConfig) creationContext.getPrivacyModuleConfig(); |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +## Privacy Module Qualifier |
| 135 | + |
| 136 | +`PrivacyModuleQualifier` is an enumeration containing all possible unique names of the privacy modules supported by this |
| 137 | +server instance. |
| 138 | + |
| 139 | +### Privacy Module Qualifier Example |
| 140 | + |
| 141 | +```java |
| 142 | +public enum PrivacyModuleQualifier { |
| 143 | + |
| 144 | + // other qualifiers |
| 145 | + |
| 146 | + @JsonProperty(Names.MY_PRIVACY_MODULE) // required when adding MY_PRIVACY_MODULE |
| 147 | + MY_PRIVACY_MODULE(Names.MY_PRIVACY_MODULE); // required when adding MY_PRIVACY_MODULE |
| 148 | + |
| 149 | + // fields and methods |
| 150 | + |
| 151 | + public static class Names { |
| 152 | + |
| 153 | + // other names |
| 154 | + |
| 155 | + public static final String MY_PRIVACY_MODULE = "privacy.my-module"; // required when adding MY_PRIVACY_MODULE |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +``` |
| 160 | + |
| 161 | +## Privacy Module Account Configuration |
| 162 | + |
| 163 | +Any privacy module must be configured in the account configuration to affect Prebid server processing workflow. |
| 164 | + |
| 165 | +When adding a new privacy module, it is important to create an appropriate configuration class. The configuration class |
| 166 | +must implement the `AccountPrivacyModuleConfig` interface, with methods should be implemented: |
| 167 | + |
| 168 | +* `getCode()` - returns privacy module qualifier. |
| 169 | +* `enabled()` - returns boolean. `null` or `true` means that this privacy module is 'on'. |
| 170 | + |
| 171 | +IMPORTANT. Because the Prebid server can merge account configurations from different locations, make sure: |
| 172 | + |
| 173 | +```text |
| 174 | +deserializeFromJson(serializeToJson(config object)) == config object |
| 175 | +``` |
| 176 | + |
| 177 | +### Privacy Module Account Configuration Example |
| 178 | + |
| 179 | +```java |
| 180 | +@Value(staticConstructor = "of") |
| 181 | +public class MyPrivacyModuleConfig implements AccountPrivacyModuleConfig { |
| 182 | + |
| 183 | + @Accessors(fluent = true) |
| 184 | + Boolean enabled; |
| 185 | + |
| 186 | + Set<Integer> forbiddenSections; |
| 187 | + |
| 188 | + @Override |
| 189 | + public PrivacyModuleQualifier getCode() { |
| 190 | + return PrivacyModuleQualifier.MY_PRIVACY_MODULE; |
| 191 | + } |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +```java |
| 196 | +package org.prebid.server.settings.model.activity.privacy; |
| 197 | + |
| 198 | + |
| 199 | +@JsonSubTypes({ |
| 200 | + // other privacy modules |
| 201 | + |
| 202 | + @JsonSubTypes.Type( // relationship between configuration class and privacy module name |
| 203 | + value = MyPrivacyModuleConfig.class, // configuration class |
| 204 | + name = PrivacyModuleQualifier.Names.MY_PRIVACY_MODULE)}) // privacy module name |
| 205 | +public sealed interface AccountPrivacyModuleConfig permits |
| 206 | + // other privacy modules |
| 207 | + |
| 208 | + MyPrivacyModuleConfig { // configuration class must be listed after 'permits' keyword |
| 209 | + |
| 210 | + // methods |
| 211 | +} |
| 212 | +``` |
| 213 | + |
| 214 | +## Privacy Module Bean Configuration |
| 215 | + |
| 216 | +Privacy module beans must be inside the destined configuration class: `ActivityInfrastructureConfiguration.PrivacyModuleCreatorConfiguration` |
| 217 | + |
| 218 | +### Privacy Module Bean Configuration Example |
| 219 | + |
| 220 | +If there is only one bean associated with the privacy module: |
| 221 | + |
| 222 | +```java |
| 223 | +@Configuration |
| 224 | +static class PrivacyModuleCreatorConfiguration { |
| 225 | + |
| 226 | + // other privacy modules |
| 227 | + |
| 228 | + @Bean |
| 229 | + MyPrivacyModuleCreator myPrivacyModuleCreator() { |
| 230 | + return new MyPrivacyModuleCreator(); |
| 231 | + } |
| 232 | +} |
| 233 | +``` |
| 234 | + |
| 235 | +If there are multiple beans associated with the privacy module: |
| 236 | + |
| 237 | +```java |
| 238 | +@Configuration |
| 239 | +static class PrivacyModuleCreatorConfiguration { |
| 240 | + |
| 241 | + // other privacy modules |
| 242 | + |
| 243 | + @Configuration |
| 244 | + static class MyPrivacyModuleCreatorConfiguration { |
| 245 | + |
| 246 | + @Bean |
| 247 | + MyPrivacyModuleDependency myPrivacyModuleDependency() { |
| 248 | + return new MyPrivacyModuleDependency(); |
| 249 | + } |
| 250 | + |
| 251 | + @Bean |
| 252 | + MyPrivacyModuleCreator myPrivacyModuleCreator(MyPrivacyModuleDependency myPrivacyModuleDependency) { |
| 253 | + return new MyPrivacyModuleCreator(myPrivacyModuleDependency); |
| 254 | + } |
| 255 | + } |
| 256 | +} |
| 257 | +``` |
| 258 | + |
| 259 | +## Adding support for trace log |
| 260 | + |
| 261 | +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. |
| 262 | + |
| 263 | +`Loggable` consists of methods that must be implemented: |
| 264 | + |
| 265 | +* `asLogEntry(...)` - returns `JsonNode` that can represent any desired structure to include in the trace log. |
| 266 | + |
| 267 | +For example: |
| 268 | + |
| 269 | +```java |
| 270 | +public class MyPrivacyModule implements PrivacyModule, Loggable { |
| 271 | + |
| 272 | + // privacy module code |
| 273 | + |
| 274 | + @Override |
| 275 | + public JsonNode asLogEntry(ObjectMapper mapper) { |
| 276 | + return TextNode.valueOf( |
| 277 | + "%s forbidding %d section.".formatted( |
| 278 | + MyPrivacyModule.class.getSimpleName(), |
| 279 | + forbiddenSection)); |
| 280 | + } |
| 281 | +} |
| 282 | +``` |
0 commit comments