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