    001/*
    002    Licensed to the Apache Software Foundation (ASF) under one
    003    or more contributor license agreements.  See the NOTICE file
    004    distributed with this work for additional information
    005    regarding copyright ownership.  The ASF licenses this file
    006    to you under the Apache License, Version 2.0 (the
    007    "License"); you may not use this file except in compliance
    008    with the License.  You may obtain a copy of the License at
    009
    010       http://www.apache.org/licenses/LICENSE-2.0
    011
    012    Unless required by applicable law or agreed to in writing,
    013    software distributed under the License is distributed on an
    014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015    KIND, either express or implied.  See the License for the
    016    specific language governing permissions and limitations
    017    under the License.
    018 */
    019
    020package org.apache.wiki.plugin;
    021
    022import org.apache.commons.lang3.StringUtils;
    023import org.apache.oro.text.regex.MalformedPatternException;
    024import org.apache.oro.text.regex.Pattern;
    025import org.apache.oro.text.regex.PatternCompiler;
    026import org.apache.oro.text.regex.PatternMatcher;
    027import org.apache.oro.text.regex.Perl5Compiler;
    028import org.apache.oro.text.regex.Perl5Matcher;
    029import org.apache.wiki.api.core.Context;
    030import org.apache.wiki.api.exceptions.PluginException;
    031import org.apache.wiki.api.plugin.Plugin;
    032import org.apache.wiki.api.providers.WikiProvider;
    033import org.apache.wiki.auth.AuthorizationManager;
    034import org.apache.wiki.pages.PageManager;
    035import org.apache.wiki.render.RenderingManager;
    036import org.apache.wiki.util.HttpUtil;
    037import org.apache.wiki.util.TextUtil;
    038import org.apache.wiki.variables.VariableManager;
    039
    040import java.security.Principal;
    041import java.util.Locale;
    042import java.util.Map;
    043import java.util.ResourceBundle;
    044import org.apache.wiki.i18n.InternationalizationManager;
    045
    046/**
    047 *  The IfPlugin allows parts of a WikiPage to be executed conditionally, and is intended as a flexible way
    048 *  of customizing a page depending on certain conditions. Do not use it as a security mechanism to conditionally
    049 *  hide content from users (use page ACLs for that).
    050 *
    051 *  You can also use shorthand "If" to run it.
    052 *
    053 *  Parameters:
    054 *  <ul>
    055 *    <li><b>group</b> - A "|" -separated list of group names.</li>
    056 *    <li><b>user</b>  - A "|" -separated list of user names.</li>
    057 *    <li><b>ip</b>    - A "|" -separated list of ip addresses.</li>
    058 *    <li><b>var</b>   - A wiki variable</li>
    059 *    <li><b>page</b>  - A page name</li>
    060 *    <li><b>contains</b> - A Perl5 regexp pattern</li>
    061 *    <li><b>is</b>    - A Perl5 regexp pattern</li>
    062 *    <li><b>exists</b> - "true" or "false".</li>
    063 *  </ul>
    064 *
    065 *  <p>If any of them match, the body of the plugin is executed.  You can
    066 *  negate the content by prefixing it with a "!".  For example, to greet
    067 *  all admins, put the following in your LeftMenu:</p>
    068 *  <pre>
    069 *  [{If group='Admin'
    070 *
    071 *  Hello, Admin, and your mighty powers!}]
    072 *  </pre>
    073 *
    074 *  <p>In order to send a message to everybody except Jack use</p>
    075 *  <pre>
    076 *  [{If user='!Jack'
    077 *
    078 *  %%warning
    079 *  Jack's surprise birthday party at eleven!
    080 *  %%}]
    081 *  </pre>
    082 *
    083 *  <p>Note that you can't use "!Jack|!Jill", because for Jack, !Jill matches;
    084 *  and for Jill, !Jack matches.  These are not regular expressions (though
    085 *  they might become so in the future).<p>
    086 *
    087 *  <p>To check for page content, use</p>
    088 *  <pre>
    089 *  [{If page='TestPage' contains='xyzzy'
    090 *
    091 *  Page contains the text "xyzzy"}]
    092 *  </pre>
    093 *
    094 *  <p>The difference between "contains" and "is" is that "is" is always an exact match,
    095 *  whereas "contains" just checks if a pattern is available.</p>
    096 *
    097 *  <p>To check for page existence, use</p>
    098 *  <pre>
    099 *  [{If page='TestPage' exists='true'
    100 *
    101 *  Page "TestPage" exists.}]
    102 *  </pre>
    103 *  <p>With the same mechanism, it's also possible to test for the existence
    104 *  of a variable - just use "var" instead of "page".</p>
    105 *
    106 *  <p>Another caveat is that the plugin body content is not counted
    107 *  towards ReferenceManager links.  So any links do not appear on any reference
    108 *  lists.  Depending on your position, this may be a good or a bad
    109 *  thing.</p>
    110 *
    111 *  <h3>Calling Externally</h3>
    112 *
    113 *  <p>The functional, decision-making part of this plugin may be called from
    114 *  other code (e.g., other plugins) since it is available as a static method
    115 *  {@link #ifInclude(Context,Map)}. Note that the plugin body may contain
    116 *  references to other plugins.</p>
    117 *
    118 *  @since 2.6
    119 */
    120public class IfPlugin implements Plugin {
    121
    122    /** The parameter name for setting the group to check.  Value is <tt>{@value}</tt>. */
    123    public static final String PARAM_GROUP    = "group";
    124
    125    /** The parameter name for setting the user id to check.  Value is <tt>{@value}</tt>. */
    126    public static final String PARAM_USER     = "user";
    127
    128    /** The parameter name for setting the ip address to check.  Value is <tt>{@value}</tt>. */
    129    public static final String PARAM_IP       = "ip";
    130
    131    /** The parameter name for setting the page name to check.  Value is <tt>{@value}</tt>. */
    132    public static final String PARAM_PAGE     = "page";
    133
    134    /** The parameter name for setting the contents of the page to check.  Value is <tt>{@value}</tt>. */
    135    public static final String PARAM_CONTAINS = "contains";
    136
    137    /** The parameter name for setting the variable name to check.  Value is <tt>{@value}</tt>. */
    138    public static final String PARAM_VAR      = "var";
    139
    140    /** The parameter name for setting the exact content to check.  Value is <tt>{@value}</tt>. */
    141    public static final String PARAM_IS       = "is";
    142
    143    /** The parameter name for checking whether a page/var exists.  Value is <tt>{@value}</tt>. */
    144    public static final String PARAM_EXISTS   = "exists";
    145
    146    @Override
    147    public String getDisplayName(Locale locale) {
    148        final ResourceBundle rb = ResourceBundle.getBundle(PluginManager.PLUGIN_I18N_RESOURCE, locale);
    149        return rb.getString(this.getClass().getSimpleName());
    150    }
    151
    152    @Override
    153    public String getSnipExample() {
    154        return "If name='{value}' page='pagename' exists='true' contains='regexp'\n\nbody\n";
    155    }
    156    /**
    157     *  {@inheritDoc}
    158     */
    159    @Override
    160    public String execute( final Context context, final Map< String, String > params ) throws PluginException {
    161        return ifInclude( context,params )
    162                ? context.getEngine().getManager( RenderingManager.class ).textToHTML( context, params.get( DefaultPluginManager.PARAM_BODY ) )
    163                : "" ;
    164    }
    165
    166
    167    /**
    168     *  Returns a boolean result based on processing the WikiContext and
    169     *  parameter Map as according to the rules stated in the IfPlugin
    170     *  documentation.
    171     *  As a static method this may be called by other classes.
    172     *
    173     * @param context   The current WikiContext.
    174     * @param params    The parameter Map which contains key-value pairs.
    175     * @throws PluginException If something goes wrong
    176     * @return True, if the condition holds.
    177     */
    178    public static boolean ifInclude( final Context context, final Map< String, String > params ) throws PluginException {
    179        final String group    = params.get( PARAM_GROUP );
    180        final String user     = params.get( PARAM_USER );
    181        final String ip       = params.get( PARAM_IP );
    182        final String page     = params.get( PARAM_PAGE );
    183        final String contains = params.get( PARAM_CONTAINS );
    184        final String var      = params.get( PARAM_VAR );
    185        final String is       = params.get( PARAM_IS );
    186        final String exists   = params.get( PARAM_EXISTS );
    187
    188        boolean include = checkGroup( context, group );
    189        include |= checkUser(context, user);
    190        include |= checkIP(context, ip);
    191
    192        if( page != null ) {
    193            final String content = context.getEngine().getManager( PageManager.class ).getPureText(page, WikiProvider.LATEST_VERSION).trim();
    194            include |= checkContains(content,contains);
    195            include |= checkIs(content,is);
    196            include |= checkExists(context,page,exists);
    197        }
    198
    199        if( var != null ) {
    200            final String content = context.getEngine().getManager( VariableManager.class ).getVariable(context, var);
    201            include |= checkContains(content,contains);
    202            include |= checkIs(content,is);
    203            include |= checkVarExists(content,exists);
    204        }
    205
    206        return include;
    207    }
    208
    209    private static boolean checkExists( final Context context, final String page, final String exists ) {
    210        if( exists == null ) {
    211            return false;
    212        }
    213        return !context.getEngine().getManager( PageManager.class ).wikiPageExists( page ) ^ TextUtil.isPositive(exists);
    214    }
    215
    216    private static boolean checkVarExists( final String varContent, final String exists ) {
    217        if( exists == null ) {
    218            return false;
    219        }
    220        return varContent == null ^ TextUtil.isPositive( exists );
    221    }
    222
    223    private static boolean checkGroup( final Context context, final String group ) {
    224        if( group == null ) {
    225            return false;
    226        }
    227        final String[] groupList = StringUtils.split(group,'|');
    228        boolean include = false;
    229
    230        for( final String grp : groupList ) {
    231            String gname = grp;
    232            boolean invert = false;
    233            if( grp.startsWith( "!" ) ) {
    234                if( grp.length() > 1 ) {
    235                    gname = grp.substring( 1 );
    236                }
    237                invert = true;
    238            }
    239
    240            final Principal g = context.getEngine().getManager( AuthorizationManager.class ).resolvePrincipal( gname );
    241
    242            include |= context.getEngine().getManager( AuthorizationManager.class ).isUserInRole( context.getWikiSession(), g ) ^ invert;
    243        }
    244        return include;
    245    }
    246
    247    private static boolean checkUser( final Context context, final String user ) {
    248        if( user == null || context.getCurrentUser() == null ) {
    249            return false;
    250        }
    251
    252        final String[] list = StringUtils.split(user,'|');
    253        boolean include = false;
    254
    255        for( final String usr : list ) {
    256            String userToCheck = usr;
    257            boolean invert = false;
    258            if( usr.startsWith( "!" ) ) {
    259                invert = true;
    260                // strip !
    261                if( user.length() > 1 ) {
    262                    userToCheck = usr.substring( 1 );
    263                }
    264            }
    265
    266            include |= userToCheck.equals( context.getCurrentUser().getName() ) ^ invert;
    267        }
    268        return include;
    269    }
    270
    271    // TODO: Add subnetwork matching, e.g. 10.0.0.0/8
    272    private static boolean checkIP( final Context context, final String ipaddr ) {
    273        if( ipaddr == null || context.getHttpRequest() == null ) {
    274            return false;
    275        }
    276
    277        final String[] list = StringUtils.split(ipaddr,'|');
    278        boolean include = false;
    279
    280        for( final String ip : list ) {
    281            String ipaddrToCheck = ip;
    282            boolean invert = false;
    283            if( ip.startsWith( "!" ) ) {
    284                invert = true;
    285                // strip !
    286                if( ip.length() > 1 ) {
    287                    ipaddrToCheck = ip.substring( 1 );
    288                }
    289            }
    290
    291            include |= HttpUtil.ipIsInRange( context.getHttpRequest(), ipaddrToCheck ) ^ invert;
    292        }
    293        return include;
    294    }
    295
    296    private static boolean doMatch( final String content, final String pattern ) throws PluginException {
    297        final PatternCompiler compiler = new Perl5Compiler();
    298        final PatternMatcher  matcher  = new Perl5Matcher();
    299
    300        try {
    301            final Pattern matchp = compiler.compile( pattern, Perl5Compiler.SINGLELINE_MASK );
    302            return matcher.matches( content, matchp );
    303        } catch( final MalformedPatternException e ) {
    304            throw new PluginException( "Faulty pattern " + pattern );
    305        }
    306
    307    }
    308
    309    private static boolean checkContains( final String pagecontent, final String matchPattern ) throws PluginException {
    310        if( pagecontent == null || matchPattern == null ) {
    311            return false;
    312        }
    313
    314        return doMatch( pagecontent, ".*"+matchPattern+".*" );
    315    }
    316
    317    private static boolean checkIs( final String content, final String matchPattern ) throws PluginException {
    318        if( content == null || matchPattern == null ) {
    319            return false;
    320        }
    321        return doMatch( content, "^" + matchPattern + "$");
    322    }
    323
    324}