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}