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