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