diff options
-rw-r--r-- | Bugzilla/Extension.pm | 11 | ||||
-rw-r--r-- | Bugzilla/Install/Filesystem.pm | 301 | ||||
-rwxr-xr-x | extensions/create.pl | 3 | ||||
-rwxr-xr-x | showdependencygraph.cgi | 24 | ||||
-rw-r--r-- | template/en/default/extensions/web-readme.txt.tmpl | 29 |
5 files changed, 233 insertions, 135 deletions
diff --git a/Bugzilla/Extension.pm b/Bugzilla/Extension.pm index 17b889b98..c7688d90b 100644 --- a/Bugzilla/Extension.pm +++ b/Bugzilla/Extension.pm @@ -649,6 +649,17 @@ case your templates should probably be in a directory like F<extensions/Foo/template/en/default/page/foo/> so as not to conflict with other pages that other extensions might add. +=head2 CSS, JavaScript, and Images + +If you include CSS, JavaScript, and images in your extension that are +served directly to the user (that is, they're not read by a script and +then printed--they're just linked directly in your HTML), they should go +into the F<web/> subdirectory of your extension. + +So, for example, if you had a CSS file called F<style.css> and your +extension was called F<Foo>, your file would go into +F<extensions/Foo/web/style.css>. + =head2 Disabling Your Extension If you want your extension to be totally ignored by Bugzilla (it will diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 9ee21ed35..0ca49a75c 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -54,6 +54,53 @@ use constant HT_DEFAULT_DENY => <<EOT; deny from all EOT +############### +# Permissions # +############### + +# Used by the permissions "constants" below. +sub _suexec { Bugzilla->localconfig->{'use_suexec'} }; +sub _group { Bugzilla->localconfig->{'webservergroup'} }; + +# Writeable by the owner only. +use constant OWNER_WRITE => 0600; +# Executable by the owner only. +use constant OWNER_EXECUTE => 0700; +# A directory which is only writeable by the owner. +use constant DIR_OWNER_WRITE => 0700; + +# A cgi script that the webserver can execute. +sub WS_EXECUTE { _group() ? 0750 : 0755 }; +# A file that is read by cgi scripts, but is not ever read +# directly by the webserver. +sub CGI_READ { _group() ? 0640 : 0644 }; +# A file that is written to by cgi scripts, but is not ever +# read or written directly by the webserver. +sub CGI_WRITE { _group() ? 0660 : 0666 }; +# A file that is served directly by the web server. +sub WS_SERVE { (_group() and !_suexec()) ? 0640 : 0644 }; + +# A directory whose contents can be read or served by the +# webserver (so even directories containing cgi scripts +# would have this permission). +sub DIR_WS_SERVE { (_group() and !_suexec()) ? 0750 : 0755 }; +# A directory that is read by cgi scripts, but is never accessed +# directly by the webserver +sub DIR_CGI_READ { _group() ? 0750 : 0755 }; +# A directory that is written to by cgi scripts, but where the +# scripts never needs to overwrite files created by other +# users. +sub DIR_CGI_WRITE { _group() ? 0770 : 01777 }; +# A directory that is written to by cgi scripts, where the +# scripts need to overwrite files created by other users. +sub DIR_CGI_OVERWRITE { _group() ? 0770 : 0777 }; + +# This can be combined (using "|") with other permissions for +# directories that, in addition to their normal permissions (such +# as DIR_CGI_WRITE) also have content served directly from them +# (or their subdirectories) to the user, via the webserver. +sub DIR_ALSO_WS_SERVE { _suexec() ? 0001 : 0 }; + # This looks like a constant because it effectively is, but # it has to call other subroutines and read the current filesystem, # so it's defined as a sub. This is not exported, so it doesn't have @@ -82,37 +129,6 @@ sub FILESYSTEM { $localconfig =~ s/\.\Q$ENV{PROJECT}\E$//; } - my $ws_group = Bugzilla->localconfig->{'webservergroup'}; - my $use_suexec = Bugzilla->localconfig->{'use_suexec'}; - - # The set of permissions that we use: - - # FILES - # Executable by the web server - my $ws_executable = $ws_group ? 0750 : 0755; - # Executable by the owner only. - my $owner_executable = 0700; - # Readable by the web server. - my $ws_readable = ($ws_group && !$use_suexec) ? 0640 : 0644; - # Readable by the owner only. - my $owner_readable = 0600; - # Writeable by the web server. - my $ws_writeable = $ws_group ? 0660 : 0666; - - # Script-readable files that should not be world-readable under suexec. - my $script_readable = $use_suexec ? 0640 : $ws_readable; - - # DIRECTORIES - # Readable by the web server. - my $ws_dir_readable = ($ws_group && !$use_suexec) ? 0750 : 0755; - # Readable only by the owner. - my $owner_dir_readable = 0700; - # Writeable by the web server. - my $ws_dir_writeable = $ws_group ? 0770 : 01777; - # The web server can overwrite files owned by other users, - # in this directory. - my $ws_dir_full_control = $ws_group ? 0770 : 0777; - # Note: When being processed by checksetup, these have their permissions # set in this order: %all_dirs, %recurse_dirs, %all_files. # @@ -123,45 +139,48 @@ sub FILESYSTEM { # --- FILE PERMISSIONS (Non-created files) --- # my %files = ( - '*' => { perms => $ws_readable }, - '*.cgi' => { perms => $ws_executable }, - 'whineatnews.pl' => { perms => $ws_executable }, - 'collectstats.pl' => { perms => $ws_executable }, - 'checksetup.pl' => { perms => $owner_executable }, - 'importxml.pl' => { perms => $ws_executable }, - 'runtests.pl' => { perms => $owner_executable }, - 'testserver.pl' => { perms => $ws_executable }, - 'whine.pl' => { perms => $ws_executable }, - 'customfield.pl' => { perms => $owner_executable }, - 'email_in.pl' => { perms => $ws_executable }, - 'sanitycheck.pl' => { perms => $ws_executable }, - 'jobqueue.pl' => { perms => $owner_executable }, - 'migrate.pl' => { perms => $owner_executable }, - 'install-module.pl' => { perms => $owner_executable }, - - # Set the permissions for localconfig the same across all - # PROJECTs. - $localconfig => { perms => $script_readable }, - "$localconfig.*" => { perms => $script_readable }, - "$localconfig.old" => { perms => $owner_readable }, - - 'contrib/README' => { perms => $owner_readable }, - 'contrib/*/README' => { perms => $owner_readable }, - 'docs/makedocs.pl' => { perms => $owner_executable }, - 'docs/style.css' => { perms => $ws_readable }, - 'docs/*/rel_notes.txt' => { perms => $ws_readable }, - 'docs/*/README.docs' => { perms => $owner_readable }, - "$datadir/params" => { perms => $ws_writeable }, - "$datadir/old-params.txt" => { perms => $owner_readable }, - "$extensionsdir/create.pl" => { perms => $owner_executable }, + '*' => { perms => OWNER_WRITE }, + # Some .pl files are WS_EXECUTE because we want + # users to be able to cron them or otherwise run + # them as a secure user, like the webserver owner. + '*.cgi' => { perms => WS_EXECUTE }, + 'whineatnews.pl' => { perms => WS_EXECUTE }, + 'collectstats.pl' => { perms => WS_EXECUTE }, + 'importxml.pl' => { perms => WS_EXECUTE }, + 'testserver.pl' => { perms => WS_EXECUTE }, + 'whine.pl' => { perms => WS_EXECUTE }, + 'email_in.pl' => { perms => WS_EXECUTE }, + 'sanitycheck.pl' => { perms => WS_EXECUTE }, + 'checksetup.pl' => { perms => OWNER_EXECUTE }, + 'runtests.pl' => { perms => OWNER_EXECUTE }, + 'jobqueue.pl' => { perms => OWNER_EXECUTE }, + 'migrate.pl' => { perms => OWNER_EXECUTE }, + 'install-module.pl' => { perms => OWNER_EXECUTE }, + + 'Bugzilla.pm' => { perms => CGI_READ }, + "$localconfig*" => { perms => CGI_READ }, + 'bugzilla.dtd' => { perms => WS_SERVE }, + 'mod_perl.pl' => { perms => WS_SERVE }, + 'robots.txt' => { perms => WS_SERVE }, + + 'contrib/README' => { perms => OWNER_WRITE }, + 'contrib/*/README' => { perms => OWNER_WRITE }, + 'docs/bugzilla.ent' => { perms => OWNER_WRITE }, + 'docs/makedocs.pl' => { perms => OWNER_EXECUTE }, + 'docs/style.css' => { perms => WS_SERVE }, + 'docs/*/rel_notes.txt' => { perms => WS_SERVE }, + 'docs/*/README.docs' => { perms => OWNER_WRITE }, + "$datadir/params" => { perms => CGI_WRITE }, + "$datadir/old-params.txt" => { perms => OWNER_WRITE }, + "$extensionsdir/create.pl" => { perms => OWNER_EXECUTE }, ); # Directories that we want to set the perms on, but not # recurse through. These are directories we didn't create # in checkesetup.pl. my %non_recurse_dirs = ( - '.' => $ws_dir_readable, - docs => $ws_dir_readable, + '.' => DIR_WS_SERVE, + docs => DIR_WS_SERVE, ); # This sets the permissions for each item inside each of these @@ -170,50 +189,64 @@ sub FILESYSTEM { # the webserver. my %recurse_dirs = ( # Writeable directories - "$datadir/template" => { files => $ws_readable, - dirs => $ws_dir_full_control }, - $attachdir => { files => $ws_writeable, - dirs => $ws_dir_writeable }, - $webdotdir => { files => $ws_writeable, - dirs => $ws_dir_writeable }, - graphs => { files => $ws_writeable, - dirs => $ws_dir_writeable }, + "$datadir/template" => { files => CGI_READ, + dirs => DIR_CGI_OVERWRITE }, + $attachdir => { files => CGI_WRITE, + dirs => DIR_CGI_WRITE }, + $webdotdir => { files => WS_SERVE, + dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE }, + graphs => { files => WS_SERVE, + dirs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE }, # Readable directories - "$datadir/mining" => { files => $ws_readable, - dirs => $ws_dir_readable }, - "$libdir/Bugzilla" => { files => $ws_readable, - dirs => $ws_dir_readable }, - $extlib => { files => $ws_readable, - dirs => $ws_dir_readable }, - $templatedir => { files => $ws_readable, - dirs => $ws_dir_readable }, - $extensionsdir => { files => $ws_readable, - dirs => $ws_dir_readable }, - images => { files => $ws_readable, - dirs => $ws_dir_readable }, - css => { files => $ws_readable, - dirs => $ws_dir_readable }, - js => { files => $ws_readable, - dirs => $ws_dir_readable }, - $skinsdir => { files => $ws_readable, - dirs => $ws_dir_readable }, - t => { files => $owner_readable, - dirs => $owner_dir_readable }, - 'docs/*/html' => { files => $ws_readable, - dirs => $ws_dir_readable }, - 'docs/*/pdf' => { files => $ws_readable, - dirs => $ws_dir_readable }, - 'docs/*/txt' => { files => $ws_readable, - dirs => $ws_dir_readable }, - 'docs/*/images' => { files => $ws_readable, - dirs => $ws_dir_readable }, - 'docs/lib' => { files => $owner_readable, - dirs => $owner_dir_readable }, - 'docs/*/xml' => { files => $owner_readable, - dirs => $owner_dir_readable }, - 'contrib' => { files => $owner_executable, - dirs => $owner_dir_readable, }, + "$datadir/mining" => { files => CGI_READ, + dirs => DIR_CGI_READ }, + "$libdir/Bugzilla" => { files => CGI_READ, + dirs => DIR_CGI_READ }, + $extlib => { files => CGI_READ, + dirs => DIR_CGI_READ }, + $templatedir => { files => CGI_READ, + dirs => DIR_CGI_READ }, + # Directories in the extensions/ dir are WS_SERVE so that + # the web/ directories can be served by the web server. + # But, for extra security, we deny direct webserver access to + # the lib/ and template/ directories of extensions. + $extensionsdir => { files => CGI_READ, + dirs => DIR_WS_SERVE }, + "$extensionsdir/*/lib" => { files => CGI_READ, + dirs => DIR_CGI_READ }, + "$extensionsdir/*/template" => { files => CGI_READ, + dirs => DIR_CGI_READ }, + + # Content served directly by the webserver + images => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, + js => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, + $skinsdir => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, + 'docs/*/html' => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, + 'docs/*/pdf' => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, + 'docs/*/txt' => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, + 'docs/*/images' => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, + "$extensionsdir/*/web" => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, + + # Directories only for the owner, not for the webserver. + '.bzr' => { files => OWNER_WRITE, + dirs => DIR_OWNER_WRITE }, + t => { files => OWNER_WRITE, + dirs => DIR_OWNER_WRITE }, + 'docs/lib' => { files => OWNER_WRITE, + dirs => DIR_OWNER_WRITE }, + 'docs/*/xml' => { files => OWNER_WRITE, + dirs => DIR_OWNER_WRITE }, + 'contrib' => { files => OWNER_EXECUTE, + dirs => DIR_OWNER_WRITE, }, ); # --- FILES TO CREATE --- # @@ -221,27 +254,31 @@ sub FILESYSTEM { # The name of each directory that we should actually *create*, # pointing at its default permissions. my %create_dirs = ( - $datadir => $ws_dir_full_control, - "$datadir/mining" => $ws_dir_readable, - "$datadir/extensions" => $ws_dir_readable, - $attachdir => $ws_dir_writeable, - $extensionsdir => $ws_dir_readable, - graphs => $ws_dir_writeable, - $webdotdir => $ws_dir_writeable, - "$skinsdir/custom" => $ws_dir_readable, - "$skinsdir/contrib" => $ws_dir_readable, + # This is DIR_ALSO_WS_SERVE because it contains $webdotdir. + $datadir => DIR_CGI_OVERWRITE | DIR_ALSO_WS_SERVE, + # Directories that are read-only for cgi scripts + "$datadir/mining" => DIR_CGI_READ, + "$datadir/extensions" => DIR_CGI_READ, + $extensionsdir => DIR_CGI_READ, + # Directories that cgi scripts can write to. + $attachdir => DIR_CGI_WRITE, + graphs => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE, + $webdotdir => DIR_CGI_WRITE | DIR_ALSO_WS_SERVE, + # Directories that contain content served directly by the web server. + "$skinsdir/custom" => DIR_WS_SERVE, + "$skinsdir/contrib" => DIR_WS_SERVE, ); # The name of each file, pointing at its default permissions and # default contents. my %create_files = ( - "$datadir/extensions/additional" => { perms => $ws_readable, + "$datadir/extensions/additional" => { perms => CGI_READ, contents => '' }, # We create this file so that it always has the right owner # and permissions. Otherwise, the webserver creates it as # owned by itself, which can cause problems if jobqueue.pl # or something else is not running as the webserver or root. - "$datadir/mailer.testfile" => { perms => $ws_writeable, + "$datadir/mailer.testfile" => { perms => CGI_WRITE, contents => '' }, ); @@ -251,18 +288,18 @@ sub FILESYSTEM { foreach my $skin_dir ("$skinsdir/custom", <$skinsdir/contrib/*>) { next if basename($skin_dir) =~ /^cvs$/i; foreach my $base_css (<$skinsdir/standard/*.css>) { - _add_custom_css($skin_dir, basename($base_css), \%create_files, $ws_readable); + _add_custom_css($skin_dir, basename($base_css), \%create_files); } foreach my $dir_css (<$skinsdir/standard/*/*.css>) { $dir_css =~ s{.+?([^/]+/[^/]+)$}{$1}; - _add_custom_css($skin_dir, $dir_css, \%create_files, $ws_readable); + _add_custom_css($skin_dir, $dir_css, \%create_files); } } # Because checksetup controls the creation of index.html separately # from all other files, it gets its very own hash. my %index_html = ( - 'index.html' => { perms => $ws_readable, contents => <<EOT + 'index.html' => { perms => WS_SERVE, contents => <<EOT <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> @@ -279,23 +316,27 @@ EOT # Because checksetup controls the .htaccess creation separately # by a localconfig variable, these go in a separate variable from # %create_files. + # + # Note that these get WS_SERVE as their permission + # because they're *read* by the webserver, even though they're not + # actually, themselves, served. my %htaccess = ( - "$attachdir/.htaccess" => { perms => $ws_readable, + "$attachdir/.htaccess" => { perms => WS_SERVE, contents => HT_DEFAULT_DENY }, - "$libdir/Bugzilla/.htaccess" => { perms => $ws_readable, + "$libdir/Bugzilla/.htaccess" => { perms => WS_SERVE, contents => HT_DEFAULT_DENY }, - "$extlib/.htaccess" => { perms => $ws_readable, + "$extlib/.htaccess" => { perms => WS_SERVE, contents => HT_DEFAULT_DENY }, - "$templatedir/.htaccess" => { perms => $ws_readable, + "$templatedir/.htaccess" => { perms => WS_SERVE, contents => HT_DEFAULT_DENY }, - 'contrib/.htaccess' => { perms => $ws_readable, + 'contrib/.htaccess' => { perms => WS_SERVE, contents => HT_DEFAULT_DENY }, - 't/.htaccess' => { perms => $ws_readable, + 't/.htaccess' => { perms => WS_SERVE, contents => HT_DEFAULT_DENY }, - "$datadir/.htaccess" => { perms => $ws_readable, + "$datadir/.htaccess" => { perms => WS_SERVE, contents => HT_DEFAULT_DENY }, - "$webdotdir/.htaccess" => { perms => $ws_readable, contents => <<EOT + "$webdotdir/.htaccess" => { perms => WS_SERVE, contents => <<EOT # Restrict access to .dot files to the public webdot server at research.att.com # if research.att.com ever changes their IP, or if you use a different # webdot server, you'll need to edit this @@ -414,8 +455,8 @@ EOT # A simple helper for creating "empty" CSS files. sub _add_custom_css { - my ($skin_dir, $path, $create_files, $perms) = @_; - $create_files->{"$skin_dir/$path"} = { perms => $perms, contents => <<EOT + my ($skin_dir, $path, $create_files) = @_; + $create_files->{"$skin_dir/$path"} = { perms => WS_SERVE, contents => <<EOT /* * Custom rules for $path. * The rules you put here override rules in that stylesheet. diff --git a/extensions/create.pl b/extensions/create.pl index c4d911c2a..1b54e6b33 100755 --- a/extensions/create.pl +++ b/extensions/create.pl @@ -41,7 +41,7 @@ mkpath($extension_dir) || die "$extension_dir already exists or cannot be created.\n"; my $lcname = lc($name); -foreach my $path (qw(lib template/en/default/hook), +foreach my $path (qw(lib web template/en/default/hook), "template/en/default/$lcname") { mkpath("$extension_dir/$path") || die "$extension_dir/$path: $!"; @@ -55,6 +55,7 @@ my %create_files = ( 'config.pm.tmpl' => 'Config.pm', 'extension.pm.tmpl' => 'Extension.pm', 'util.pm.tmpl' => 'lib/Util.pm', + 'web-readme.txt.tmpl' => 'web/README', 'hook-readme.txt.tmpl' => 'template/en/default/hook/README', 'name-readme.txt.tmpl' => "template/en/default/$lcname/README", ); diff --git a/showdependencygraph.cgi b/showdependencygraph.cgi index a036ee0c9..5fadce998 100755 --- a/showdependencygraph.cgi +++ b/showdependencygraph.cgi @@ -29,6 +29,7 @@ use File::Temp; use Bugzilla; use Bugzilla::Constants; +use Bugzilla::Install::Filesystem; use Bugzilla::Util; use Bugzilla::Error; use Bugzilla::Bug; @@ -114,7 +115,13 @@ if (!defined $cgi->param('id') && $display ne 'doall') { my ($fh, $filename) = File::Temp::tempfile("XXXXXXXXXX", SUFFIX => '.dot', - DIR => $webdotdir); + DIR => $webdotdir, + UNLINK => 1); + +chmod Bugzilla::Install::Filesystem::CGI_WRITE, $filename + or warn install_string('chmod_failed', { path => $filename, + error => $! }); + my $urlbase = Bugzilla->params->{'urlbase'}; print $fh "digraph G {"; @@ -244,8 +251,6 @@ foreach my $k (keys(%seen)) { print $fh "}\n"; close $fh; -chmod 0777, $filename; - my $webdotbase = Bugzilla->params->{'webdotbase'}; if ($webdotbase =~ /^https?:/) { @@ -263,13 +268,18 @@ if ($webdotbase =~ /^https?:/) { my ($pngfh, $pngfilename) = File::Temp::tempfile("XXXXXXXXXX", SUFFIX => '.png', DIR => $webdotdir); + + chmod Bugzilla::Install::Filesystem::WS_SERVE, $pngfilename + or warn install_string('chmod_failed', { path => $pngfilename, + error => $! }); + binmode $pngfh; open(DOT, "\"$webdotbase\" -Tpng $filename|"); binmode DOT; print $pngfh $_ while <DOT>; close DOT; close $pngfh; - + # On Windows $pngfilename will contain \ instead of / $pngfilename =~ s|\\|/|g if ON_WINDOWS; @@ -287,12 +297,18 @@ if ($webdotbase =~ /^https?:/) { my ($mapfh, $mapfilename) = File::Temp::tempfile("XXXXXXXXXX", SUFFIX => '.map', DIR => $webdotdir); + + chmod Bugzilla::Install::Filesystem::WS_SERVE, $mapfilename + or warn install_string('chmod_failed', { path => $mapfilename, + error => $! }); + binmode $mapfh; open(DOT, "\"$webdotbase\" -Tismap $filename|"); binmode DOT; print $mapfh $_ while <DOT>; close DOT; close $mapfh; + $vars->{'image_map'} = CreateImagemap($mapfilename); } diff --git a/template/en/default/extensions/web-readme.txt.tmpl b/template/en/default/extensions/web-readme.txt.tmpl new file mode 100644 index 000000000..55e593914 --- /dev/null +++ b/template/en/default/extensions/web-readme.txt.tmpl @@ -0,0 +1,29 @@ +[%# The contents of this file are subject to the Mozilla Public + # License Version 1.1 (the "License"); you may not use this file + # except in compliance with the License. You may obtain a copy of + # the License at http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS + # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + # implied. See the License for the specific language governing + # rights and limitations under the License. + # + # The Original Code is the Bugzilla Bug Tracking System. + # + # The Initial Developer of the Original Code is Everything Solved, Inc. + # Portions created by the Initial Developer are Copyright (C) 2010 the + # Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Max Kanat-Alexander <mkanat@bugzilla.org> + #%] + +[% PROCESS global/variables.none.tmpl %] + +Web-accessible files, like JavaScript, CSS, and images go in this +directory. You can reference them directly in your HTML. For example, +if you have a file called "style.css" and your extension is called +"Foo", you would put it in "extensions/Foo/web/style.css", and then +you could link to it in HTML like: + +<link href="extensions/Foo/web/style.css" rel="stylesheet" type="text/css"> |