NAME

GDPR::IAB::TCFv2::Parser - bit-stream parser for TCF v2.3 consent strings

SYNOPSIS

The purpose of this package is to parse Transparency & Consent String (TC String) as defined by IAB version 2.

use warnings;

use GDPR::IAB::TCFv2::Parser;
use GDPR::IAB::TCFv2::Constants::Purpose qw<:all>;
use GDPR::IAB::TCFv2::Constants::SpecialFeature qw<:all>;
use GDPR::IAB::TCFv2::Constants::RestrictionType qw<:all>;

my $consent = GDPR::IAB::TCFv2::Parser->Parse(
    'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA'
);

use feature qw<say>;

say $consent->version;             # 2
say $consent->created;             # epoch 1228644257 or 07/12/2008
say $consent->last_updated;        # epoch 1326215413 or 10/01/2012
say $consent->cmp_id;              # 21 - Traffective GmbH
say $consent->cmp_version;         # 7
say $consent->consent_screen;      # 2
say $consent->consent_language;    # "EN"
say $consent->vendor_list_version; # 23

say "find consent for purpose ids 1, 3, 9 and 10" if 4 == grep {
    $consent->is_purpose_consent_allowed($_)
} ( # constants exported by GDPR::IAB::TCFv2::Constants::Purpose
    InfoStorageAccess,       #  1
    PersonalizationProfile,  #  3
    MarketResearch,          #  9
    DevelopImprove,          # 10
);

say "find consent for vendor id 284 (Weborama)" if $consent->vendor_consent(284);

# Geolocation exported by GDPR::IAB::TCFv2::Constants::SpecialFeature
say "user is opt in for special feature 'Geolocation (id 1)'"
    if $consent->is_special_feature_opt_in(Geolocation);

# NotAllowed exported by GDPR::IAB::TCFv2::Constants::RestrictionType
say "publisher restriction for purpose Info Storage Access (1), restriction type NotAllowed (0) for weborama (284)"
    if $consent->check_publisher_restriction(InfoStorageAccess, NotAllowed, 284);

For policy-driven checks against an entire vendor profile (required purposes, legal basis, GVL flexibility, optional Disclosed Vendors enforcement), use the GDPR::IAB::TCFv2::Validator companion class instead of stringing the predicates above together by hand:

use GDPR::IAB::TCFv2::Validator;

my $validator = GDPR::IAB::TCFv2::Validator->new(
    vendor_id                       => 284,
    consent_purpose_ids             => [ 1, 3 ],
    legitimate_interest_purpose_ids => [ 7 ],
);

my $tc_string = '...';
my $result = $validator->validate($tc_string);   # fail-fast
# ...or $validator->validate_all($tc_string) to accumulate every reason

if ($result) {
    # vendor 284 has every required permission
}
else {
    warn "compliance failed: $result\n";   # stringifies to the reasons
    warn $_ for $result->reasons;
}

DESCRIPTION

GDPR::IAB::TCFv2::Parser is the single-pass bit-stream decoder for TCF v2.3 (and v2.0/v2.2) consent strings, as specified by the IAB. It is normally constructed via the GDPR::IAB::TCFv2 hub (GDPR::IAB::TCFv2->Parse(...)) but can be called directly when you want to bypass the hub's facade.

The returned object exposes every accessor, predicate, and JSON serializer documented below.

CONSTRUCTOR

Parse

The Parse method will decode and validate a base64 encoded version of the tcf v2 string.

Will return a GDPR::IAB::TCFv2::Parser immutable object that allow easy access to different properties.

Will die if can't decode the string.

use GDPR::IAB::TCFv2::Parser;

my $consent = GDPR::IAB::TCFv2::Parser->Parse(
    'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA'
);

or

use GDPR::IAB::TCFv2::Parser;

my $consent = GDPR::IAB::TCFv2::Parser->Parse(
    'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA',
    json => {
        verbose        => 0,
        compact        => 1,
        use_epoch      => 0,
        boolean_values => [ 0, 1 ],
        date_format    => '%Y%m%d',    # yyymmdd
    },
    strict => 1,
    prefetch => 284,
);

