Skip to content

Commit

Permalink
Merge branch 'release/0.200'
Browse files Browse the repository at this point in the history
  • Loading branch information
peczenyj committed Dec 17, 2023
2 parents 9058e3d + a7126a3 commit 3dc9877
Show file tree
Hide file tree
Showing 15 changed files with 2,571 additions and 563 deletions.
8 changes: 8 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
0.200
- refactor constants, stop use dualvars
- validate parameters on method check_publisher_restriction
- add method publisher_restrictions by vendor id
- add prefetch option to cache vendor ids when the consent string is range based.
It is 2x faster check consent on a prefetched vendor than a regular one if the consent is range based.
Without increase the Parsing time.

0.100
- parse publisher tc section if available
- add strict mode (disabled by default) to validate the consent string version
Expand Down
7 changes: 4 additions & 3 deletions MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ lib/GDPR/IAB/TCFv2/PublisherTC.pm
lib/GDPR/IAB/TCFv2/RangeSection.pm
LICENSE
Makefile.PL
MANIFEST This list of files
MANIFEST
README.pod
t/00-load.t
t/01-parse.t
t/02-json.t
t/03-bugs.t
t/02-json-bitfield.t
t/03-json-range.t
t/90-bugs.t
t/99-pod.t
38 changes: 33 additions & 5 deletions README.pod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ GDPR::IAB::TCFv2 - Transparency & Consent String version 2 parser

=head1 VERSION

Version 0.100
Version 0.200

=head1 SYNOPSIS

Expand Down Expand Up @@ -114,14 +114,33 @@ or
date_format => '%Y%m%d', # yyymmdd
},
strict => 1,
prefetch => 284,
);

Parse may receive an optional hash of parameters: C<strict> (boolean) and C<json> (hashref with the following properties):
Parse may receive an optional hash with the following parameters:

=over

=item *

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

The C<strict> mode is disabled by default.

=item *

The C<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.

=item *

C<json> is hashref with the following properties used to customize the json format:

=over

=item *

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

Expand Down Expand Up @@ -150,9 +169,7 @@ except if the option C<use_epoch> is true.

=back

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

The C<strict> mode is disabled by default.
=back

=head1 METHODS

Expand Down Expand Up @@ -308,6 +325,13 @@ It true, there is a publisher restriction of certain type, for a given purpose i
# 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:

=over
Expand Down Expand Up @@ -340,6 +364,10 @@ 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.

=head2 publisher_restrictions

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

=head2 publisher_tc

If the consent string has a C<Publisher TC> section, we will decode this section as an instance of L<GDPR::IAB::TCFv2::PublisherTC>.
Expand Down
82 changes: 68 additions & 14 deletions lib/GDPR/IAB/TCFv2.pm
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use GDPR::IAB::TCFv2::BitUtils qw<is_set
use GDPR::IAB::TCFv2::Publisher;
use GDPR::IAB::TCFv2::RangeSection;

our $VERSION = "0.100";
our $VERSION = "0.200";

