Skip to content

Commit

Permalink
Refactor publisher restrictions (#27)
Browse files Browse the repository at this point in the history
* add named parameters on check_publisher_restriction method

* add new method

* improve test

* Revert "improve test"

This reverts commit ae7274e.

* fix format

* fix pod

* Update Publisher.pm

Remove char

* Update PublisherRestrictions.pm

Remove char

* tidy test

* remove bad chars

* try remove all bad chars
  • Loading branch information
peczenyj authored Dec 17, 2023
1 parent 7f191b7 commit b474750
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 27 deletions.
11 changes: 11 additions & 0 deletions README.pod
Original file line number Diff line number Diff line change
Expand Up @@ -325,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 @@ -357,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
45 changes: 37 additions & 8 deletions lib/GDPR/IAB/TCFv2.pm
Original file line number Diff line number Diff line change
Expand Up @@ -333,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 @@ -501,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 @@ -523,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 @@ -959,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 @@ -991,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
29 changes: 22 additions & 7 deletions lib/GDPR/IAB/TCFv2/Publisher.pm
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,16 @@ sub Parse {
}

sub check_restriction {
my ( $self, $purpose_id, $restrict_type, $vendor ) = @_;
my ( $self, $purpose_id, $restriction_type, $vendor_id ) = @_;

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

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

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

sub publisher_tc {
Expand Down Expand Up @@ -95,7 +101,7 @@ Combines the creation of L<GDPR::IAB::TCFv2::PublisherRestrictions> and L<GDPR::
options => { json => ... },
);
say "there is publisher restriction on purpose id 1, type 0 on vendor 284"
say "there is publisher restriction on purpose id 1, type 0 on vendor_id 284"
if $publisher->check_restriction(1, 0, 284);
=head1 CONSTRUCTOR
Expand Down Expand Up @@ -126,12 +132,21 @@ Key C<options> is the L<GDPR::IAB::TCFv2> options (includes the C<json> field to
=head2 check_restriction
Return true for a given combination of purpose id, restriction type and vendor
Return true for a given combination of purpose id, restriction type and vendor_id
my $purpose_id = 1;
my $restriction_type = 0;
my $vendor = 284;
$ok = $range->check_restriction($purpose_id, $restriction_type, $vendor);
my $vendor_id = 284;
$ok = $publisher->check_restriction($purpose_id, $restriction_type, $vendor_id);
=head2 restrictions
Return a hashref of purpose => { restriction type => bool } for a given vendor id.
Example, by parsing the consent C<COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA> we can generate this.
my $restrictions = $publisher->restrictions(32);
# returns { 7 => { 1 => 1 } }
=head2 publisher_tc
Expand All @@ -155,7 +170,7 @@ Returns a hashref with the following format:
# 0 - Not Allowed
# 1 - Require Consent
# 2 - Require Legitimate Interest
'[vendor id]' => 1,
'[vendor_id id]' => 1,
},
}
}
Expand Down
57 changes: 48 additions & 9 deletions lib/GDPR/IAB/TCFv2/PublisherRestrictions.pm
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,44 @@ sub Parse {
return $self;
}

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

my %restrictions;

foreach my $purpose_id ( keys %{ $self->{restrictions} } ) {
foreach my $restriction_type (
keys %{ $self->{restrictions}->{$purpose_id} } )
{
if ( $self->{restrictions}->{$purpose_id}->{$restriction_type}
->contains($vendor_id) )
{
$restrictions{$purpose_id} ||= {};
$restrictions{$purpose_id}->{$restriction_type} = 1;
}
}
}

return \%restrictions;
}

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

my $nargs = scalar(@_);

croak "missing arguments: purpose id, restriction type and vendor id"
if $nargs == 0;
croak "missing arguments: restriction type and vendor id" if $nargs == 1;
croak "missing argument: vendor id" if $nargs == 2;

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

return 0
unless exists $self->{restrictions}->{$purpose_id}->{$restrict_type};
unless exists $self->{restrictions}->{$purpose_id}->{$restriction_type};

return $self->{restrictions}->{$purpose_id}->{$restrict_type}
->contains($vendor);
return $self->{restrictions}->{$purpose_id}->{$restriction_type}
->contains($vendor_id);
}

