Skip to content

Commit 34c4020

Browse files
authored
Bug 1879792 - Add a cookie banner to BMO
1 parent 70544f7 commit 34c4020

31 files changed

+919
-220
lines changed

Bugzilla/CGI.pm

+36-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use Bugzilla::Util;
2222
use Bugzilla::Search::Recent;
2323

2424
use File::Basename;
25+
use List::Util qw(any none);
2526
use URI;
2627

2728
BEGIN {
@@ -493,7 +494,7 @@ sub param {
493494

494495
sub _fix_utf8 {
495496
my $input = shift;
496-
497+
497498
# The is_utf8 is here in case CGI gets smart about UTF-8 someday.
498499
utf8::decode($input) if defined $input && !ref $input && !utf8::is_utf8($input);
499500
return $input;
@@ -514,6 +515,16 @@ sub should_set {
514515
sub send_cookie {
515516
my ($self, %paramhash) = @_;
516517

518+
# We check to see if the cookie be set is essential and if
519+
# not we check to see if the user has given consent to set it
520+
if (Bugzilla->params->{cookie_consent_enabled}
521+
&& $self->cookie_consent_required
522+
&& !$self->cookie_consented
523+
)
524+
{
525+
return undef if none { $_ eq $paramhash{'-name'} } ESSENTIAL_COOKIES;
526+
}
527+
517528
# Complain if -value is not given or empty (bug 268146).
518529
if (!exists($paramhash{'-value'}) || !$paramhash{'-value'}) {
519530
ThrowCodeError('cookies_need_value');
@@ -666,6 +677,30 @@ sub set_dated_content_disp {
666677
$self->{'_content_disp'} = $disposition;
667678
}
668679

680+
# Return true/false if a user has consent to non-essential cookies
681+
# 1. If cookie is not present then no consent
682+
# 2. If cookie is present and equal to 'yes' then we have consent
683+
# 3. Any other value we do not have consent
684+
sub cookie_consented {
685+
my ($self) = @_;
686+
if (defined $self->cookie(CONSENT_COOKIE)
687+
&& $self->cookie(CONSENT_COOKIE) eq 'yes')
688+
{
689+
return 1;
690+
}
691+
return 0;
692+
}
693+
694+
# Return true if client is accessing this site
695+
# from within a required consent country
696+
sub cookie_consent_required {
697+
my ($self) = @_;
698+
return 1 if $ENV{CI};
699+
my $client_region = $self->http('X-Client-Region') || '';
700+
return 1 if any { $client_region eq $_ } COOKIE_CONSENT_COUNTRIES;
701+
return 0;
702+
}
703+
669704
# If a cookie is requested that has been set but not yet stored in the browser,
670705
# then we can return it here. 'X' means the cookie is being removed
671706
sub cookie {

Bugzilla/Config/Admin.pm

+6
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ sub get_param_list {
5353
type => 't',
5454
default => 'https://product-details.mozilla.org/1.0',
5555
},
56+
57+
{
58+
name => 'cookie_consent_enabled',
59+
type => 'b',
60+
default => 0,
61+
}
5662
);
5763
return @param_list;
5864
}

Bugzilla/Constants.pm

+26-8
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ use Memoize;
208208
JOB_QUEUE_VIEW_MAX_JOBS
209209
210210
BOUNCE_COUNT_MAX
211+
212+
CONSENT_COOKIE
213+
ESSENTIAL_COOKIES
214+
COOKIE_CONSENT_COUNTRIES
211215
);
212216

213217
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
@@ -678,6 +682,27 @@ use constant JOB_QUEUE_VIEW_MAX_JOBS => 2500;
678682
# before the account is completely disabled.
679683
use constant BOUNCE_COUNT_MAX => 5;
680684

685+
# Consent cookie name
686+
use constant CONSENT_COOKIE => 'moz-consent-pref';
687+
688+
# List of essential cookies that cannot be opted out
689+
use constant ESSENTIAL_COOKIES => qw(
690+
bugzilla
691+
Bugzilla_login
692+
Bugzilla_logincookie
693+
Bugzilla_login_request_cookie
694+
github_state
695+
github_token
696+
mfa_verification_token
697+
moz-consent-pref
698+
sudo
699+
);
700+
701+
# List of countries that require cookie consent
702+
use constant COOKIE_CONSENT_COUNTRIES => qw(
703+
AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IS IT LV
704+
LI LT LU MT NL NO PL PT RO SK SI ES SE CH GB );
705+
681706
sub bz_locations {
682707

683708
# Force memoize() to re-compute data per project, to avoid
@@ -745,7 +770,7 @@ sub DEFAULT_CSP {
745770
my %policy = (
746771
default_src => ['self'],
747772
script_src =>
748-
['self', 'nonce', 'unsafe-inline', 'https://www.google-analytics.com'],
773+
['self', 'nonce', 'unsafe-inline'],
749774
frame_src => [
750775
# This is for extensions/BMO/web/js/firefox-crash-table.js
751776
'https://crash-stop-addon.herokuapp.com',
@@ -760,9 +785,6 @@ sub DEFAULT_CSP {
760785
# This is for extensions/BMO/web/js/firefox-crash-table.js
761786
'https://product-details.mozilla.org',
762787

763-
# This is for extensions/GoogleAnalytics using beacon or XHR
764-
'https://www.google-analytics.com',
765-
766788
# This is from extensions/OrangeFactor/web/js/orange_factor.js
767789
'https://treeherder.mozilla.org/api/failurecount/',
768790

@@ -805,7 +827,6 @@ sub SHOW_BUG_MODAL_CSP {
805827
script_src => [
806828
'self', 'nonce',
807829
'unsafe-inline', 'unsafe-eval',
808-
'https://www.google-analytics.com'
809830
],
810831
img_src => ['self', 'data:', 'https://secure.gravatar.com'],
811832
media_src => ['self'],
@@ -815,9 +836,6 @@ sub SHOW_BUG_MODAL_CSP {
815836
# This is for extensions/BMO/web/js/firefox-crash-table.js
816837
'https://product-details.mozilla.org',
817838

818-
# This is for extensions/GoogleAnalytics using beacon or XHR
819-
'https://www.google-analytics.com',
820-
821839
# This is from extensions/OrangeFactor/web/js/orange_factor.js
822840
'https://treeherder.mozilla.org/api/failurecount/',
823841
],

docker-compose.test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ services:
2727
- BZ_ANSWERS_FILE=/app/conf/checksetup_answers.txt
2828
- BZ_QA_CONF_FILE=/app/qa/config/selenium_test.conf
2929
- BZ_QA_CONFIG=1
30+
- CI=1
3031
- LOCALCONFIG_ENV=1
3132
- LOG4PERL_CONFIG_FILE=log4perl-test.conf
3233
- LOGGING_PORT=5880

extensions/BugModal/template/en/default/bug/create/create-modal.html.tmpl

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"js/bug.js", # Possible Duplicates table
1616
"js/attachment.js",
1717
"extensions/BugModal/web/create.js",
18+
"js/util.js"
1819
]
1920
style_urls = [
2021
"skins/standard/attachment.css",

extensions/BugModal/template/en/default/bug_modal/header.html.tmpl

+4-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
"extensions/ComponentWatching/web/js/overlay.js",
6565
"js/bugzilla-readable-status-min.js",
6666
"js/field.js",
67-
"js/comments.js"
67+
"js/comments.js",
68+
"js/util.js"
6869
);
6970
jquery.push(
7071
"contextMenu",
@@ -117,7 +118,8 @@
117118
remember_collapsed: [% user.settings.ui_remember_collapsed.value == "on" ? "true" : "false" %],
118119
inline_attachments: [% user.settings.inline_attachments.value == "on" ? "true" : "false" %],
119120
autosize_comments: [% user.settings.autosize_comments.value == "on" ? "true" : "false" %]
120-
}
121+
},
122+
cookie_consent: [% Bugzilla.cgi.consent_cookie ? "true" : "false" %]
121123
};
122124
[% IF user.id %]
123125
BUGZILLA.default_assignee = '[% bug.component_obj.default_assignee.login FILTER js %]';

extensions/BugModal/web/bug_modal.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function slide_module(module, action, fast) {
2121
'aria-label': is_visible ? latch.data('label-expanded') : latch.data('label-collapsed'),
2222
});
2323
if (BUGZILLA.user.settings.remember_collapsed && module.is(':visible'))
24-
localStorage.setItem(module.attr('id') + '.visibility', is_visible ? 'show' : 'hide');
24+
Bugzilla.Storage.set(module.attr('id') + '.visibility', is_visible ? 'show' : 'hide');
2525
}
2626

2727
if (action == 'show') {
@@ -43,7 +43,7 @@ function init_module_visibility() {
4343
var id = that.attr('id');
4444
if (!id) return;
4545
if (that.data('non-stick')) return;
46-
var stored = localStorage.getItem(id + '.visibility');
46+
var stored = Bugzilla.Storage.get(id + '.visibility');
4747
if (stored) {
4848
slide_module(that, stored, true);
4949
}
@@ -139,7 +139,7 @@ $(function() {
139139
// restore edit mode after navigating back
140140
function restoreEditMode() {
141141
if (!$('#editing').val()) {
142-
if (localStorage.getItem('modal-perm-edit-mode') === 'true') {
142+
if (Bugzilla.Storage.get('modal-perm-edit-mode') === 'true') {
143143
$('#mode-btn').click();
144144
$('#action-enable-perm-edit').attr('aria-checked', 'true');
145145
}
@@ -170,7 +170,7 @@ $(function() {
170170
text: text,
171171
savedAt: Date.now()
172172
};
173-
localStorage.setItem(bugCommentCacheKey, JSON.stringify(value));
173+
Bugzilla.Storage.set(bugCommentCacheKey, JSON.stringify(value));
174174
}
175175

176176
/**
@@ -180,7 +180,7 @@ $(function() {
180180
* to take such special cases into account. Otherwise the current bug’s comment cache will be removed.
181181
*/
182182
const clearSavedBugComment = (bug_id = BUGZILLA.bug_id) => {
183-
localStorage.removeItem(`bug-modal-saved-comment-${bug_id}`);
183+
Bugzilla.Storage.delete(`bug-modal-saved-comment-${bug_id}`);
184184
};
185185

186186
/**
@@ -200,7 +200,7 @@ $(function() {
200200

201201
function restoreSavedBugComment() {
202202
expireSavedComments();
203-
let value = JSON.parse(localStorage.getItem(bugCommentCacheKey));
203+
let value = Bugzilla.Storage.get(bugCommentCacheKey);
204204
if (value){
205205
let commentBox = document.querySelector("textarea#comment");
206206
if (commentBox.value === '')
@@ -213,10 +213,10 @@ $(function() {
213213
function expireSavedComments() {
214214
const AGE_THRESHOLD = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds.
215215
let expiredKeys = [];
216-
for (let i = 0; i < localStorage.length; i++) {
217-
let key = localStorage.key(i);
216+
for (let i = 0; i < window.localStorage.length; i++) {
217+
let key = window.localStorage.key(i);
218218
if (key.match(/^bug-modal-saved-comment-/)) {
219-
let value = JSON.parse(localStorage.getItem(key));
219+
let value = Bugzilla.Storage.get(key);
220220
let savedAt = value['savedAt'] || 0;
221221
let age = Date.now() - savedAt;
222222
if (age < 0 || age > AGE_THRESHOLD) {
@@ -225,7 +225,7 @@ $(function() {
225225
}
226226
}
227227
expiredKeys.forEach((key) => {
228-
localStorage.removeItem(key);
228+
Bugzilla.Storage.delete(key);
229229
});
230230
}
231231

@@ -543,7 +543,7 @@ $(function() {
543543
event.preventDefault();
544544
const enabled = $(this).attr('aria-checked') !== 'true';
545545
$(this).attr('aria-checked', enabled);
546-
localStorage.setItem('modal-perm-edit-mode', enabled);
546+
Bugzilla.Storage.set('modal-perm-edit-mode', enabled);
547547
});
548548

549549
// reset

extensions/BugModal/web/create.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ window.addEventListener('DOMContentLoaded', () => {
5959
$toggleAdvanced.textContent = $toggleAdvanced.dataset[advancedStateStr];
6060

6161
if (cache) {
62-
window.localStorage.setItem('create-form.advanced', advancedStateStr);
62+
Bugzilla.Storage.set('create-form.advanced', advancedStateStr);
6363
}
6464
};
6565

@@ -73,7 +73,7 @@ window.addEventListener('DOMContentLoaded', () => {
7373
// Check the local storage or the TUI cookie used on the legacy form to see if the user wants
7474
// to show advanced fields on the bug form.
7575
let showAdvanced =
76-
window.localStorage.getItem('create-form.advanced') === 'show'
76+
Bugzilla.Storage.get('create-form.advanced') === 'show'
7777
|| /\bTUI=\S*?expert_fields=1\b/.test(document.cookie);
7878

7979
if (showAdvanced) {

extensions/GoogleAnalytics/Config.pm

-16
This file was deleted.

extensions/GoogleAnalytics/Extension.pm

-23
This file was deleted.

extensions/GoogleAnalytics/lib/Config.pm

-38
This file was deleted.

extensions/GoogleAnalytics/template/en/default/admin/params/googleanalytics.html.tmpl

-20
This file was deleted.

0 commit comments

Comments
 (0)