Parse may receive an optional hash with the following parameters:

  • On strict mode we will validate if the version of the consent string is the version 2 (or die with an exception).

    Additionally, for TCF v2.3 strings (Policy Version 5+), strict mode will enforce that the Disclosed Vendors segment is present.

    The strict mode is disabled by default.

  • The prefetch option receives one (as scalar) or more (as arrayref) vendor ids.

    This is useful when parsing a range based consent string, since we need to visit all ranges to find a particular id.

  • json is hashref with the following properties used to customize the json format:

    • verbose changes the json encoding. By default we omit some false values such as vendor_consents to create a compact json representation. With verbose we will present everything. See "TO_JSON" for more details.

    • compact changes the json encoding. All fields that are a mapping of something to a boolean will be changed to an array of all elements keys where the value is true. This affects the following fields: special_features_opt_in, purpose/consents, purpose/legitimate_interests, vendor/consents and vendor/legitimate_interests. See "TO_JSON" for more details.

    • use_epoch changes the json encode. By default we format the created and last_updated are converted to string using ISO_8601. With use_epoch we will return the unix epoch in seconds. See "TO_JSON" for more details.

    • boolean_values if present, expects an arrayref if two elements: the false and the true values to be used in json encoding. If omit, we will try to use JSON::false and JSON::true if the package JSON is available, else we will fallback to 0 and 1.

    • date_format if present accepts two kinds of value: an string (to be used on POSIX::strftime) or a code reference to a subroutine that will be called with two arguments: epoch in seconds and nanoseconds. If omitted the format ISO_8601 will be used except if the option use_epoch is true.

    • vendor_id if present, filters the JSON output to only include data for the specific vendor ID. This affects the vendor and publisher/restrictions sections, drastically reducing the size of the output.

METHODS

tc_string

Returns the original consent string.

The consent object GDPR::IAB::TCFv2::Parser will call this method on string interpolations.

version

Version number of the encoding format. The value is 2 for this format.

created

Epoch time format when TC String was created in numeric format. You can easily parse with DateTime if needed.

On scalar context it returns epoch in seconds. On list context it returns epoch in seconds and nanoseconds.

use GDPR::IAB::TCFv2::Parser;
use Test::More tests => 3;

my $consent = GDPR::IAB::TCFv2::Parser->Parse(
    'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA.argAC0gAAAAAAAAAAAA'
);

is $consent->created, 1228644257,
  'should return the creation epoch 07/12/2008';

my ( $seconds, $nanoseconds ) = $consent->created;
is $seconds, 1228644257,
    'should return the creation epoch 07/12/2008 on list context';
is $nanoseconds, 700000000,
    'should return the 700000000 nanoseconds of epoch on list context';

last_updated

Epoch time format when TC String was last updated in numeric format. You can easily parse with DateTime if needed.

On scalar context it returns epoch in seconds. On list context it returns epoch in seconds and nanoseconds, like the created

cmp_id

Consent Management Platform ID that last updated the TC String. Is a unique ID will be assigned to each Consent Management Platform.

cmp_version

Consent Management Platform version of the CMP that last updated this TC String. Each change to a CMP should increment their internally assigned version number as a record of which version the user gave consent and transparency was established.

CMP Screen number at which consent was given for a user with the CMP that last updated this TC String. The number is a CMP internal designation and is CmpVersion specific. The number is used for identifying on which screen a user gave consent as a record.

Two-letter ISO 639-1 language code in which the CMP UI was presented.

vendor_list_version

Number corresponds to GVL vendorListVersion. Version of the GVL used to create this TC String.

policy_version

Version of policy used within GVL.

From the corresponding field in the GVL that was used for obtaining consent.

is_v22_plus

Returns true if the TC string uses Policy Version 4 or higher (TCF v2.2+).

is_v23

Returns true if the TC string uses Policy Version 5 or higher (TCF v2.3).

is_service_specific

This field must always have the value of 1. When a Vendor encounters a TC String with is_service_specific=0 then it is considered invalid.

use_non_standard_stacks

If true, CMP used non-IAB standard texts during consent gathering.

Setting this to 1 signals to Vendors that a private CMP has modified standard Stack descriptions and/or their translations and/or that a CMP has modified or supplemented standard Illustrations and/or their translations as allowed by the policy..

is_special_feature_opt_in

If true means Opt in.

The TCF Policies designates certain Features as "special" which means a CMP must afford the user a means to opt in to their use. These "Special Features" are published and numerically identified in the Global Vendor List separately from normal Features.

See also: GDPR::IAB::TCFv2::Constants::SpecialFeature.

If true means Consent.