sub TO_JSON {
Expand All @@ -86,11 +116,11 @@ sub TO_JSON {

my %purpose_restrictions;

foreach my $restrict_type ( keys %{$restriction_map} ) {
my $vendors = $restriction_map->{$restrict_type}->all;
foreach my $restriction_type ( keys %{$restriction_map} ) {
my $vendors = $restriction_map->{$restriction_type}->all;

foreach my $vendor ( @{$vendors} ) {
$purpose_restrictions{$vendor} = int($restrict_type);
$purpose_restrictions{$vendor} = int($restriction_type);
}
}

Expand Down Expand Up @@ -146,8 +176,17 @@ Return true for a given combination of purpose id, restriction type and vendor
my $purpose_id = 1;
my $restriction_type = 0;
my $vendor = 284;
$ok = $range->check_restriction($purpose_id, $restriction_type, $vendor);
my $vendor_id = 284;
my $ok = $object->check_restriction($purpose_id, $restriction_type, $vendor_id);
=head2 restrictions
Return a hashref of purpose => { restriction type => bool } for a given vendor id.
Example, by parsing the consent C<COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA> we can generate this.
my $restrictions = $object->restrictions(32);
# returns { 7 => { 1 => 1 } }
=head2 TO_JSON
Expand Down
4 changes: 2 additions & 2 deletions t/00-load.t
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ subtest "check interfaces" => sub {
@role_decoder_methods, qw<all>;

can_ok 'GDPR::IAB::TCFv2::PublisherRestrictions', @role_base_methods,
qw<check_restriction>;
qw<check_restriction restrictions>;
can_ok 'GDPR::IAB::TCFv2::Publisher', @role_base_methods,
qw<check_restriction>;
qw<check_restriction restrictions>;
can_ok 'GDPR::IAB::TCFv2::PublisherTC', @role_base_methods,
qw<num_custom_purposes
is_purpose_consent_allowed
Expand Down
44 changes: 43 additions & 1 deletion t/01-parse.t
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,17 @@ subtest "bitfield" => sub {
};

ok !$consent->check_publisher_restriction( 1, 0, 284 ),
"should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";
"should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher' when called with positional parameters";

ok !$consent->check_publisher_restriction(
purpose_id => 1,
restriction_type => 0, vendor_id => 284
),
"should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher' when called with named parameters";

my $restrictions = $consent->publisher_restrictions(284);
is_deeply $restrictions, {},
"should return the restriction purpose id => restriction map type map";

my $publisher_tc = $consent->publisher_tc;

Expand Down Expand Up @@ -438,6 +448,10 @@ subtest "range" => sub {
ok !$consent->check_publisher_restriction( 1, 0, 284 ),
"should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

my $restrictions = $consent->publisher_restrictions(284);
is_deeply $restrictions, {},
"should return the restriction purpose id => restriction map type map";

my $publisher_tc = $consent->publisher_tc;

ok !defined($publisher_tc), "should not return publisher_tc";
Expand Down Expand Up @@ -499,6 +513,10 @@ subtest "range" => sub {
ok !$consent->check_publisher_restriction( 1, 0, 284 ),
"should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

my $restrictions = $consent->publisher_restrictions(284);
is_deeply $restrictions, {},
"should return the restriction purpose id => restriction map type map";

done_testing;
};
done_testing;
Expand Down Expand Up @@ -529,6 +547,14 @@ subtest "check publisher restriction" => sub {
ok !$consent->check_publisher_restriction( 5, 1, 32 ),
"must have publisher restriction to vendor 32 regarding purpose id 5 of type 1 'Require Consent'";

my $restrictions = $consent->publisher_restrictions(284);
is_deeply $restrictions, {},
"should return the restriction purpose id => restriction map type map";

$restrictions = $consent->publisher_restrictions(32);
is_deeply $restrictions, { 7 => { 1 => 1 } },
"should return the restriction purpose id => restriction map type map";

done_testing;
};

Expand Down Expand Up @@ -569,6 +595,18 @@ subtest "check publisher restriction" => sub {
ok $consent->check_publisher_restriction( 2, 1, 32 );
ok !$consent->check_publisher_restriction( 2, 1, 42 );

my $restrictions = $consent->publisher_restrictions(284);
is_deeply $restrictions, {},
"should return the restriction purpose id => restriction map type map";

$restrictions = $consent->publisher_restrictions(32);
is_deeply $restrictions,
{ 1 => { 0 => 1 }, 2 => { 0 => 1, 1 => 1 }, 7 => { 0 => 1, 1 => 1 },
10 => { 0 => 1, 1 => 1 }
},
"should return the restriction purpose id => restriction map type map";


done_testing;
};

Expand All @@ -588,6 +626,10 @@ subtest "check publisher restriction" => sub {
ok !$consent->check_publisher_restriction( 1, 0, 284 ),
"should have no publisher restriction to vendor 284 regarding purpose id 1 of type 0 'Purpose Flatly Not Allowed by Publisher'";

my $restrictions = $consent->publisher_restrictions(284);
is_deeply $restrictions, {},
"should return the restriction purpose id => restriction map type map";

done_testing;
};

Expand Down

0 comments on commit b474750

Please sign in to comment.