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}