From 05a2b9c6955bb301a31182169e1d092e6b11a8c9 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 4 Apr 2012 10:10:03 -0700 Subject: [PATCH] options to limit access to full customer list, #15928 --- FS/FS/AccessRight.pm | 3 + FS/FS/Conf.pm | 7 + FS/FS/Schema.pm | 13 ++ FS/FS/Upgrade.pm | 1 + FS/FS/access_right.pm | 38 +++++ FS/FS/upgrade_journal.pm | 151 ++++++++++++++++++++ FS/MANIFEST | 2 + FS/t/upgrade_journal.t | 5 + httemplate/elements/menu.html | 2 +- httemplate/search/cust_main.cgi | 2 +- httemplate/search/elements/search-html.html | 6 +- httemplate/search/elements/search.html | 10 +- 12 files changed, 235 insertions(+), 5 deletions(-) create mode 100644 FS/FS/upgrade_journal.pm create mode 100644 FS/t/upgrade_journal.t diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 1bfae03ad..b164948f2 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -253,6 +253,7 @@ tie my %rights, 'Tie::IxHash', ### 'Reporting/listing rights' => [ 'List customers', + 'List all customers', 'List zip codes', #NEW 'List invoices', 'List packages', @@ -266,6 +267,8 @@ tie my %rights, 'Tie::IxHash', { rightname=> 'List inventory', global=>1 }, { rightname=>'View email logs', global=>1 }, + 'Download report data', + #{ rightname => 'List customers of all agents', global=>1 }, ], diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 344dc2aee..4a4f92c8a 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1571,6 +1571,13 @@ and customer address. Include units.', 'type' => 'text', }, + { + 'key' => 'disable_maxselect', + 'section' => 'UI', + 'description' => 'Prevent changing the number of records per page.', + 'type' => 'checkbox', + }, + { 'key' => 'session-start', 'section' => 'session', diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index ef7a24445..fdf29e0a0 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3604,6 +3604,19 @@ sub tables_hashref { 'index' => [], }, + 'upgrade_journal' => { + 'columns' => [ + 'upgradenum', 'serial', '', '', '', '', + '_date', 'int', '', '', '', '', + 'upgrade', 'varchar', '', $char_d, '', '', + 'status', 'varchar', '', $char_d, '', '', + 'statustext', 'varchar', 'NULL', $char_d, '', '', + ], + 'primary_key' => 'upgradenum', + 'unique' => [ [ 'upgradenum' ] ], + 'index' => [ [ 'upgrade' ] ], + }, + %{ tables_hashref_torrus() }, # tables of ours for doing torrus virtual port combining diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm index 8f66c66b5..aabc4e72f 100644 --- a/FS/FS/Upgrade.pm +++ b/FS/FS/Upgrade.pm @@ -7,6 +7,7 @@ use Tie::IxHash; use FS::UID qw( dbh driver_name ); use FS::Conf; use FS::Record qw(qsearchs qsearch str2time_sql); +use FS::upgrade_journal; use FS::svc_domain; $FS::svc_domain::whois_hack = 1; diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm index ef8cc6cd8..d2a39aac8 100644 --- a/FS/FS/access_right.pm +++ b/FS/FS/access_right.pm @@ -180,6 +180,44 @@ sub _upgrade_data { # class method } + my @all_groups = qsearch('access_group', {}); + + ### ACL_list_all_customers + if ( !FS::upgrade_journal->is_done('ACL_list_all_customers') ) { + + # grant "List all customers" to all users who have "List customers" + for my $group (@all_groups) { + if ( $group->access_right('List customers') ) { + my $access_right = FS::access_right->new( { + 'righttype' => 'FS::access_group', + 'rightobjnum' => $group->groupnum, + 'rightname' => 'List all customers', + } ); + my $error = $access_right->insert; + die $error if $error; + } + } + + FS::upgrade_journal->set_done('ACL_list_all_customers'); + } + + ### ACL_download_report_data + if ( !FS::upgrade_journal->is_done('ACL_download_report_data') ) { + + # grant to everyone + for my $group (@all_groups) { + my $access_right = FS::access_right->new( { + 'righttype' => 'FS::access_group', + 'rightobjnum' => $group->groupnum, + 'rightname' => 'Download report data', + } ); + my $error = $access_right->insert; + die $error if $error; + } + + FS::upgrade_journal->set_done('ACL_download_report_data'); + } + ''; } diff --git a/FS/FS/upgrade_journal.pm b/FS/FS/upgrade_journal.pm new file mode 100644 index 000000000..8f6d121a3 --- /dev/null +++ b/FS/FS/upgrade_journal.pm @@ -0,0 +1,151 @@ +package FS::upgrade_journal; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::upgrade_journal - Object methods for upgrade_journal records + +=head1 SYNOPSIS + + use FS::upgrade_journal; + + $record = new FS::upgrade_journal \%hash; + $record = new FS::upgrade_journal { 'column' => 'value' }; + + $error = $record->insert; + + # Typical use case + my $upgrade = 'rename_all_customers_to_Bob'; + if ( ! FS::upgrade_journal->is_done($upgrade) ) { + ... # do the upgrade, then, if it succeeds + FS::upgrade_journal->set_done($upgrade); + } + +=head1 DESCRIPTION + +An FS::upgrade_journal object records an upgrade procedure that was run +on the database. FS::upgrade_journal inherits from FS::Record. The +following fields are currently supported: + +=over 4 + +=item upgradenum - primary key + +=item _date - unix timestamp when the upgrade was run + +=item upgrade - string identifier for the upgrade procedure; must match /^\w+$/ + +=item status - either 'done' or 'failed' + +=item statustext - any other message that needs to be recorded + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new upgrade record. To add it to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'upgrade_journal'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +sub delete { die "upgrade_journal records can't be deleted" } +sub replace { die "upgrade_journal records can't be modified" } + +=item check + +Checks all fields to make sure this is a valid example. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + if ( !$self->_date ) { + $self->_date(time); + } + + my $error = + $self->ut_numbern('upgradenum') + || $self->ut_number('_date') + || $self->ut_alpha('upgrade') + || $self->ut_text('status') + || $self->ut_textn('statustext') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 CLASS METHODS + +=over 4 + +=item is_done UPGRADE + +Returns the upgrade entry with identifier UPGRADE and status 'done', if +there is one. This is an easy way to check whether an upgrade has been done. + +=cut + +sub is_done { + my ($class, $upgrade) = @_; + qsearch('upgrade_journal', { 'status' => 'done', 'upgrade' => $upgrade }) +} + +=item set_done UPGRADE + +Creates and inserts an upgrade entry with the current time, status 'done', +and identifier UPGRADE. Dies on error. + +=cut + +sub set_done { + my ($class, $upgrade) = @_; + my $new = $class->new({ 'status' => 'done', 'upgrade' => $upgrade }); + my $error = $new->insert; + die $error if $error; + $new; +} + + +=head1 BUGS + +Despite how it looks, this is not currently suitable for use as a mutex. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index 49d27b437..0a0580412 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -630,3 +630,5 @@ FS/tower_sector.pm t/tower_sector.t FS/h_svc_cert.pm t/h_svc_cert.t +FS/upgrade_journal.pm +FS/upgrade_journal.t diff --git a/FS/t/upgrade_journal.t b/FS/t/upgrade_journal.t new file mode 100644 index 000000000..0822effc5 --- /dev/null +++ b/FS/t/upgrade_journal.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::upgrade_journal; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index eb6371237..75468df9f 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -106,7 +106,7 @@ $report_customers_lists{'with USPS-unvalidated addresses'} = [ $fsurl. 'search/c tie my %report_customers, 'Tie::IxHash'; $report_customers{'List customers'} = [ \%report_customers_lists, 'List customers' ] - if $curuser->access_right('List customers'); + if $curuser->access_right('List all customers'); $report_customers{'Zip code distribution'} = [ $fsurl. 'search/report_cust_main-zip.html', 'Zip codes by number of customers' ]; $report_customers{'Customer signup report'} = [ $fsurl. 'graph/report_cust_signup.html', 'New customer signups by date' ], $report_customers{'Advanced customer reports'} = [ $fsurl. 'search/report_cust_main.html', 'by status, signup date, agent, etc.' ] diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index aae8c7e99..859ef04e6 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -334,7 +334,7 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" - unless $curuser->access_right('List customers'); + unless $curuser->access_right('List all customers'); my $conf = new FS::Conf; my $maxrecords = $conf->config('maxsearchrecordsperpage'); diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html index af0c8fc09..53167c26e 100644 --- a/httemplate/search/elements/search-html.html +++ b/httemplate/search/elements/search-html.html @@ -130,7 +130,9 @@ -% unless ( $opt{'disable_download'} || $type eq 'html-print' ) { +% if ( $curuser->access_right('Download report data') +% and !$opt{'disable_download'} +% and $type ne 'html-print' ) { @@ -470,6 +472,8 @@ % } <%init> +my $curuser = $FS::CurrentUser::CurrentUser; + my %args = @_; my $type = $args{'type'}; my $header = $args{'header'}; diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index 81ec4d082..9bc66b6fa 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -170,7 +170,6 @@ Example: % <% include('search-csv.html', header=>$header, rows=>$rows, opt=>\%opt ) %> % -% #} elsif ( $type eq 'excel' ) { % } elsif ( $type =~ /\.xls$/ ) { % <% include('search-xls.html', header=>$header, rows=>$rows, opt=>\%opt ) %> @@ -179,7 +178,7 @@ Example: % <% include('search-xml.html', rows=>$rows, opt=>\%opt ) %> % -% } else { # regular HTML +% } else { % <% include('search-html.html', type => $type, @@ -205,6 +204,11 @@ my $curuser = $FS::CurrentUser::CurrentUser; my $type = $cgi->param('_type') =~ /^(csv|\w*\.xls|xml|select|html(-print)?)$/ ? $1 : 'html' ; +if ( !$curuser->access_right('Download report data') ) { + $opt{'disable_download'} = 1; + $type = 'html'; +} + my %align = ( 'l' => 'left', 'r' => 'right', @@ -363,6 +367,8 @@ unless ( $type =~ /^(csv|\w*.xls)$/) { $maxrecords ||= $confmax; } + $opt{'disable_maxselect'} ||= $conf->exists('disable_maxselect'); + $limit = $maxrecords ? "LIMIT $maxrecords" : ''; $offset = $cgi->param('offset') =~ /^(\d+)$/ ? $1 : 0; -- 2.20.1