use constant {
CONSENT_STRING_TCF_V2 => {
Expand Down Expand Up @@ -106,6 +106,14 @@ sub Parse {
$options{json}->{date_format} ||= DATE_FORMAT_ISO_8601;
$options{json}->{boolean_values} ||= [ _json_false(), _json_true() ];

if ( exists $opts{prefetch} ) {
my $prefetch = $opts{prefetch};

$prefetch = [$prefetch] if ref($prefetch) ne ref( [] );

$options{prefetch} = $prefetch;
}

my $self = {
core_data => $segments->{core_data},
publisher_tc_data => $segments->{publisher_tc},
Expand Down Expand Up @@ -325,10 +333,28 @@ sub vendor_legitimate_interest {
}

sub check_publisher_restriction {
my ( $self, $purpose_id, $restrict_type, $vendor ) = @_;
my $self = shift;

my ( $purpose_id, $restriction_type, $vendor_id );

if ( scalar(@_) == 6 ) {
my (%opts) = @_;

$purpose_id = $opts{purpose_id};
$restriction_type = $opts{restriction_type};
$vendor_id = $opts{vendor_id};
}

( $purpose_id, $restriction_type, $vendor_id ) = @_;

return $self->{publisher}
->check_restriction( $purpose_id, $restrict_type, $vendor );
->check_restriction( $purpose_id, $restriction_type, $vendor_id );
}

sub publisher_restrictions {
my ( $self, $vendor_id ) = @_;

return $self->{publisher}->restrictions($vendor_id);
}

sub publisher_tc {
Expand Down Expand Up @@ -493,10 +519,10 @@ sub _parse_vendor_section {

# parse vendor legitimate interest

my $pub_restrict_offset =
my $pub_restriction_offset =
$self->_parse_vendor_legitimate_interests($legitimate_interest_offset);

return $pub_restrict_offset;
return $pub_restriction_offset;
}

sub _parse_vendor_consents {
Expand All @@ -515,22 +541,22 @@ sub _parse_vendor_consents {
sub _parse_vendor_legitimate_interests {
my ( $self, $legitimate_interest_offset ) = @_;

my ( $vendor_legitimate_interests, $pub_restrict_offset ) =
my ( $vendor_legitimate_interests, $pub_restriction_offset ) =
$self->_parse_bitfield_or_range(
$legitimate_interest_offset,
);

$self->{vendor_legitimate_interests} = $vendor_legitimate_interests;

return $pub_restrict_offset;
return $pub_restriction_offset;
}

sub _parse_publisher_section {
my ( $self, $pub_restrict_offset ) = @_;
my ( $self, $pub_restriction_offset ) = @_;

# parse public restrictions

my $core_data = substr( $self->{core_data}, $pub_restrict_offset );
my $core_data = substr( $self->{core_data}, $pub_restriction_offset );
my $core_data_size = length( $self->{core_data} );

my $publisher = GDPR::IAB::TCFv2::Publisher->Parse(
Expand Down Expand Up @@ -652,7 +678,7 @@ GDPR::IAB::TCFv2 - Transparency & Consent String version 2 parser
=head1 VERSION
Version 0.100
Version 0.200
=head1 SYNOPSIS
Expand Down Expand Up @@ -740,14 +766,33 @@ or
date_format => '%Y%m%d', # yyymmdd
},
strict => 1,
prefetch => 284,
);
Parse may receive an optional hash of parameters: C<strict> (boolean) and C<json> (hashref with the following properties):
Parse may receive an optional hash with the following parameters:
=over
=item *
On C<strict> mode we will validate if the version of the consent string is the version 2 (or die with an exception).
The C<strict> mode is disabled by default.
=item *
The C<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.
=item *
C<json> is hashref with the following properties used to customize the json format:
=over
=item *
C<verbose> changes the json encoding. By default we omit some false values such as C<vendor_consents> to create
a compact json representation. With C<verbose> we will present everything. See L</TO_JSON> for more details.
Expand Down Expand Up @@ -776,9 +821,7 @@ except if the option C<use_epoch> is true.
=back
On C<strict> mode we will validate if the version of the consent string is the version 2 (or die with an exception).
The C<strict> mode is disabled by default.
=back
=head1 METHODS
Expand Down Expand Up @@ -934,6 +977,13 @@ It true, there is a publisher restriction of certain type, for a given purpose i
# 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:
=over
Expand Down Expand Up @@ -966,6 +1016,10 @@ 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.
=head2 publisher_restrictions
Similar to L</check_publisher_restriction> but return an hashref of purpose => { restriction type => bool } for a given vendor.
=head2 publisher_tc
If the consent string has a C<Publisher TC> section, we will decode this section as an instance of L<GDPR::IAB::TCFv2::PublisherTC>.
Expand Down
55 changes: 34 additions & 21 deletions lib/GDPR/IAB/TCFv2/Constants/Purpose.pm
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
package GDPR::IAB::TCFv2::Constants::Purpose;
use strict;
use warnings;
use Scalar::Util qw<dualvar>;

require Exporter;
use base qw<Exporter>;

use constant {
InfoStorageAccess =>
dualvar( 1, "Store and/or access information on a device" ),
BasicAdserving => dualvar( 2, "Use limited data to select advertising" ),
PersonalizationProfile =>
dualvar( 3, "Create profiles for personalised advertising" ),
InfoStorageAccess => 1,
BasicAdserving => 2,
PersonalizationProfile => 3,
PersonalizationSelection => 4,
ContentProfile => 5,
ContentSelection => 6,
AdPerformance => 7,
ContentPerformance => 8,
MarketResearch => 9,
DevelopImprove => 10,
SelectContent => 11,
};

use constant PurposeDescription => {
InfoStorageAccess => "Store and/or access information on a device",
BasicAdserving => "Use limited data to select advertising",
PersonalizationProfile => "Create profiles for personalised advertising",
PersonalizationSelection =>
dualvar( 4, "Use profiles to select personalised advertising" ),
ContentProfile => dualvar( 5, "Create profiles to personalise content" ),
ContentSelection =>
dualvar( 6, "Use profiles to select personalised content" ),
AdPerformance => dualvar( 7, "Measure advertising performance" ),
ContentPerformance => dualvar( 8, "Measure content performance" ),
MarketResearch => dualvar(
9,
"Understand audiences through statistics or combinations of data from different sources"
),
DevelopImprove => dualvar( 10, "Develop and improve services" ),
SelectContent => dualvar( 11, "Use limited data to select content" ),
"Use profiles to select personalised advertising",
ContentProfile => "Create profiles to personalise content",
ContentSelection => "Use profiles to select personalised content",
AdPerformance => "Measure advertising performance",
ContentPerformance => "Measure content performance",
MarketResearch =>
"Understand audiences through statistics or combinations of data from different sources",
DevelopImprove => "Develop and improve services",
SelectContent => "Use limited data to select content",
};

our @EXPORT_OK = qw<
Expand All @@ -39,6 +47,7 @@ our @EXPORT_OK = qw<
MarketResearch
DevelopImprove
SelectContent
PurposeDescription
>;
our %EXPORT_TAGS = ( all => \@EXPORT_OK );

Expand All @@ -58,15 +67,15 @@ GDPR::IAB::TCFv2::Constants::Purpose - TCF v2.2 purposes
use feature 'say';
say "Purpose id is ". (0+InfoStorageAccess), ", and it means " . InfoStorageAccess;
say "Purpose id is ", InfoStorageAccess , ", and it means " . PurposeDescription->{InfoStorageAccess};
# Output:
# Purpose id is 1, and it means Store and/or access information on a device
=head1 CONSTANTS
All constants are C<dualvar> (see L<Scalar::Util>).
All constants are integers.
Returns a scalar that has the C<id> in a numeric context and the C<description> in a string context.
To find the description of a given id you can use the hashref L</PurposeDescription>
=head2 InfoStorageAccess
Expand Down Expand Up @@ -289,3 +298,7 @@ A travel magazine has published an article on its website about the new online c
A sports news mobile app has started a new section of articles covering the most recent football games. Each article includes videos hosted by a separate streaming platform showcasing the highlights of each match. If you fast-forward a video, this information may be used to select a shorter video to play next.
=back
=head2 PurposeDescription
Returns a hashref with a mapping between all purpose ids and their description.
Loading

0 comments on commit 3dc9877

Please sign in to comment.