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}