The user's consent value for each Purpose established on the legal basis of consent. Accepts one or more Purpose IDs. Returns true if all Purposes have consent.

my $ok = $instance->is_purpose_consent_allowed(1);
my $all_ok = $instance->is_purpose_consent_allowed(1, 2, 3);

Throws an exception if no arguments are provided or if an ID is invalid.

See also: GDPR::IAB::TCFv2::Constants::Purpose.

is_purpose_legitimate_interest_allowed

The user's consent value for each Purpose established on the legal basis of legitimate interest. Accepts one or more Purpose IDs. Returns true if all Purposes have legitimate interest.

my $ok = $instance->is_purpose_legitimate_interest_allowed(1);
my $all_ok = $instance->is_purpose_legitimate_interest_allowed(1, 2, 3);

Throws an exception if no arguments are provided or if an ID is invalid.

See also: GDPR::IAB::TCFv2::Constants::Purpose.

purpose_one_treatment

CMPs can use the publisher_country_code field to indicate the legal jurisdiction the publisher is under to help vendors determine whether the vendor needs consent for Purpose 1.

Returns true if Purpose 1 was NOT disclosed at all.

Returns false if Purpose 1 was disclosed commonly as consent as expected by the Policies.

publisher_country_code

Two-letter ISO 639-1 language code of the country that determines legislation of reference. Commonly, this corresponds to the country in which the publisher's business entity is established.

The maximum Vendor ID that is represented in the following bit field or range encoding.

Because this section can be a variable length, this indicates the last ID of the section so that a decoder will know when it has reached the end.

If true, vendor has consent.

The consent value for each Vendor ID.

my $ok = $instance->vendor_consent(284); # if true, consent ok for Weborama (vendor id 284).

max_vendor_id_legitimate_interest

The maximum Vendor ID that is represented in the following bit field or range encoding.

Because this section can be a variable length, this indicates the last ID of the section so that a decoder will know when it has reached the end.

vendor_legitimate_interest

If true, legitimate interest established.

The legitimate interest value for each Vendor ID

my $ok = $instance->vendor_legitimate_interest(284); # if true, legitimate interest established for Weborama (vendor id 284).

disclosed_vendor

If true, the vendor was disclosed to the user (Segment 1 or 5).

say "Vendor 284 was disclosed" if $consent->disclosed_vendor(284);

has_vendor_disclosure

Returns true (1) if the TC string contains a "Disclosed Vendors" segment (ID 1 or 5), and false (0) otherwise.

allowed_vendor

If true, the vendor is in the "Allowed Vendors" segment (Segment 2). This is typically used for service-specific TC strings.

say "Vendor 284 is allowed" if $consent->allowed_vendor(284);

Check if a vendor has consent for a list of purposes, respecting publisher restrictions.

if ($consent->is_vendor_consent_allowed(284, 1, 2, 3)) {
    # ...
}

is_vendor_legitimate_interest_allowed

Check if a vendor has legitimate interest for a list of purposes, respecting publisher restrictions.

if ($consent->is_vendor_legitimate_interest_allowed(284, 2, 4)) {
    # ...
}

is_vendor_allowed_for_any_basis

Check if a vendor has either consent or legitimate interest for a list of purposes, respecting publisher restrictions.

if ($consent->is_vendor_allowed_for_any_basis(284, 1, 2)) {
    # ...
}

has_publisher_restrictions

Returns true (1) if the TC string contains a "Publisher Restrictions" section, and false (0) otherwise.

check_publisher_restriction

It true, there is a publisher restriction of certain type, for a given purpose id, for a given vendor id:

# return true if there is publisher restriction to vendor 284 regarding purpose id 1
# with restriction type 0 'Purpose Flatly Not Allowed by Publisher'
my $ok = $instance->check_publisher_restriction(1, 0, 284);

or

my $ok = $instance->check_publisher_restriction(
    purpose_id       => 1,
    restriction_type => 0,
    vendor_id        => 284);

Version 2.0 of the Framework introduced the ability for publishers to signal restrictions on how vendors may process personal data. Restrictions can be of two types:

  • Purposes. Restrict the purposes for which personal data is processed by a vendor.

  • Legal basis. Specify the legal basis upon which a publisher requires a vendor to operate where a vendor has signaled flexibility on legal basis in the GVL.

