Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug 1879792 - Add a cookie banner to BMO #2306

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
34 changes: 33 additions & 1 deletion Bugzilla/CGI.pm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use Bugzilla::Util;
use Bugzilla::Search::Recent;

use File::Basename;
use List::Util qw(any none);
use URI;

BEGIN {
Expand Down Expand Up @@ -493,7 +494,7 @@ sub param {

sub _fix_utf8 {
my $input = shift;

# The is_utf8 is here in case CGI gets smart about UTF-8 someday.
utf8::decode($input) if defined $input && !ref $input && !utf8::is_utf8($input);
return $input;
Expand All @@ -514,6 +515,16 @@ sub should_set {
sub send_cookie {
my ($self, %paramhash) = @_;

# We check to see if the cookie be set is essential and if
# not we check to see if the user has given consent to set it
if (Bugzilla->params->{cookie_consent_enabled}
&& $self->cookie_consent_required
&& !$self->cookie_consented
)
{
return undef if none { $_ eq $paramhash{'-name'} } ESSENTIAL_COOKIES;
}

# Complain if -value is not given or empty (bug 268146).
if (!exists($paramhash{'-value'}) || !$paramhash{'-value'}) {
ThrowCodeError('cookies_need_value');
Expand Down Expand Up @@ -666,6 +677,27 @@ sub set_dated_content_disp {
$self->{'_content_disp'} = $disposition;
}

# Return true/false if a user has consent to non-essential cookies
# 1. If cookie is not present then no consent
# 2. If cookie is present and equal to 'yes' then we have consent
# 3. Any other value we do not have consent
sub cookie_consented {
my ($self) = @_;
return 0 if !defined $self->cookie(CONSENT_COOKIE);
return 1 if $self->cookie(CONSENT_COOKIE) eq 'yes';
return 0; # Anything other than yes is a no
dklawren marked this conversation as resolved.
Show resolved Hide resolved
}

# Return true if client is accessing this site
# from within a required consent country
sub cookie_consent_required {
my ($self) = @_;
return 1 if $ENV{CI};
my $client_region = $self->http('X-Client-Region') || '';
return 1 if any { $client_region eq $_ } COOKIE_CONSENT_COUNTRIES;
return 0;
}

##########################
# Vars TIEHASH Interface #
##########################
Expand Down
6 changes: 6 additions & 0 deletions Bugzilla/Config/Admin.pm
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ sub get_param_list {
type => 't',
default => 'https://product-details.mozilla.org/1.0',
},

dklawren marked this conversation as resolved.
Show resolved Hide resolved
{
name => 'cookie_consent_enabled',
type => 'b',
default => 0,
}
);
return @param_list;
}
Expand Down
26 changes: 18 additions & 8 deletions Bugzilla/Constants.pm
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ use Memoize;
JOB_QUEUE_VIEW_MAX_JOBS

BOUNCE_COUNT_MAX

CONSENT_COOKIE
ESSENTIAL_COOKIES
COOKIE_CONSENT_COUNTRIES
);

@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
Expand Down Expand Up @@ -678,6 +682,19 @@ use constant JOB_QUEUE_VIEW_MAX_JOBS => 2500;
# before the account is completely disabled.
use constant BOUNCE_COUNT_MAX => 5;

# Consent cookie name
use constant CONSENT_COOKIE => 'moz-consent-pref';

# List of essential cookies that cannot be opted out
use constant ESSENTIAL_COOKIES =>
qw(bugzilla Bugzilla_login Bugzilla_logincookie Bugzilla_login_request_cookie
bugzilla github_state github_token sudo moz-consent-pref);

# List of countries the require cookie consent
dklawren marked this conversation as resolved.
Show resolved Hide resolved
use constant COOKIE_CONSENT_COUNTRIES => qw(
AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IS IT LV
LI LT LU MT NL NO PL PT RO SK SI ES SE CH GB );

sub bz_locations {

# Force memoize() to re-compute data per project, to avoid
Expand Down Expand Up @@ -753,7 +770,7 @@ sub DEFAULT_CSP {
my %policy = (
default_src => ['self'],
script_src =>
['self', 'nonce', 'unsafe-inline', 'https://www.google-analytics.com'],
['self', 'nonce', 'unsafe-inline'],
frame_src => [
# This is for extensions/BMO/web/js/firefox-crash-table.js
'https://crash-stop-addon.herokuapp.com',
Expand All @@ -768,9 +785,6 @@ sub DEFAULT_CSP {
# This is for extensions/BMO/web/js/firefox-crash-table.js
'https://product-details.mozilla.org',

# This is for extensions/GoogleAnalytics using beacon or XHR
'https://www.google-analytics.com',

# This is from extensions/OrangeFactor/web/js/orange_factor.js
'https://treeherder.mozilla.org/api/failurecount/',

Expand Down Expand Up @@ -813,7 +827,6 @@ sub SHOW_BUG_MODAL_CSP {
script_src => [
'self', 'nonce',
'unsafe-inline', 'unsafe-eval',
'https://www.google-analytics.com'
],
img_src => ['self', 'data:', 'https://secure.gravatar.com'],
media_src => ['self'],
Expand All @@ -823,9 +836,6 @@ sub SHOW_BUG_MODAL_CSP {
# This is for extensions/BMO/web/js/firefox-crash-table.js
'https://product-details.mozilla.org',

# This is for extensions/GoogleAnalytics using beacon or XHR
'https://www.google-analytics.com',

# This is from extensions/OrangeFactor/web/js/orange_factor.js
'https://treeherder.mozilla.org/api/failurecount/',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"js/bug.js", # Possible Duplicates table
"js/attachment.js",
"extensions/BugModal/web/create.js",
"js/util.js"
]
style_urls = [
"skins/standard/attachment.css",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"extensions/ComponentWatching/web/js/overlay.js",
"js/bugzilla-readable-status-min.js",
"js/field.js",
"js/comments.js"
"js/comments.js",
"js/util.js"
);
jquery.push(
"contextMenu",
Expand Down Expand Up @@ -116,7 +117,8 @@
remember_collapsed: [% user.settings.ui_remember_collapsed.value == "on" ? "true" : "false" %],
inline_attachments: [% user.settings.inline_attachments.value == "on" ? "true" : "false" %],
autosize_comments: [% user.settings.autosize_comments.value == "on" ? "true" : "false" %]
}
},
cookie_consent: [% Bugzilla.cgi.consent_cookie ? "true" : "false" %]
};
[% IF user.id %]
BUGZILLA.default_assignee = '[% bug.component_obj.default_assignee.login FILTER js %]';
Expand Down
22 changes: 11 additions & 11 deletions extensions/BugModal/web/bug_modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function slide_module(module, action, fast) {
'aria-label': is_visible ? latch.data('label-expanded') : latch.data('label-collapsed'),
});
if (BUGZILLA.user.settings.remember_collapsed && module.is(':visible'))
localStorage.setItem(module.attr('id') + '.visibility', is_visible ? 'show' : 'hide');
Bugzilla.Storage.set(module.attr('id') + '.visibility', is_visible ? 'show' : 'hide');
dklawren marked this conversation as resolved.
Show resolved Hide resolved
}

if (action == 'show') {
Expand All @@ -43,7 +43,7 @@ function init_module_visibility() {
var id = that.attr('id');
if (!id) return;
if (that.data('non-stick')) return;
var stored = localStorage.getItem(id + '.visibility');
var stored = Bugzilla.Storage.get(id + '.visibility');
if (stored) {
slide_module(that, stored, true);
}
Expand Down Expand Up @@ -139,7 +139,7 @@ $(function() {
// restore edit mode after navigating back
function restoreEditMode() {
if (!$('#editing').val()) {
if (localStorage.getItem('modal-perm-edit-mode') === 'true') {
if (Bugzilla.Storage.get('modal-perm-edit-mode') === 'true') {
$('#mode-btn').click();
$('#action-enable-perm-edit').attr('aria-checked', 'true');
}
Expand Down Expand Up @@ -170,7 +170,7 @@ $(function() {
text: text,
savedAt: Date.now()
};
localStorage.setItem(bugCommentCacheKey, JSON.stringify(value));
Bugzilla.Storage.set(bugCommentCacheKey, JSON.stringify(value));
}

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

/**
Expand All @@ -200,7 +200,7 @@ $(function() {

function restoreSavedBugComment() {
expireSavedComments();
let value = JSON.parse(localStorage.getItem(bugCommentCacheKey));
let value = Bugzilla.Storage.get(bugCommentCacheKey);
dklawren marked this conversation as resolved.
Show resolved Hide resolved
if (value){
let commentBox = document.querySelector("textarea#comment");
commentBox.value = value['text'];
Expand All @@ -213,10 +213,10 @@ $(function() {
function expireSavedComments() {
const AGE_THRESHOLD = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds.
let expiredKeys = [];
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
for (let i = 0; i < window.localStorage.length; i++) {
let key = window.localStorage.key(i);
if (key.match(/^bug-modal-saved-comment-/)) {
let value = JSON.parse(localStorage.getItem(key));
let value = Bugzilla.Storage.get(key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out-of-scope nit: I don't really love the implicit JSON handling, it would be better to have a get_json method that handles JSON values instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is an issue as the Bugzilla.Storage class is handling the JSON parsing and stringification behind the scenes now. So the caller will use Bugzilla.Storage.get and the value returned will be a valid JSON object or a single value. So the caller will just need to check of value is typeof object if it is not sure. Bugzilla.Storage.set looks to see if the value passed is and object and if so, it will stringify it as JSON before storing.

let savedAt = value['savedAt'] || 0;
let age = Date.now() - savedAt;
if (age < 0 || age > AGE_THRESHOLD) {
Expand All @@ -225,7 +225,7 @@ $(function() {
}
}
expiredKeys.forEach((key) => {
localStorage.removeItem(key);
Bugzilla.Storage.delete(key);
});
}

Expand Down Expand Up @@ -532,7 +532,7 @@ $(function() {
event.preventDefault();
const enabled = $(this).attr('aria-checked') !== 'true';
$(this).attr('aria-checked', enabled);
localStorage.setItem('modal-perm-edit-mode', enabled);
Bugzilla.Storage.set('modal-perm-edit-mode', enabled);
});

// reset
Expand Down
4 changes: 2 additions & 2 deletions extensions/BugModal/web/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ window.addEventListener('DOMContentLoaded', () => {
$toggleAdvanced.textContent = $toggleAdvanced.dataset[advancedStateStr];

if (cache) {
window.localStorage.setItem('create-form.advanced', advancedStateStr);
Bugzilla.Storage.set('create-form.advanced', advancedStateStr);
}
};

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

if (showAdvanced) {
Expand Down
16 changes: 0 additions & 16 deletions extensions/GoogleAnalytics/Config.pm

This file was deleted.

23 changes: 0 additions & 23 deletions extensions/GoogleAnalytics/Extension.pm

This file was deleted.

38 changes: 0 additions & 38 deletions extensions/GoogleAnalytics/lib/Config.pm

This file was deleted.

This file was deleted.

Loading