Skip to content

Commit

Permalink
Item8868: re-enabled the {Config}{Var} syntax in %QUERY (and by assoc…
Browse files Browse the repository at this point in the history
…iation, %SEARCH) to give access to config var values. This was disabled because of concerns over security of config vars, so I added a filter to ensure only selected vars are visible (like AccessibleENV does for environment variables)

git-svn-id: http://svn.foswiki.org/trunk@7970 0b4bb1d4-4e5a-0410-9cc4-b2b747904278
  • Loading branch information
CrawfordCurrie authored and CrawfordCurrie committed Jun 29, 2010
1 parent 87df6fc commit 146db70
Show file tree
Hide file tree
Showing 15 changed files with 82 additions and 26 deletions.
1 change: 1 addition & 0 deletions UnitTestContrib/test/unit/Fn_IF.pm
Expand Up @@ -986,6 +986,7 @@ sub set_up {
sub simpleTest {
my ( $this, %test ) = @_;
$this->{session}->enterContext('test');
push(@{$Foswiki::cfg{AccessibleCFG}}, '{Fnargle}', '{A}{B}');
$Foswiki::cfg{Fnargle} = 'Fleeble';
$Foswiki::cfg{A}{B} = 'C';
$this->{request}->param( 'notempty', 'v' );
Expand Down
24 changes: 24 additions & 0 deletions UnitTestContrib/test/unit/Fn_QUERY.pm
Expand Up @@ -107,6 +107,11 @@ sub test_badQUERY {
$this->assert( $result =~ s/^.*}:\s*//s );
$this->assert_str_equals( $test->{expect}, $result );
}
my $result = $this->{test_topicObject}->expandMacros('%QUERY%');
$result =~ s/^.*foswikiAlert'>\s*//s;
$result =~ s/\s*<\/span>\s*//s;
$this->assert( $result =~ s/^.*}:\s*//s );
$this->assert_str_equals( 'Empty expression', $result );
}

sub test_CAS {
Expand Down Expand Up @@ -218,4 +223,23 @@ whatsnot.gif,World.gif
THIS
}

sub test_cfg {
my $this = shift;

# Check a few that should be hidden
foreach my $var ( '{Htpasswd}{FileName}', '{Password}', '{ScriptDir}') {
my $text = "%QUERY{\"$var\"}%";
my $result = $this->{test_topicObject}->expandMacros($text);
$this->assert_equals( '', $result );
}

# Try those that *should* be visible
foreach my $var ( grep { !/AccessibleCFG/ } @{$Foswiki::cfg{AccessibleCFG}}) {
my $text = "%QUERY{\"$var\"}%";
my $result = $this->{test_topicObject}->expandMacros($text);
my $expected = eval("\$Foswiki::cfg$var");
$this->assert_equals( $expected, "$result", "$var!=$expected" );
}
}

1;
1 change: 1 addition & 0 deletions UnitTestContrib/test/unit/FuncTests.pm
Expand Up @@ -1621,6 +1621,7 @@ sub test_loadTemplate {
my $view = Foswiki::Func::loadTemplate('view');
$this->assert(length($view));
my $print = Foswiki::Func::loadTemplate('view', 'print');
$this->assert(length($print));
$this->assert($print ne $view);
$this->assert_str_equals('', Foswiki::Func::loadTemplate('crud'));
}
Expand Down
5 changes: 5 additions & 0 deletions core/data/System/DefaultPreferences.txt
Expand Up @@ -386,6 +386,11 @@ Preference settings local to your site should be set in [[%LOCALSITEPREFS%]] (th
* Set ALLOWTOPICCHANGE = %USERSWEB%.AdminGroup
* Set ALLOWTOPICRENAME = %USERSWEB%.AdminGroup

---++ Accessible configuration items
Note that any configuration variable accessed via QUERY must be included in the
definition of {AccessibleCFG} in =configure= (expert setting)
* Set NAMEFILTER = %QUERY{"{NameFilter}"}%

---++ Creating New Preference Settings

You can introduce new [[PreferenceSettings][preference settings]] and use them in your topics and templates. There is no need to change the Foswiki engine (Perl scripts).
Expand Down
7 changes: 4 additions & 3 deletions core/data/System/IfStatements.txt
Expand Up @@ -23,7 +23,6 @@ The basic syntax of a condition is the same as [[QuerySearch][the syntax used fo
| =defined= | True if a [[PreferenceSettings][preference setting]] or url parameter of this name is defined. |
| =isempty= | True if a [[PreferenceSettings][preference setting]], url parameter or session variable of this name has an empty value. It is equivalent to the expression =(!defined(x) &#124;&#124; $x='')= |
| =$= | expands a URL parameter or [[Macros][macro]] name. Plugin handlers *are not called*. You can pass a limited subset of parameters to macros by enclosing the parameter string in single quotes; for example, =$ 'MACRO{value}'=. The ='MACRO{value}'= string may *not* contain quotes (' or "). |
| ={X}= | expands to the value of the =configure= setting {X} - for example, ={ScriptUrlPath}= |

---+++ Examples

Expand Down Expand Up @@ -53,10 +52,12 @@ url param t is %IF{ "0 < $ t and $ t < 1000" then="in" else="out of"}% range.
%IF{ "$'URLPARAM{scope}'='text'" then="Plain text search" }%
</verbatim>

6. Configuration item set or not
6. Configuration item set or not (item must be one of %FOREACH{"%QUERY{"{AccessibleCFG}"}%" type="string" format="=$item=" separator=", "}%)
<verbatim>
%IF{ "{AntiSpam}{HideUserDetails}" then="User details are hidden" }%
User details are %IF{ "{AntiSpam}{HideUserDetails}" then="hidden" else="shown" }%
</verbatim>
expands to:
User details are %IF{ "{AntiSpam}{HideUserDetails}" then="hidden" else="shown" }%

7. Plugin enabled test
<verbatim>
Expand Down
1 change: 1 addition & 0 deletions core/data/System/QuerySearch.txt
Expand Up @@ -76,6 +76,7 @@ Fields in this plan are referenced using a simple _field specifier_ syntax:
| =X[<i>query</i>]= | refers to all the elements of the array =X= that match _query_. If <i>query</i> is of the form =name='Y'= then you can use the same =X.Y= syntax as is used for accessing structures. | =attachments[size>1024]=, =DocumentContainer[name!='Summary' AND value~'top secret'].value=|
| =X[N]= | where =X= is an array and =N= is an integer number >= 0, gets the Nth element of the array =X=. If N is a floating point number, the integer part will be used as the index. Negative indices can be used to index the array from the end e.g. =attachments[-1]= to get the last attachment. | =attachments[3]= |
| =X/Y= | accesses =Y= from the topic specified by the _value_ of =X=. =X= must evaluate to a topic name | =parent.name/(form.name='ExampleForm')= will evaluate to true if (1) the topic has a parent, (2) the parent topic has the main form type =ExampleForm=. |
| ={X}= | expands to the value of the =configure= setting {X}, if it is accessible, or '' otherwise | only some configuration settings are available: %FOREACH{"%QUERY{"{AccessibleCFG}"}%" type="string" format="=$item=" separator=", "}% |

Note: at some point Foswiki may support multiple forms in the same topic. For this reason you are recommended *not* to use the =fields= shortcut when accessing form fields, but always use the name of the form instead.

Expand Down
4 changes: 3 additions & 1 deletion core/data/System/VarQUERY.txt
Expand Up @@ -10,7 +10,7 @@
* %<nop>QUERY{"fields[name='Firstname'].value"}% gets the value of the 'Firstname' form field in the current topic
* %<nop>QUERY{"Firstname"}% gets the value of the 'Firstname' form field in the current topic (shorthand version)
* %<nop>QUERY{"'System.DocumentGraphics'/attachments.name"}% gets a list of all the names of attachments on the topic 'System.DocumentGraphics'

* %QUERY{"{NameFilter}"}% gets a configuration setting
Plain strings (such as field values) are returned _without_ quotes. Simple arrays of scalars are also returned without quotes, in a comma-separated list (beware of values that contain commas!).

More complex data structures (e.g. arrays of hashes) will be returned as Perl code strings generated by running through CPAN:Data::Dumper.
Expand All @@ -19,4 +19,6 @@ You can make the macro generate different output formats using the =style= param
* =style="perl"= - generates values as Perl code strings
* =style="json"= - generates values as JSON strings, suitable for reading by browsers.

Only some configuration settings are available via QUERY: %FOREACH{"%QUERY{"{AccessibleCFG}"}%" type="string" format="=$item=" separator=", "}%

* Related: [[%IF{"'%INCLUDINGTOPIC%'='Macros'" then="#"}%VarMETA][META]] [[%IF{"'%INCLUDINGTOPIC%'='Macros'" then="#"}%QuerySearch][QuerySearch]]
1 change: 0 additions & 1 deletion core/lib/Foswiki.pm
Expand Up @@ -347,7 +347,6 @@ BEGIN {
$macros{LOCALSITEPREFS} = sub { $Foswiki::cfg{LocalSitePreferences} };
$macros{NOFOLLOW} =
sub { $Foswiki::cfg{NoFollow} ? 'rel=' . $Foswiki::cfg{NoFollow} : '' };
$macros{NAMEFILTER} = sub { $Foswiki::cfg{NameFilter} };
$macros{NOTIFYTOPIC} = sub { $Foswiki::cfg{NotifyTopicName} };
$macros{SCRIPTSUFFIX} = sub { $Foswiki::cfg{ScriptSuffix} };
$macros{STATISTICSTOPIC} = sub { $Foswiki::cfg{Stats}{TopicName} };
Expand Down
14 changes: 9 additions & 5 deletions core/lib/Foswiki.spec
Expand Up @@ -535,6 +535,13 @@ $Foswiki::cfg{Password} = '';
# </ol>
$Foswiki::cfg{SafeEnvPath} = '';
# **PERL**
# Array of the names of configuration items that are available when using %IF, %SEARCH
# and %QUERY{}%. Extensions can push into this array to extend the set. This is done as
# a filter in because while the bulk of configuration items are quite innocent,
# it's better to be a bit paranoid.
$Foswiki::cfg{AccessibleCFG} = [ qw( {ScriptSuffix} {LoginManager} {AuthScripts} {LoginNameFilterIn} {AdminUserLogin} {AdminUserWikiName} {SuperAdminGroup} {UsersTopicName} {AuthRealm} {MinPasswordLength} {Register}{AllowLoginName} {Register}{EnableNewUserRegistration} {Register}{NeedVerification} {Register}{RegistrationAgentWikiName} {AllowInlineScript} {DenyDotDotInclude} {UploadFilter} {NameFilter} {AccessibleCFG} {AntiSpam}{EmailPadding} {AntiSpam}{HideUserDetails} {AntiSpam}{RobotsAreWelcome} {Stats}{TopViews} {Stats}{TopContrib} {Stats}{TopicName} {UserInterfaceInternationalisation} {UseLocale} {Site}{Locale} {Site}{CharSet} {DisplayTimeValues} {DefaultDateFormat} {Site}{LocaleRegexes} {UpperNational} {LowerNational} {PluralToSingular} {EnableHierarchicalWebs} {WebMasterEmail} {WebMasterName} {NotifyTopicName} {SystemWebName} {TrashWebName} {SitePrefsTopicName} {LocalSitePreferences} {HomeTopicName} {WebPrefsTopicName} {UsersWebName} {TemplatePath} {LinkProtocolPattern} {NumberOfRevisions} {MaxRevisionsInADiff} {ReplaceIfEditedAgainWithin} {LeaseLength} {LeaseLengthLessForceful} {Plugins}{WebSearchPath} {PluginsOrder} {Cache}{Enabled} {Validation}{Method} ) ];
# **BOOLEAN**
# Allow %INCLUDE of URLs. This is disabled by default, because it is possible
# to mount a denial-of-service (DoS) attack on a Foswiki site using INCLUDE and
Expand Down Expand Up @@ -640,7 +647,7 @@ $Foswiki::cfg{UsePathForRedirectCache} = $FALSE;
# **REGEX EXPERT**
# Defines the filter-in regexp that must match the names of environment
# variables that can be seen using the %ENV{}% Foswiki variable. Set it to
# variables that can be seen using the %ENV{}% macro. Set it to
# '^.*$' to allow all environment variables to be seen (not recommended).
$Foswiki::cfg{AccessibleENV} = '^(HTTP_\w+|REMOTE_\w+|SERVER_\w+|REQUEST_\w+|MOD_PERL|FOSWIKI_ACTION|PATH_INFO)$';
Expand Down Expand Up @@ -1439,9 +1446,6 @@ $Foswiki::cfg{Plugins}{RenderListPlugin}{Module} = 'Foswiki::Plugins::RenderList
$Foswiki::cfg{Plugins}{MailerContribPlugin}{Enabled} = 1;
$Foswiki::cfg{Plugins}{MailerContribPlugin}{Module} = 'Foswiki::Contrib::MailerContribPlugin';
#---++ Install and Update Extensions
# <p>Consult online extensions repositories for new extensions, or check and manage updates.</p>
#
Expand All @@ -1454,7 +1458,7 @@ $Foswiki::cfg{Plugins}{MailerContribPlugin}{Module} = 'Foswiki::Contrib::MailerC
# <ol>
# <li>Create a Foswiki web to contain the repository</li>
# <li>Copy the <tt>FastReport</tt> page from <a href="http://foswiki.org/Extensions/FastReport?raw=on" target="_new">Foswiki:Extensions.FastReport</a> to your new web</li>
# <li> Set the <tt>WEBFORMS</tt> variable in WebPreferences to <tt>PackageForm</tt></li>
# <li> Set the <tt>WEBFORMS</tt> preference in WebPreferences to <tt>PackageForm</tt></li>
# </ol>
# The page for each extension must have the <tt>PackageForm</tt> (copy from Foswiki.org),
# and should have the packaged extension attached as a <tt>zip</tt> and/or
Expand Down
13 changes: 0 additions & 13 deletions core/lib/Foswiki/If/Node.pm
Expand Up @@ -16,19 +16,6 @@ use warnings;
use Foswiki::Query::Node ();
our @ISA = ('Foswiki::Query::Node');

use Foswiki::Infix::Node ();

sub newLeaf {
my ( $class, $val, $type ) = @_;
if ( $type == $Foswiki::Infix::Node::NAME && $val =~ /^({\w+})+$/ ) {
eval '$val = $Foswiki::cfg' . $val;
return $class->SUPER::newLeaf( $val, $Foswiki::Infix::Node::STRING );
}
else {
return $class->SUPER::newLeaf( $val, $type );
}
}

# Used wherever a plain string is expected, this method suppresses automatic
# lookup of names in meta-data
sub _evaluate {
Expand Down
1 change: 0 additions & 1 deletion core/lib/Foswiki/If/Parser.pm
Expand Up @@ -25,7 +25,6 @@ sub new {
my $this = $class->SUPER::new(
{
nodeClass => 'Foswiki::If::Node',
words => qr/([A-Z][A-Z0-9_:]+|({[A-Z0-9_]+})+)/i
}
);
die "{Operators}{If} is undefined; re-run configure"
Expand Down
1 change: 1 addition & 0 deletions core/lib/Foswiki/Macros/QUERY.pm
Expand Up @@ -10,6 +10,7 @@ sub QUERY {
my ( $this, $params, $topicObject ) = @_;
my $result;
my $expr = $params->{_DEFAULT};
$expr = '' unless defined $expr;
my $style = lc( $params->{style} || '' );

# Recursion block.
Expand Down
1 change: 1 addition & 0 deletions core/lib/Foswiki/Net.pm
Expand Up @@ -371,6 +371,7 @@ sub sendEmail {
$e = join( "\n", grep( /^ERROR/, split( /\n/, $e ) ) );

unless ( $e =~ /^ERROR/ ) {
# SMELL: maketext; and WIKIWEBMASTER is an email address
$e =
"Mail could not be sent - please ask your %WIKIWEBMASTER% to look at the Foswiki warning log.";
}
Expand Down
32 changes: 31 additions & 1 deletion core/lib/Foswiki/Query/Node.pm
Expand Up @@ -61,10 +61,32 @@ our %aliases = (
our %isArrayType =
map { $_ => 1 } qw( FILEATTACHMENT FIELD PREFERENCE );

# Cache of the names of $Foswiki::cfg items that are accessible
our $isAccessibleCfg;

# <DEBUG SUPPORT>

sub MONITOR_EVAL { 0 }

# We expand config vars to constant strings during the parse, because otherwise we'd
# have to export the knowledge of config vars out to other engines that may
# evaluate queries instead of the default evaluator.
sub newLeaf {
my ( $class, $val, $type ) = @_;

if ( $type == $Foswiki::Infix::Node::NAME && $val =~ /^({[A-Z][A-Z0-9_]*})+$/i ) {
# config var name, make sure it's accessible.
unless (defined $isAccessibleCfg) {
$isAccessibleCfg = { map { $_ => 1 } @{$Foswiki::cfg{AccessibleCFG}} };
}
$val = ($isAccessibleCfg->{$val}) ? eval( '$Foswiki::cfg'.$val ) : '';
return $class->SUPER::newLeaf( $val, $Foswiki::Infix::Node::STRING );
}
else {
return $class->SUPER::newLeaf( $val, $type );
}
}

sub toString {
my ($a) = @_;
return 'undef' unless defined($a);
Expand All @@ -90,9 +112,17 @@ my $ind = 0;
# field of the operator. The return result is either an array ref (for many
# results) or a scalar (for a single result)
#
# this function is a stub to execute the getField function in the
# This is the default evaluator for queries. However it may not be the only
# engine that evaluates them; external engines, such as SQL, might be delegated
# the responsibility of evaluating queries in a search context.
#
# Note that the name resolution simply executes the getField function in the
# query algorithm. It is placed there to allow for Store specific optimisations
# such as direct database lookups.
#
# SMELL: is the getField passed enough domain information? It can see the data
# object (usually a Meta) but cannot see the context of the name in the query.
#
sub evaluate {
my $this = shift;
ASSERT( scalar(@_) % 2 == 0 );
Expand Down
2 changes: 1 addition & 1 deletion core/lib/Foswiki/Query/Parser.pm
Expand Up @@ -36,7 +36,7 @@ use Foswiki::Query::Node ();
sub new {
my ( $class, $options ) = @_;

$options->{words} ||= qr/[A-Z][A-Z0-9_:]*/i;
$options->{words} ||= qr/([A-Z][A-Z0-9_:]*|({[A-Z][A-Z0-9_]*})+)/i;
$options->{nodeClass} ||= 'Foswiki::Query::Node';
my $this = $class->SUPER::new($options);
die "{Operators}{Query} is undefined; re-run configure"
Expand Down

0 comments on commit 146db70

Please sign in to comment.