Publisher restrictions are custom requirements specified by a publisher. In order for vendors to determine if processing is permissible at all for a specific purpose or which legal basis is applicable (in case they signaled flexibility in the GVL) restrictions must be respected.

  1. Vendors must always respect a restriction signal that disallows them the processing for a specific purpose regardless of whether or not they have declared that purpose to be "flexible".

  2. Vendors that declared a purpose with a default legal basis (consent or legitimate interest respectively) but also declared this purpose as flexible must respect a legal basis restriction if present. That means for example in case they declared a purpose as legitimate interest but also declared that purpose as flexible and there is a legal basis restriction to require consent, they must then check for the consent signal and must not apply the legitimate interest signal.

For the avoidance of doubt:

In case a vendor has declared flexibility for a purpose and there is no legal basis restriction signal it must always apply the default legal basis under which the purpose was registered aside from being registered as flexible. That means if a vendor declared a purpose as legitimate interest and also declared that purpose as flexible it may not apply a "consent" signal without a legal basis restriction signal to require consent.

is_vendor_allowed_for_flexible_purpose

Check if a vendor is allowed for a flexible purpose, given a default legal basis (true if Legitimate Interest, false if Consent).

if ($consent->is_vendor_allowed_for_flexible_purpose(284, 2, 1)) {
    # vendor 284, purpose 2, default is LI
}

publisher_restrictions

Similar to "check_publisher_restriction" but return an hashref of purpose => { restriction type => bool } for a given vendor.

publisher_tc

If the consent string has a Publisher TC section, we will decode this section as an instance of GDPR::IAB::TCFv2::PublisherTC.

Will return undefined if there is no Publisher TC section.

TO_JSON

Will serialize the consent object into a hash reference. The objective is to be used by JSON package.

With option convert_blessed, the encoder will call this method.

use warnings;
use feature qw<say>;

use JSON;
use DateTime;
use DateTimeX::TO_JSON formatter => 'DateTime::Format::RFC3339';
use GDPR::IAB::TCFv2::Parser;

my $consent = GDPR::IAB::TCFv2::Parser->Parse(
    'COyiILmOyiILmADACHENAPCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAAAAAA.argAC0gAAAAAAAAAAAA',
    json => {
        compact     => 1,
        date_format => sub { # can be omitted, with DateTimeX::TO_JSON
            my ( $epoch, $ns ) = @_;

            return DateTime->from_epoch( epoch => $epoch )
            ->set_nanosecond($ns);
        },
    },
);

my $json    = JSON->new->convert_blessed;
my $encoded = $json->pretty->encode($consent);

say $encoded;

Outputs:

{
    "tc_string" : "COyiILmOyiILmADACHENAPCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAAAAAA",
    "consent_language" : "EN",
    "purpose" : {
        "consents" : [],
        "legitimate_interests" : []
    },
    "cmp_id" : 3,
    "purpose_one_treatment" : false,
    "publisher" : {
        "consents" : [
            2,
            4,
            6,
            8,
            9,
            10
        ],
        "legitimate_interests" : [
            2,
            4,
            5,
            7,
            10
        ],
        "custom_purposes" : {
            "consents" : [],
            "legitimate_interests" : []
        },
        "restrictions" : {}
    },
    "special_features_opt_in" : [],
    "last_updated" : "2020-04-27T20:27:54.200000000Z",
    "use_non_standard_stacks" : false,
    "policy_version" : 2,
    "version" : 2,
    "is_service_specific" : false,
    "created" : "2020-04-27T20:27:54.200000000Z",
    "consent_screen" : 7,
    "vendor_list_version" : 15,
    "cmp_version" : 2,
    "publisher_country_code" : "AA",
    "vendor" : {
        "consents" : [
            23,
            42,
            126,
            127,
            128,
            587,
            613,
            626
        ],
        "legitimate_interests" : []
    }
}

If JSON is installed, the "TO_JSON" method will use JSON::true and JSON::false as boolean value.

By default it returns a compacted format where we omit the false on fields like vendor_consents and we convert the dates using ISO_8601. This behaviour can be changed by extra option in the Parse constructor.

SEE ALSO

GDPR::IAB::TCFv2 for the documentation hub and project overview.

GDPR::IAB::TCFv2::Validator for declarative compliance checks.

GDPR::IAB::TCFv2::CMPValidator for CMP-list validation.

iabtcfv2 for one-liner / shell-use shortcuts.

The original IAB documentation of TCF v2.