From 541e2b41af8cc44ad3eb0638618bc457c666d612 Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Sat, 7 Apr 2018 19:20:00 -0400 Subject: a bit of a quantum leap It's now possible to load the CGIs into a mojolicious controller. Compatibility isn't 100% yet, but it should give a migration path for any random CGI to become a proper controller. --- Bugzilla/CGI.pm | 57 ++++--------------------- Bugzilla/CGI/ContentSecurityPolicyAttr.pm | 71 +++++++++++++++++++++++++++++++ Bugzilla/CGI/Mojo.pm | 57 ------------------------- Bugzilla/Constants.pm | 4 +- Bugzilla/Install/Filesystem.pm | 3 +- Bugzilla/ModPerl.pm | 4 +- Bugzilla/Quantum.pm | 25 +++++++++++ Bugzilla/Quantum/CGI.pm | 59 +++++++++++++++++++++++++ Bugzilla/Quantum/Legacy.pm | 47 ++++++++++++++++++++ Bugzilla/Quantum/Plugin/Glue.pm | 61 ++++++++++++++++++++++++++ Bugzilla/Quantum/Template.pm | 40 +++++++++++++++++ Bugzilla/Util.pm | 2 +- 12 files changed, 319 insertions(+), 111 deletions(-) create mode 100644 Bugzilla/CGI/ContentSecurityPolicyAttr.pm delete mode 100644 Bugzilla/CGI/Mojo.pm create mode 100644 Bugzilla/Quantum.pm create mode 100644 Bugzilla/Quantum/CGI.pm create mode 100644 Bugzilla/Quantum/Legacy.pm create mode 100644 Bugzilla/Quantum/Plugin/Glue.pm create mode 100644 Bugzilla/Quantum/Template.pm (limited to 'Bugzilla') diff --git a/Bugzilla/CGI.pm b/Bugzilla/CGI.pm index 03805ad1e..bfd2a72f6 100644 --- a/Bugzilla/CGI.pm +++ b/Bugzilla/CGI.pm @@ -24,6 +24,10 @@ use Bugzilla::Search::Recent; use File::Basename; use URI; +use Role::Tiny::With; + +with 'Bugzilla::CGI::ContentSecurityPolicyAttr'; + BEGIN { if (ON_WINDOWS) { # Help CGI find the correct temp directory as the default list @@ -33,35 +37,6 @@ BEGIN { *AUTOLOAD = \&CGI::AUTOLOAD; } -sub DEFAULT_CSP { - my %policy = ( - default_src => [ 'self' ], - script_src => [ 'self', 'nonce', 'unsafe-inline', 'https://www.google-analytics.com' ], - frame_src => [ 'none', ], - worker_src => [ 'none', ], - img_src => [ 'self', 'https://secure.gravatar.com', 'https://www.google-analytics.com' ], - style_src => [ 'self', 'unsafe-inline' ], - object_src => [ 'none' ], - connect_src => [ - 'self', - # This is from extensions/OrangeFactor/web/js/orange_factor.js - 'https://treeherder.mozilla.org/api/failurecount/', - ], - form_action => [ - 'self', - # used in template/en/default/search/search-google.html.tmpl - 'https://www.google.com/search' - ], - frame_ancestors => [ 'none' ], - report_only => 1, - ); - if (Bugzilla->params->{github_client_id} && !Bugzilla->user->id) { - push @{$policy{form_action}}, 'https://github.com/login/oauth/authorize', 'https://github.com/login'; - } - - return %policy; -} - # 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. @@ -193,30 +168,16 @@ sub target_uri { } } -sub content_security_policy { - my ($self, %add_params) = @_; - if (%add_params || !$self->{Bugzilla_csp}) { - my %params = DEFAULT_CSP; - delete $params{report_only} if %add_params && !$add_params{report_only}; - foreach my $key (keys %add_params) { - if (defined $add_params{$key}) { - $params{$key} = $add_params{$key}; - } - else { - delete $params{$key}; - } - } - $self->{Bugzilla_csp} = Bugzilla::CGI::ContentSecurityPolicy->new(%params); - } +sub set_csp_object { + my ( $self, $object ) = @_; - return $self->{Bugzilla_csp}; + $self->{Bugzilla_csp} = $object; } -sub csp_nonce { +sub csp_object { my ($self) = @_; - my $csp = $self->content_security_policy; - return $csp->has_nonce ? $csp->nonce : ''; + return $self->{Bugzilla_csp}; } # We want this sorted plus the ability to exclude certain params diff --git a/Bugzilla/CGI/ContentSecurityPolicyAttr.pm b/Bugzilla/CGI/ContentSecurityPolicyAttr.pm new file mode 100644 index 000000000..c94b3815c --- /dev/null +++ b/Bugzilla/CGI/ContentSecurityPolicyAttr.pm @@ -0,0 +1,71 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::CGI::ContentSecurityPolicyAttr; +use 5.10.1; +use strict; +use warnings; +use Role::Tiny; + +requires 'csp_object', 'set_csp_object'; + +sub DEFAULT_CSP { + my %policy = ( + default_src => [ 'self' ], + script_src => [ 'self', 'nonce', 'unsafe-inline', 'https://www.google-analytics.com' ], + frame_src => [ 'none', ], + worker_src => [ 'none', ], + img_src => [ 'self', 'https://secure.gravatar.com', 'https://www.google-analytics.com' ], + style_src => [ 'self', 'unsafe-inline' ], + object_src => [ 'none' ], + connect_src => [ + 'self', + # This is from extensions/OrangeFactor/web/js/orange_factor.js + 'https://treeherder.mozilla.org/api/failurecount/', + ], + form_action => [ + 'self', + # used in template/en/default/search/search-google.html.tmpl + 'https://www.google.com/search' + ], + frame_ancestors => [ 'none' ], + report_only => 1, + ); + if (Bugzilla->params->{github_client_id} && !Bugzilla->user->id) { + push @{$policy{form_action}}, 'https://github.com/login/oauth/authorize', 'https://github.com/login'; + } + + return %policy; +} + +sub content_security_policy { + my ($self, %add_params) = @_; + if (%add_params || !$self->csp_object) { + my %params = DEFAULT_CSP; + delete $params{report_only} if %add_params && !$add_params{report_only}; + foreach my $key (keys %add_params) { + if (defined $add_params{$key}) { + $params{$key} = $add_params{$key}; + } + else { + delete $params{$key}; + } + } + $self->set_csp_object( Bugzilla::CGI::ContentSecurityPolicy->new(%params) ); + } + + return $self->csp_object; +} + +sub csp_nonce { + my ($self) = @_; + + my $csp = $self->content_security_policy; + return $csp->has_nonce ? $csp->nonce : ''; +} + +1; diff --git a/Bugzilla/CGI/Mojo.pm b/Bugzilla/CGI/Mojo.pm deleted file mode 100644 index 2d4f40d3e..000000000 --- a/Bugzilla/CGI/Mojo.pm +++ /dev/null @@ -1,57 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. - -package Bugzilla::CGI::Mojo; -use 5.10.1; -use Moo; - -has 'controller' => ( - is => 'ro', - handles => [qw(param cookie)], -); - -has 'content_security_policy' => ( - is => 'lazy', -); - -sub _build_content_security_policy { - my ($self) = @_; - my $csp = $self->controller->stash->{content_security_policy} // { Bugzilla::CGI::DEFAULT_CSP() }; - return Bugzilla::CGI::ContentSecurityPolicy->new( $csp ); -} - -sub csp_nonce { - my ($self) = @_; - - my $csp = $self->content_security_policy; - return $csp->has_nonce ? $csp->nonce : ''; -} - -sub script_name { - my ($self) = @_; - - return $self->controller->req->env->{SCRIPT_NAME}; -} - -sub http { - my ($self, $header) = @_; - return $self->controller->req->headers->header($header); -} - -sub redirect { - my ($self, $location) = @_; - - $self->controller->redirect_to($location); -} - -sub Vars { - my ($self) = @_; - - return $self->controller->req->query_params->to_hash; -} - -1; diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 8ef776f96..127f1bd58 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -130,7 +130,7 @@ use Memoize; USAGE_MODE_JSON USAGE_MODE_TEST USAGE_MODE_REST - USAGE_MODE_MOJO + USAGE_MODE_QUANTUM ERROR_MODE_WEBPAGE ERROR_MODE_DIE @@ -488,7 +488,7 @@ use constant USAGE_MODE_EMAIL => 3; use constant USAGE_MODE_JSON => 4; use constant USAGE_MODE_TEST => 5; use constant USAGE_MODE_REST => 6; -use constant USAGE_MODE_MOJO => 7; +use constant USAGE_MODE_QUANTUM => 7; # Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE # usually). Use with Bugzilla->error_mode. diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 739ea9a0a..b5d8bae08 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -240,6 +240,7 @@ sub FILESYSTEM { '.htaccess' => { perms => WS_SERVE }, 'cvs-update.log' => { perms => WS_SERVE }, 'scripts/sendunsentbugmail.pl' => { perms => WS_EXECUTE }, + 'scripts/bugzilla_quantum' => { perms => CGI_READ }, 'docs/bugzilla.ent' => { perms => OWNER_WRITE }, 'docs/makedocs.pl' => { perms => OWNER_EXECUTE }, 'docs/style.css' => { perms => WS_SERVE }, @@ -346,7 +347,7 @@ sub FILESYSTEM { 'contrib' => { files => OWNER_EXECUTE, dirs => DIR_OWNER_WRITE, }, 'scripts' => { files => OWNER_EXECUTE, - dirs => DIR_OWNER_WRITE, }, + dirs => DIR_WS_SERVE, }, ); # --- FILES TO CREATE --- # diff --git a/Bugzilla/ModPerl.pm b/Bugzilla/ModPerl.pm index 6f7ed22e9..b4ba36934 100644 --- a/Bugzilla/ModPerl.pm +++ b/Bugzilla/ModPerl.pm @@ -109,11 +109,11 @@ ErrorDocument 500 /errors/500.html require valid-user - + SetHandler perl-script PerlResponseHandler Plack::Handler::Apache2 PerlSetEnv MOJO_HOME [% cgi_path %] - PerlSetVar psgi_app [% cgi_path %]/new.psgi + PerlSetVar psgi_app [% cgi_path %]/scripts/bugzilla_quantum # directory rules for all the other places we have .htaccess files diff --git a/Bugzilla/Quantum.pm b/Bugzilla/Quantum.pm new file mode 100644 index 000000000..7da68e0ed --- /dev/null +++ b/Bugzilla/Quantum.pm @@ -0,0 +1,25 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum; +use Mojo::Base 'Mojolicious'; + +use Bugzilla::Constants; +use Bugzilla::Quantum::CGI; +use Try::Tiny; + +sub startup { + my ($self) = @_; + + $self->plugin('Bugzilla::Quantum::Plugin::Glue'); + my $r = $self->routes; + + $r->any( '/' )->to('legacy#index_cgi'); + $r->any( '/show_bug.cgi' )->to('legacy#show_bug'); +} + +1; \ No newline at end of file diff --git a/Bugzilla/Quantum/CGI.pm b/Bugzilla/Quantum/CGI.pm new file mode 100644 index 000000000..5afa19b18 --- /dev/null +++ b/Bugzilla/Quantum/CGI.pm @@ -0,0 +1,59 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::CGI; +use 5.10.1; +use Moo; + +has 'controller' => ( + is => 'ro', + handles => [qw(param cookie)], +); + +has 'csp_object' => ( + is => 'ro', + writer => 'set_csp_object', +); + +with 'Bugzilla::CGI::ContentSecurityPolicyAttr'; + +sub script_name { + my ($self) = @_; + + return $self->controller->req->env->{SCRIPT_NAME}; +} + +sub http { + my ($self, $header) = @_; + return $self->controller->req->headers->header($header); +} + +sub header { + my ($self, @args) = @_; + my $c = $self->controller; + return '' if @args == 0; + + if (@args == 1) { + $c->res->headers->content_type($args[0]); + } + + return ''; +} + +sub redirect { + my ($self, $location) = @_; + + $self->controller->redirect_to($location); +} + +sub Vars { + my ($self) = @_; + + return $self->controller->req->query_params->to_hash; +} + +1; diff --git a/Bugzilla/Quantum/Legacy.pm b/Bugzilla/Quantum/Legacy.pm new file mode 100644 index 000000000..73e877af3 --- /dev/null +++ b/Bugzilla/Quantum/Legacy.pm @@ -0,0 +1,47 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::Legacy; +use Mojo::Base 'Mojolicious::Controller'; +use File::Spec::Functions qw(catfile); +use Bugzilla::Constants qw(bz_locations); +use Taint::Util qw(untaint); +use Sub::Name qw(subname); +use autodie; + +_load_cgi(index_cgi => 'index.cgi'); +_load_cgi(show_bug => 'show_bug.cgi'); + +sub _load_cgi { + my ($name, $file) = @_; + my $content; + { + local $/ = undef; + open my $fh, '<', catfile(bz_locations->{cgi_path}, $file); + $content = <$fh>; + untaint($content); + close $fh; + } + my $pkg = __PACKAGE__ . '::' . $name; + my @lines = ( + qq{package $pkg;}, + qq{#line 1 "$file"}, + "sub { my (\$self) = \@_; $content };" + ); + my $code = join "\n", @lines; + my $sub = _eval($code); + { + no strict 'refs'; ## no critic (strict) + *{ $name } = subname($name, $sub); + } +} + +sub _eval { ## no critic (unpack) + return eval $_[0]; ## no critic (eval) +} + +1; \ No newline at end of file diff --git a/Bugzilla/Quantum/Plugin/Glue.pm b/Bugzilla/Quantum/Plugin/Glue.pm new file mode 100644 index 000000000..d689f598a --- /dev/null +++ b/Bugzilla/Quantum/Plugin/Glue.pm @@ -0,0 +1,61 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::Plugin::Glue; +use Mojo::Base 'Mojolicious::Plugin'; + +use Try::Tiny; +use Bugzilla::Constants; +use Bugzilla::Quantum::CGI; +use Bugzilla::Quantum::Template; + +sub register { + my ( $self, $app, $conf ) = @_; + + my $template = Bugzilla::Template->create; + $template->{_is_main} = 1; + + $app->renderer->add_handler( + 'bugzilla' => sub { + my ( $renderer, $c, $output, $options ) = @_; + my %params; + + # Helpers + foreach my $method ( grep {m/^\w+\z/} keys %{ $renderer->helpers } ) { + my $sub = $renderer->helpers->{$method}; + $params{$method} = sub { $c->$sub(@_) }; + } + + # Stash values + $params{$_} = $c->stash->{$_} for grep {m/^\w+\z/} keys %{ $c->stash }; + $params{self} = $params{c} = $c; + my $name = $options->{template}; + unless ($name =~ /\./) { + $name = sprintf '%s.%s.tmpl', $options->{template}, $options->{format}; + } + $template->process( $name, \%params, $output ) + or die $template->error; + } + ); + + $app->hook( + around_dispatch => sub { + my ($next, $c) = @_; + try { + local %{ Bugzilla->request_cache } = (); + Bugzilla->usage_mode(USAGE_MODE_QUANTUM); + Bugzilla->cgi( Bugzilla::Quantum::CGI->new(controller => $c) ); + Bugzilla->template( Bugzilla::Quantum::Template->new( controller => $c, template => $template ) ); + $next->(); + } catch { + die $_ unless /\bModPerl::Util::exit\b/; + }; + } + ); +} + +1; \ No newline at end of file diff --git a/Bugzilla/Quantum/Template.pm b/Bugzilla/Quantum/Template.pm new file mode 100644 index 000000000..ebae0cf9f --- /dev/null +++ b/Bugzilla/Quantum/Template.pm @@ -0,0 +1,40 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Quantum::Template; +use 5.10.1; +use Moo; + +has 'controller' => ( + is => 'ro', + required => 1, +); + +has 'template' => ( + is => 'ro', + required => 1, + handles => ['error', 'get_format'], +); + +sub process { + my ($self, $file, $vars, $output) = @_; + + if (@_ < 4) { + my $stash = $self->controller->stash; + $stash->{$_} = $vars->{$_} for keys %$vars; + $self->controller->render(template => $file, handler => 'bugzilla'); + return 1; + } + elsif (@_ == 4) { + return $self->template->process($file, $vars, $output); + } + else { + die __PACKAGE__ . '->process() called with too many arguments'; + } +} + +1; \ No newline at end of file diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm index 206c0aa3f..8e60944b0 100644 --- a/Bugzilla/Util.pm +++ b/Bugzilla/Util.pm @@ -321,7 +321,7 @@ sub do_ssl_redirect_if_required { # Returns the real remote address of the client, sub remote_ip { - if (Bugzilla->usage_mode == USAGE_MODE_MOJO) { + if (Bugzilla->usage_mode == USAGE_MODE_QUANTUM) { return Bugzilla->cgi->controller->tx->remote_address; } else { -- cgit v1.2.3-24-g4f1b