Consent Management
The GDPR requires to always ask for consent whenever processing personal data. The regulation itself categorizes them
Anything that can be used to identify a person it is related to.
Compliance Checklist
https://eur-lex.europa.eu/eli/reg/2016/679/oj
https://gdpr-info.eu/issues/consent/
Consent Architecture
The first we need to version the consents and use cases. We create a table name use_case
.
CREATE TABLE consent_category (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL -- such as necessary_cookies, tracking_cookies, privacy_policy
);
CREATE TABLE use_case (
id SERIAL PRIMARY KEY,
category INTEGER REFERENCES consent_category,
version Integer UNIQUE NOT NULL,
summary VARCHAR(500) NOT NULL, -- So that programmers can read it easily.
document_name VARCHAR(200) NOT NULL, -- Keep a separate document that user sees.
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT now()
);
I think you are not allowed to change the consents, unless it is a typo. You should create a new version.
We also need a table consent
to actually hold the consents
CREATE TABLE consent (
id SERIAL PRIMARY KEY ,
"user" INTEGER REFERENCES app_user,
use_case INTEGER REFERENCES use_case,
accepted BOOLEAN NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
The consent
table points to use case and each time user accepts or cancels consent it should
create a new row.
The idea is that we only read the latest created_at
for a given use case and if it accepted
is true
then we can
proceed. If you want to keep your database small, you should put as much as possible to single use_case
.
The reason we keep history is for audit logs.
The Interface
def has_consent(user_id: int, category: str) -> bool:
"""
Finds whether user has consent for a given action. Only the latest one matters.
:param user_id for user to find consent
:param category of consent
:raise ConsentDoesNotExist
:returns a boolean value whether consent is valid
"""
raise NotImplementedError
Then before each sensitive action we do
if has_consent(user_id=user.id, category=ConsentCategory.TRACKING):
# do your special stuff here
# for normal users it should execute differently
Another alternative is to define context
class ConsentSession:
def __init__(self, user, category):
# ...
def __enter__(self):
if self.has_consent():
# ...
else:
self.__exit__()
def __exit__(self, exc_type, exc_val, exc_tb):
# ...
with ConsentSession(user, ConsentCategory.Tracking) as session:
# ...
API Interface
For frontend processing and showing stuff you likely want to cache it and return all from single endpoint
GET /v1/caches/consents
{
"necessaryCookies": true,
"trackingCookies": false
}
Then implement a similar interface as in backend but read them from wherever those consents are stored such as localStorage
.