From 6a727b70a9f7d3deb690dffd818d7bb5e9eb7bf5 Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Wed, 25 Jan 2017 15:04:07 -0500 Subject: Bug 1286290 - CSP compliant bug modal --- Bugzilla.pm | 7 +++- Bugzilla/CGI.pm | 46 ++++++++++++++++++---- Bugzilla/CGI/ContentSecurityPolicy.pm | 1 + Bugzilla/Template.pm | 5 +++ attachment.cgi | 28 ++++++++++++- .../hook/bug_modal/edit-top_actions.html.tmpl | 4 +- extensions/BMO/web/js/edituser_menu.js | 6 +++ .../template/en/default/bug_modal/edit.html.tmpl | 2 +- .../template/en/default/bug_modal/user.html.tmpl | 7 ++-- extensions/BugModal/web/bug_modal.js | 7 ++++ .../auth/login-small-additional_methods.html.tmpl | 2 +- .../template/en/default/bug/needinfo.html.tmpl | 11 +++--- .../auth/login-additional_methods.html.tmpl | 2 +- .../auth/login-small-additional_methods.html.tmpl | 4 +- .../account/create-additional_methods.html.tmpl | 2 +- .../hook/global/header-additional_header.html.tmpl | 7 +++- index.cgi | 2 + js/global.js | 26 ++++++++++++ post_bug.cgi | 3 ++ process_bug.cgi | 8 ++-- show_bug.cgi | 5 +++ .../en/default/account/auth/login-small.html.tmpl | 12 +++--- template/en/default/bug/process/bugmail.html.tmpl | 37 ++++++++++------- template/en/default/global/common-links.html.tmpl | 4 +- template/en/default/global/header.html.tmpl | 14 +++++-- .../en/default/global/per-bug-queries.html.tmpl | 11 ++++-- template/en/default/global/userselect.html.tmpl | 5 +-- template/en/default/index.html.tmpl | 10 +++-- template/en/default/pages/quicksearch.html.tmpl | 4 +- 29 files changed, 208 insertions(+), 74 deletions(-) diff --git a/Bugzilla.pm b/Bugzilla.pm index f563ba9e3..55e416933 100644 --- a/Bugzilla.pm +++ b/Bugzilla.pm @@ -210,10 +210,13 @@ sub init_page { sub template { # BMO - use metrics subclass if required if (Bugzilla->metrics_enabled) { - return $_[0]->request_cache->{template} ||= Bugzilla::Metrics::Template->create(); + $_[0]->request_cache->{template} ||= Bugzilla::Metrics::Template->create(); } else { - return $_[0]->request_cache->{template} ||= Bugzilla::Template->create(); + $_[0]->request_cache->{template} ||= Bugzilla::Template->create(); } + $_[0]->request_cache->{template}->{_is_main} = 1; + + return $_[0]->request_cache->{template}; } sub template_inner { diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm index 78987ab71..91dec7e72 100644 --- a/Bugzilla/CGI.pm +++ b/Bugzilla/CGI.pm @@ -33,13 +33,41 @@ BEGIN { use constant DEFAULT_CSP => ( default_src => [ 'self' ], - script_src => [ 'self', 'https://login.persona.org', 'unsafe-inline', 'unsafe-eval' ], - child_src => [ 'self', 'https://login.persona.org' ], + script_src => [ 'self', 'unsafe-inline', 'unsafe-eval' ], + child_src => [ 'self', ], img_src => [ 'self', 'https://secure.gravatar.com' ], style_src => [ 'self', 'unsafe-inline' ], - disable => 1, + object_src => [ 'none' ], + form_action => [ + 'self', + # used in template/en/default/search/search-google.html.tmpl + 'https://www.google.com/search' + ], + frame_ancestors => [ 'none' ], + disable => 1, ); +# Because show_bug code lives in many different .cgi files, +# we needed a centralized place to define the policy. +# normally the policy would just live in one .cgi file. +# Additionally, correct_urlbase() cannot be called at compile time, so this can't be a constant. +sub SHOW_BUG_MODAL_CSP { + return ( + script_src => ['self', 'nonce', 'unsafe-inline', 'unsafe-eval' ], + object_src => [correct_urlbase() . "extensions/BugModal/web/ZeroClipboard/ZeroClipboard.swf"], + connect_src => [ + 'self', + # This is from extensions/OrangeFactor/web/js/orange_factor.js + 'https://brasstacks.mozilla.com/orangefactor/api/count', + ], + child_src => [ + 'self', + # This is for the socorro lens addon and is to be removed by Bug 1332016 + 'https://ashughes1.github.io/bugzilla-socorro-lens/chart.htm' + ], + ); +} + sub _init_bz_cgi_globals { my $invocant = shift; # We need to disable output buffering - see bug 179174 @@ -143,9 +171,9 @@ sub content_security_policy { my ($self, %add_params) = @_; if (Bugzilla->has_feature('csp')) { require Bugzilla::CGI::ContentSecurityPolicy; - return $self->{Bugzilla_csp} if $self->{Bugzilla_csp}; - my %params = DEFAULT_CSP; - if (%add_params) { + if (%add_params || !$self->{Bugzilla_csp}) { + my %params = DEFAULT_CSP; + delete $params{disable} if %add_params && !$add_params{disable}; foreach my $key (keys %add_params) { if (defined $add_params{$key}) { $params{$key} = $add_params{$key}; @@ -154,8 +182,10 @@ sub content_security_policy { delete $params{$key}; } } + $self->{Bugzilla_csp} = Bugzilla::CGI::ContentSecurityPolicy->new(%params); } - return $self->{Bugzilla_csp} = Bugzilla::CGI::ContentSecurityPolicy->new(%params); + + return $self->{Bugzilla_csp}; } return undef; } @@ -455,7 +485,7 @@ sub header { $headers{'-x_content_type_options'} = 'nosniff'; my $csp = $self->content_security_policy; - $csp->add_cgi_headers(\%headers) if defined $csp; + $csp->add_cgi_headers(\%headers) if defined $csp && !$csp->disable; Bugzilla::Hook::process('cgi_headers', { cgi => $self, headers => \%headers } diff --git a/Bugzilla/CGI/ContentSecurityPolicy.pm b/Bugzilla/CGI/ContentSecurityPolicy.pm index 74bce6374..022d49b44 100644 --- a/Bugzilla/CGI/ContentSecurityPolicy.pm +++ b/Bugzilla/CGI/ContentSecurityPolicy.pm @@ -37,6 +37,7 @@ my @ALL_SRC = qw( default_src child_src connect_src font_src img_src media_src object_src script_src style_src + frame_ancestors form_action ); has \@ALL_SRC => ( is => 'ro', isa => $SOURCE_LIST, predicate => 1 ); diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index eb1496fca..2887f0138 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -1000,6 +1000,11 @@ sub create { # Currenly active language 'current_language' => sub { return Bugzilla->current_language; }, + 'script_nonce' => sub { + my $cgi = Bugzilla->cgi; + return $cgi->csp_nonce ? sprintf('nonce="%s"', $cgi->csp_nonce) : ''; + }, + # If an sudo session is in progress, this is the user who # started the session. 'sudoer' => sub { return Bugzilla->sudoer; }, diff --git a/attachment.cgi b/attachment.cgi index d5a69f198..d228c9c7f 100755 --- a/attachment.cgi +++ b/attachment.cgi @@ -628,6 +628,14 @@ sub insert { my $recipients = { 'changer' => $user, 'owner' => $owner }; $vars->{'sent_bugmail'} = Bugzilla::BugMail::Send($bugid, $recipients); + # BMO: add show_bug_format hook for experimental UI work + my $show_bug_format = {}; + Bugzilla::Hook::process('show_bug_format', $show_bug_format); + + if ($show_bug_format->{format} eq 'modal') { + $cgi->content_security_policy(Bugzilla::CGI::SHOW_BUG_MODAL_CSP()); + } + print $cgi->header(); # Generate and return the UI (HTML page) from the appropriate template. $template->process("attachment/created.html.tmpl", $vars) @@ -784,6 +792,14 @@ sub update { $vars->{'sent_bugmail'} = Bugzilla::BugMail::Send($bug->id, { 'changer' => $user }); + # BMO: add show_bug_format hook for experimental UI work + my $show_bug_format = {}; + Bugzilla::Hook::process('show_bug_format', $show_bug_format); + + if ($show_bug_format->{format} eq 'modal') { + $cgi->content_security_policy(Bugzilla::CGI::SHOW_BUG_MODAL_CSP()); + } + print $cgi->header(); # Generate and return the UI (HTML page) from the appropriate template. @@ -796,8 +812,6 @@ sub delete_attachment { my $user = Bugzilla->login(LOGIN_REQUIRED); my $dbh = Bugzilla->dbh; - print $cgi->header(); - $user->in_group('admin') || ThrowUserError('auth_failure', {group => 'admin', action => 'delete', @@ -853,6 +867,15 @@ sub delete_attachment { $vars->{'sent_bugmail'} = Bugzilla::BugMail::Send($bug->id, { 'changer' => $user }); + # BMO: add show_bug_format hook for experimental UI work + my $show_bug_format = {}; + Bugzilla::Hook::process('show_bug_format', $show_bug_format); + + if ($show_bug_format->{format} eq 'modal') { + $cgi->content_security_policy(Bugzilla::CGI::SHOW_BUG_MODAL_CSP()); + } + + print $cgi->header(); $template->process("attachment/updated.html.tmpl", $vars) || ThrowTemplateError($template->error()); } @@ -863,6 +886,7 @@ sub delete_attachment { $vars->{'a'} = $attachment; $vars->{'token'} = $token; + print $cgi->header(); $template->process("attachment/confirm-delete.html.tmpl", $vars) || ThrowTemplateError($template->error()); } diff --git a/extensions/BMO/template/en/default/hook/bug_modal/edit-top_actions.html.tmpl b/extensions/BMO/template/en/default/hook/bug_modal/edit-top_actions.html.tmpl index 03c7d2e49..a21e8a441 100644 --- a/extensions/BMO/template/en/default/hook/bug_modal/edit-top_actions.html.tmpl +++ b/extensions/BMO/template/en/default/hook/bug_modal/edit-top_actions.html.tmpl @@ -16,7 +16,7 @@ END; END; %] - diff --git a/extensions/BMO/web/js/edituser_menu.js b/extensions/BMO/web/js/edituser_menu.js index 707e35b6e..7008a2b84 100644 --- a/extensions/BMO/web/js/edituser_menu.js +++ b/extensions/BMO/web/js/edituser_menu.js @@ -45,3 +45,9 @@ function show_usermenu(id, email, show_edit) { }); } +$(function() { + $('.show_usermenu').on("click", function (event) { + var $this = $(this); + return show_usermenu($this.data('user-id'), $this.data('user-email'), $this.data('show-edit')); + }); +}); diff --git a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl index fe0a7d4fe..acdd55ee2 100644 --- a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl @@ -1194,7 +1194,7 @@ [%# === initialise module visibility === %] - diff --git a/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl index 4c28936cc..5c630ba07 100644 --- a/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/user.html.tmpl @@ -41,11 +41,12 @@ END; width="[% gravatar_size FILTER none %]" height="[% gravatar_size FILTER none %]"> [% END %] [% UNLESS gravatar_only %] - + @@ -227,7 +226,7 @@ $(function() { value => "" size => 30 multiple => 5 - onchange => "needinfo_from_changed()" + classes => ["needinfo_from_changed"] field_title => "Enter one or more comma separated users to request more information from" %] diff --git a/extensions/Persona/template/en/default/hook/account/auth/login-additional_methods.html.tmpl b/extensions/Persona/template/en/default/hook/account/auth/login-additional_methods.html.tmpl index c964f9fed..1743db9a6 100644 --- a/extensions/Persona/template/en/default/hook/account/auth/login-additional_methods.html.tmpl +++ b/extensions/Persona/template/en/default/hook/account/auth/login-additional_methods.html.tmpl @@ -1,7 +1,7 @@ [% IF Param('user_info_class').split(',').contains('Persona') && Param('persona_includejs_url') %]

- +

more info). diff --git a/extensions/Persona/template/en/default/hook/account/auth/login-small-additional_methods.html.tmpl b/extensions/Persona/template/en/default/hook/account/auth/login-small-additional_methods.html.tmpl index 5d8503d73..17a86a71d 100644 --- a/extensions/Persona/template/en/default/hook/account/auth/login-small-additional_methods.html.tmpl +++ b/extensions/Persona/template/en/default/hook/account/auth/login-small-additional_methods.html.tmpl @@ -1,6 +1,6 @@ [% IF Param('user_info_class').split(',').contains('Persona') && Param('persona_includejs_url') %] - or + title="Sign in with Persona" class='persona_sign_in'> or [% END %] diff --git a/extensions/Persona/template/en/default/hook/account/create-additional_methods.html.tmpl b/extensions/Persona/template/en/default/hook/account/create-additional_methods.html.tmpl index 355ce3629..b6fb1eedc 100644 --- a/extensions/Persona/template/en/default/hook/account/create-additional_methods.html.tmpl +++ b/extensions/Persona/template/en/default/hook/account/create-additional_methods.html.tmpl @@ -9,5 +9,5 @@ [% RETURN UNLESS Param('user_info_class').split(',').contains('Persona') %] Or, use your Persona account: - diff --git a/extensions/Persona/template/en/default/hook/global/header-additional_header.html.tmpl b/extensions/Persona/template/en/default/hook/global/header-additional_header.html.tmpl index 5b2fa043b..12282df16 100644 --- a/extensions/Persona/template/en/default/hook/global/header-additional_header.html.tmpl +++ b/extensions/Persona/template/en/default/hook/global/header-additional_header.html.tmpl @@ -17,8 +17,8 @@ [% USE Bugzilla %] [% cgi = Bugzilla.cgi %] - - + diff --git a/index.cgi b/index.cgi index 1dd62d9fb..56513aff7 100755 --- a/index.cgi +++ b/index.cgi @@ -33,6 +33,8 @@ if ($cgi->param('logout')) { $cgi->delete('logout'); } +$cgi->content_security_policy(script_src => ['self', 'nonce']); + ############################################################################### # Main Body Execution ############################################################################### diff --git a/js/global.js b/js/global.js index 8ff509289..a997821f4 100644 --- a/js/global.js +++ b/js/global.js @@ -16,6 +16,31 @@ * */ +$(function () { + $('.show_mini_login_form').on("click", function (event) { + return show_mini_login_form($(this).data('qs-suffix')); + }); + $('.hide_mini_login_form').on("click", function (event) { + return hide_mini_login_form($(this).data('qs-suffix')); + }); + $('.show_forgot_form').on("click", function (event) { + return show_forgot_form($(this).data('qs-suffix')); + }); + $('.hide_forgot_form').on("click", function (event) { + return hide_forgot_form($(this).data('qs-suffix')); + }); + $('.check_mini_login_fields').on("click", function (event) { + return check_mini_login_fields($(this).data('qs-suffix')); + }); + $('form .quicksearch_check_empty').on("submit", function (event) { + if (this.quicksearch.value == '') { + alert('Please enter one or more search terms first.'); + return false; + } + return true; + }); +}); + function show_mini_login_form( suffix ) { $('#login_link' + suffix).addClass('bz_default_hidden'); $('#mini_login' + suffix).removeClass('bz_default_hidden'); @@ -37,6 +62,7 @@ function show_forgot_form( suffix ) { return false; } + function hide_forgot_form( suffix ) { $('#forgot_link' + suffix).removeClass('bz_default_hidden'); $('#forgot_form' + suffix).addClass('bz_default_hidden'); diff --git a/post_bug.cgi b/post_bug.cgi index 0975e32ae..bbba125c1 100755 --- a/post_bug.cgi +++ b/post_bug.cgi @@ -264,6 +264,9 @@ $format = $template->get_format("bug/create/created", # don't leak the enter_bug format param to show_bug $cgi->delete('format'); +if ($user->setting('ui_experiments') eq 'on') { + Bugzilla->cgi->content_security_policy(Bugzilla::CGI::SHOW_BUG_MODAL_CSP()); +} print $cgi->header(); $template->process($format->{'template'}, $vars) || ThrowTemplateError($template->error()); diff --git a/process_bug.cgi b/process_bug.cgi index 0858a3ff8..ac8e32c53 100755 --- a/process_bug.cgi +++ b/process_bug.cgi @@ -419,9 +419,12 @@ my $format_params = { ctype => scalar $cgi->param('ctype'), }; Bugzilla::Hook::process('show_bug_format', $format_params); +if ($format_params->{format} eq 'modal') { + $cgi->content_security_policy(Bugzilla::CGI::SHOW_BUG_MODAL_CSP()); +} my $format = $template->get_format("bug/show", - $format_params->{format}, - $format_params->{ctype}); + $format_params->{format}, + $format_params->{ctype}); if (Bugzilla->usage_mode != USAGE_MODE_EMAIL) { print $cgi->header(); @@ -466,5 +469,4 @@ if (Bugzilla->usage_mode != USAGE_MODE_EMAIL) { $template->process("global/footer.html.tmpl", $vars) || ThrowTemplateError($template->error()); } - 1; diff --git a/show_bug.cgi b/show_bug.cgi index 517017688..d4e6ea771 100755 --- a/show_bug.cgi +++ b/show_bug.cgi @@ -19,6 +19,7 @@ use Bugzilla::User; use Bugzilla::Keyword; use Bugzilla::Bug; use Bugzilla::Hook; +use Bugzilla::CGI; my $cgi = Bugzilla->cgi; my $template = Bugzilla->template; @@ -36,6 +37,10 @@ my $format = $template->get_format("bug/show", $format_params->{format}, $format_params->{ctype}); +if ($format_params->{format} eq 'modal') { + $cgi->content_security_policy(Bugzilla::CGI::SHOW_BUG_MODAL_CSP()); +} + # Editable, 'single' HTML bugs are treated slightly specially in a few places my $single = (!$format->{format} || $format->{format} ne 'multiple') && $format->{extension} eq 'html'; diff --git a/template/en/default/account/auth/login-small.html.tmpl b/template/en/default/account/auth/login-small.html.tmpl index 111aca0dd..b182ddef3 100644 --- a/template/en/default/account/auth/login-small.html.tmpl +++ b/template/en/default/account/auth/login-small.html.tmpl @@ -38,14 +38,15 @@ [% END %] [% script_url = login_target _ connector _ "GoAheadAndLogIn=1" %] Log In + class='show_mini_login_form' data-qs-suffix="[% qs_suffix FILTER html %]">Log In [% Hook.process('additional_methods') %]

[x] + class="hide_mini_login_form" data-qs-suffix="[% qs_suffix FILTER html %]">[x]
  • | Forgot Password + class='show_forgot_form' + data-qs-suffix="[% qs_suffix FILTER html %]">Forgot Password
    @@ -92,6 +94,6 @@ type="submit"> - [x] + [x]
  • diff --git a/template/en/default/bug/process/bugmail.html.tmpl b/template/en/default/bug/process/bugmail.html.tmpl index 0c4f2f27d..0e392c760 100644 --- a/template/en/default/bug/process/bugmail.html.tmpl +++ b/template/en/default/bug/process/bugmail.html.tmpl @@ -35,20 +35,25 @@ %] [% recipient_count = sent_bugmail.sent.size %] -
    hide) + (hide) [% ELSE %] (list of e-mails not available) [% END %] @@ -74,7 +80,8 @@ function toggleBugmailRecipients(bug_id, show) { class="[% show_recipients ? "bz_default_hidden" : "" %]"> [% IF recipient_count > 0 %] Email sent to [% recipient_count FILTER html %] recipient[% 's' UNLESS recipient_count == 1 %]. - (show) + (show) [% ELSE %] No emails were sent. [% END %] diff --git a/template/en/default/global/common-links.html.tmpl b/template/en/default/global/common-links.html.tmpl index 50cfa020c..76b0855d8 100644 --- a/template/en/default/global/common-links.html.tmpl +++ b/template/en/default/global/common-links.html.tmpl @@ -31,9 +31,7 @@
  • |
    + class='quicksearch_check_empty'> + @@ -58,7 +62,7 @@ - [% IF user.tags.size %] @@ -81,8 +85,7 @@ [% " or create and add the tag" IF user.tags.size %] + size="20" maxlength="64" name="newqueryname"> to [%+ terms.bugs %] diff --git a/template/en/default/global/userselect.html.tmpl b/template/en/default/global/userselect.html.tmpl index f7dc03d89..5577448fb 100644 --- a/template/en/default/global/userselect.html.tmpl +++ b/template/en/default/global/userselect.html.tmpl @@ -11,7 +11,6 @@ # id: optional; field id # value: optional; default field value/selection # classes: optional; an array of classes to be added - # onchange: optional; onchange attribute value # disabled: optional; if true, the field is disabled # accesskey: optional, input only; accesskey attribute value # size: optional, input only; size attribute value @@ -24,11 +23,12 @@ # mandatory: optional; if true, the field cannot be empty. #%] +[% THROW "onchange is not allowed" IF onchange %] + [% IF Param("usemenuforusers") %] Type in one or more words (or pieces of words) to search for:

    + class='quicksearch_check_empty'>
    -- cgit v1.2.3-24-g4f1b