Repository: incubator-unomi Updated Branches: refs/heads/master 629284e4c -> de5dea661
UNOMI-133 Add (GDPR) consents to visitor profiles - Add new consents object attached to visitor profile - Added a new event to update multiple consents - Added a condition to match the event - Added an action to modify the consents This is not yet complete we still need a rule to match the event to the action, as well as internal events to be able to react to individual consent changes. Signed-off-by: Serge Huber <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/incubator-unomi/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-unomi/commit/de5dea66 Tree: http://git-wip-us.apache.org/repos/asf/incubator-unomi/tree/de5dea66 Diff: http://git-wip-us.apache.org/repos/asf/incubator-unomi/diff/de5dea66 Branch: refs/heads/master Commit: de5dea66154dc8454d89430e23f5fb4ae460ac01 Parents: 629284e Author: Serge Huber <[email protected]> Authored: Tue Oct 31 17:23:54 2017 +0100 Committer: Serge Huber <[email protected]> Committed: Tue Oct 31 17:23:54 2017 +0100 ---------------------------------------------------------------------- .../main/java/org/apache/unomi/api/Consent.java | 72 ++++++++++++++++++++ .../java/org/apache/unomi/api/ConsentGrant.java | 22 ++++++ .../org/apache/unomi/api/ContextResponse.java | 23 +++++++ .../main/java/org/apache/unomi/api/Profile.java | 48 +++++++++++-- .../actions/ModifyConsentsAction.java | 67 ++++++++++++++++++ .../cxs/actions/modifyConsentsAction.json | 16 +++++ .../modifyConsentsEventCondition.json | 23 +++++++ .../resources/OSGI-INF/blueprint/blueprint.xml | 8 +++ .../org/apache/unomi/web/ContextServlet.java | 1 + 9 files changed, 276 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/api/src/main/java/org/apache/unomi/api/Consent.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/unomi/api/Consent.java b/api/src/main/java/org/apache/unomi/api/Consent.java new file mode 100644 index 0000000..6359a52 --- /dev/null +++ b/api/src/main/java/org/apache/unomi/api/Consent.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.unomi.api; + +import java.util.Date; + +/** + * A consent is an object attached to a profile that indicates whether the profile has agreed or denied a special + * consent type. For example a user might have agreed to receiving a newsletter but might have not agreed to being + * tracked. + */ +public class Consent extends Item { + + private String typeId; // types are defined and managed externally of Apache Unomi + private ConsentGrant grant; + private Date grantDate; + private Date revokeDate; + + public Consent(String itemId, String typeId, ConsentGrant grant, Date grantDate, Date revokeDate) { + super(itemId); + this.typeId = typeId; + this.grant = grant; + this.grantDate = grantDate; + this.revokeDate = revokeDate; + } + + public void setTypeId(String typeId) { + this.typeId = typeId; + } + + public String getTypeId() { + return typeId; + } + + public ConsentGrant getGrant() { + return grant; + } + + public void setGrant(ConsentGrant grant) { + this.grant = grant; + } + + public Date getGrantDate() { + return grantDate; + } + + public void setGrantDate(Date grantDate) { + this.grantDate = grantDate; + } + + public Date getRevokeDate() { + return revokeDate; + } + + public void setRevokeDate(Date revokeDate) { + this.revokeDate = revokeDate; + } +} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/api/src/main/java/org/apache/unomi/api/ConsentGrant.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/unomi/api/ConsentGrant.java b/api/src/main/java/org/apache/unomi/api/ConsentGrant.java new file mode 100644 index 0000000..a72125d --- /dev/null +++ b/api/src/main/java/org/apache/unomi/api/ConsentGrant.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.unomi.api; + +public enum ConsentGrant { + GRANT, + DENY +} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/api/src/main/java/org/apache/unomi/api/ContextResponse.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/unomi/api/ContextResponse.java b/api/src/main/java/org/apache/unomi/api/ContextResponse.java index 58bcadd..184586b 100644 --- a/api/src/main/java/org/apache/unomi/api/ContextResponse.java +++ b/api/src/main/java/org/apache/unomi/api/ContextResponse.java @@ -21,6 +21,7 @@ import org.apache.unomi.api.conditions.Condition; import org.apache.unomi.api.services.RulesService; import java.io.Serializable; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -50,6 +51,8 @@ public class ContextResponse implements Serializable { private boolean anonymousBrowsing; + private Map<String, Consent> consents = new LinkedHashMap<>(); + /** * Retrieves the profile identifier associated with the profile of the user on behalf of which the client performed the context request. * @@ -203,4 +206,24 @@ public class ContextResponse implements Serializable { public void setAnonymousBrowsing(boolean anonymousBrowsing) { this.anonymousBrowsing = anonymousBrowsing; } + + /** + * Retrieves the map of consents for the current profile. + * @return a Map where the key is the name of the consent identifier, and the value is a consent object that + * contains all the consent data such as whether the consent was granted or deny, the date of granting/denying + * the date at which the consent will be revoked automatically. + */ + public Map<String, Consent> getConsents() { + return consents; + } + + /** + * Sets the map of consents for the current profile. + * @param consents a Map where the key is the name of the consent identifier, and the value is a consent object that + * contains all the consent data such as whether the consent was granted or deny, the date of granting/denying + * the date at which the consent will be revoked automatically. + */ + public void setConsents(Map<String, Consent> consents) { + this.consents = consents; + } } http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/api/src/main/java/org/apache/unomi/api/Profile.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/unomi/api/Profile.java b/api/src/main/java/org/apache/unomi/api/Profile.java index 8c20adc..fa83e39 100644 --- a/api/src/main/java/org/apache/unomi/api/Profile.java +++ b/api/src/main/java/org/apache/unomi/api/Profile.java @@ -21,10 +21,7 @@ import org.apache.unomi.api.segments.Scoring; import org.apache.unomi.api.segments.Segment; import javax.xml.bind.annotation.XmlTransient; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * A user profile gathering all known information about a given user as well as segments it is part of and scores. @@ -59,6 +56,8 @@ public class Profile extends Item { private String mergedWith; + private Map<String, Consent> consents = new LinkedHashMap<>(); + /** * Instantiates a new Profile. */ @@ -196,12 +195,53 @@ public class Profile extends Item { this.scores = scores; } + public Map<String, Consent> getConsents() { + return consents; + } + @XmlTransient public boolean isAnonymousProfile() { Boolean anonymous = (Boolean) getSystemProperties().get("isAnonymousProfile"); return anonymous != null && anonymous; } + @XmlTransient + public boolean grantConsent(String consentTypeId, Date grantDate, Date revokeDate) { + Consent consent = new Consent(itemId, consentTypeId, ConsentGrant.GRANT, grantDate, revokeDate); + consents.put(consentTypeId, consent); + return true; + } + + @XmlTransient + public boolean denyConsent(String consentTypeId, Date grantDate, Date revokeDate) { + Consent consent = new Consent(itemId, consentTypeId, ConsentGrant.DENY, grantDate, revokeDate); + consents.put(consentTypeId, consent); + return true; + } + + @XmlTransient + public boolean revokeConsent(String consentTypeId) { + if (consents.containsKey(consentTypeId)) { + consents.remove(consentTypeId); + return true; + } + return false; + } + + @XmlTransient + public boolean isConsentGiven(String consentTypeId) { + if (consents.containsKey(consentTypeId)) { + Consent consent = consents.get(consentTypeId); + Date nowDate = new Date(); + if (consent.getGrantDate().before(nowDate) && (consent.getRevokeDate() == null || (consent.getRevokeDate().after(nowDate)))) { + if (consent.getGrant().equals(ConsentGrant.GRANT)) { + return true; + } + } + } + return false; + } + @Override public String toString() { return new StringBuilder(512).append("{id: \"").append(getItemId()).append("\", segments: ") http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/ModifyConsentsAction.java ---------------------------------------------------------------------- diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/ModifyConsentsAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/ModifyConsentsAction.java new file mode 100644 index 0000000..84a1d2c --- /dev/null +++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/ModifyConsentsAction.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.unomi.plugins.baseplugin.actions; + +import org.apache.unomi.api.Consent; +import org.apache.unomi.api.Event; +import org.apache.unomi.api.Profile; +import org.apache.unomi.api.actions.Action; +import org.apache.unomi.api.actions.ActionExecutor; +import org.apache.unomi.api.services.EventService; + +import java.util.List; + +/** + * This class will process consent modification actions and update the profile's consents accordingly. + */ +public class ModifyConsentsAction implements ActionExecutor { + + public static final String GRANTED_CONSENTS = "grantedConsents"; + public static final String DENIED_CONSENTS = "deniedConsents"; + public static final String REVOKED_CONSENTS = "revokedConsents"; + + @Override + public int execute(Action action, Event event) { + + Profile profile = event.getProfile(); + boolean isProfileUpdated = false; + + List<Consent> grantedConsents = (List<Consent>) event.getProperties().get(GRANTED_CONSENTS); + if (grantedConsents != null) { + for (Consent consent : grantedConsents) { + profile.grantConsent(consent.getTypeId(), consent.getGrantDate(), consent.getRevokeDate()); + } + isProfileUpdated = true; + } + List<Consent> deniedConsents = (List<Consent>) event.getProperties().get(DENIED_CONSENTS); + if (deniedConsents != null) { + for (Consent consent : deniedConsents) { + profile.denyConsent(consent.getTypeId(), consent.getGrantDate(), consent.getRevokeDate()); + } + isProfileUpdated = true; + } + List<Consent> revokedConsents = (List<Consent>) event.getProperties().get(REVOKED_CONSENTS); + if (revokedConsents != null) { + for (Consent consent : revokedConsents) { + profile.revokeConsent(consent.getTypeId()); + } + isProfileUpdated = true; + } + + return isProfileUpdated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE; + } +} http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/modifyConsentsAction.json ---------------------------------------------------------------------- diff --git a/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/modifyConsentsAction.json b/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/modifyConsentsAction.json new file mode 100644 index 0000000..2910019 --- /dev/null +++ b/plugins/baseplugin/src/main/resources/META-INF/cxs/actions/modifyConsentsAction.json @@ -0,0 +1,16 @@ +{ + "metadata": { + "id": "modifyConsentsAction", + "name": "modifyConsentsAction", + "description": "Modify profile consents", + "systemTags": [ + "profileTags", + "demographic" + ], + "readOnly": true + }, + "actionExecutor": "modifyConsents", + "parameters": [ + + ] +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/modifyConsentsEventCondition.json ---------------------------------------------------------------------- diff --git a/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/modifyConsentsEventCondition.json b/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/modifyConsentsEventCondition.json new file mode 100644 index 0000000..ec1333e --- /dev/null +++ b/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/modifyConsentsEventCondition.json @@ -0,0 +1,23 @@ +{ + "metadata": { + "id": "modifyConsentsEventCondition", + "name": "modifyConsentsEventCondition", + "description": "", + "systemTags": [ + "profileTags", + "event", + "condition", + "eventCondition" + ], + "readOnly": true + }, + "parentCondition": { + "type": "eventTypeCondition", + "parameterValues": { + "eventTypeId": "modifyConsents" + } + }, + + "parameters": [ + ] +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml ---------------------------------------------------------------------- diff --git a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml index ac245ac..c2a7012 100644 --- a/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -182,6 +182,14 @@ <service auto-export="interfaces"> <service-properties> + <entry key="actionExecutorId" value="modifyConsents"/> + </service-properties> + <bean class="org.apache.unomi.plugins.baseplugin.actions.ModifyConsentsAction"> + </bean> + </service> + + <service auto-export="interfaces"> + <service-properties> <entry key="actionExecutorId" value="evaluateProfileSegments"/> </service-properties> <bean class="org.apache.unomi.plugins.baseplugin.actions.EvaluateProfileSegmentsAction"> http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/de5dea66/wab/src/main/java/org/apache/unomi/web/ContextServlet.java ---------------------------------------------------------------------- diff --git a/wab/src/main/java/org/apache/unomi/web/ContextServlet.java b/wab/src/main/java/org/apache/unomi/web/ContextServlet.java index 245dc94..42d478a 100644 --- a/wab/src/main/java/org/apache/unomi/web/ContextServlet.java +++ b/wab/src/main/java/org/apache/unomi/web/ContextServlet.java @@ -373,6 +373,7 @@ public class ContextServlet extends HttpServlet { } data.setAnonymousBrowsing(privacyService.isRequireAnonymousBrowsing(profile.getItemId())); + data.setConsents(profile.getConsents()); return changes; }
