001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.security; 020 021import lucee.commons.io.res.Resource; 022import lucee.commons.io.res.type.file.FileResourceProvider; 023import lucee.commons.io.res.util.ResourceUtil; 024import lucee.commons.lang.ExceptionUtil; 025import lucee.commons.lang.StringUtil; 026import lucee.runtime.PageContext; 027import lucee.runtime.PageContextImpl; 028import lucee.runtime.config.Config; 029import lucee.runtime.engine.ThreadLocalPageContext; 030import lucee.runtime.exp.PageException; 031import lucee.runtime.exp.SecurityException; 032import lucee.runtime.type.util.ArrayUtil; 033 034/** 035 * SecurityManager to control access to different services 036 */ 037public final class SecurityManagerImpl implements Cloneable, SecurityManager { 038 039 040 041 private static final Resource[] EMPTY_RESOURCE_ARRAY = new Resource[0]; 042 043 044 private short[] accesses=new short[22]; 045 private Resource rootDirectory; 046 private Resource[] customFileAccess=EMPTY_RESOURCE_ARRAY; 047 048 private SecurityManagerImpl() { 049 } 050 051 /** 052 * create a new Accessor 053 * @param setting 054 * @param file 055 * @param directJavaAccess 056 * @param mail 057 * @param datasource 058 * @param mapping 059 * @param customTag 060 * @param cfxSetting 061 * @param cfxUsage 062 * @param debugging 063 * @param search 064 * @param scheduledTasks 065 * @param tagExecute 066 * @param tagImport 067 * @param tagObject 068 * @param tagRegistry 069 * @param t 070 * @param accessRead 071 */ 072 public SecurityManagerImpl(short setting, short file,short directJavaAccess, 073 short mail, short datasource, short mapping, short remote, short customTag, 074 short cfxSetting, short cfxUsage, short debugging, 075 short search, short scheduledTasks, 076 short tagExecute, short tagImport, short tagObject, short tagRegistry, short cache, short gateway, short orm, short accessRead, short accessWrite) { 077 accesses[TYPE_SETTING]=setting; 078 accesses[TYPE_FILE]=file; 079 accesses[TYPE_DIRECT_JAVA_ACCESS]=directJavaAccess; 080 accesses[TYPE_MAIL]=mail; 081 accesses[TYPE_DATASOURCE]=datasource; 082 accesses[TYPE_MAPPING]=mapping; 083 accesses[TYPE_CUSTOM_TAG]=customTag; 084 accesses[TYPE_CFX_SETTING]=cfxSetting; 085 accesses[TYPE_CFX_USAGE]=cfxUsage; 086 accesses[TYPE_DEBUGGING]=debugging; 087 accesses[TYPE_SEARCH]=search; 088 accesses[TYPE_SCHEDULED_TASK]=scheduledTasks; 089 090 accesses[TYPE_TAG_EXECUTE]=tagExecute; 091 accesses[TYPE_TAG_IMPORT]=tagImport; 092 accesses[TYPE_TAG_OBJECT]=tagObject; 093 accesses[TYPE_TAG_REGISTRY]=tagRegistry; 094 accesses[TYPE_CACHE]=cache; 095 accesses[TYPE_GATEWAY]=gateway; 096 accesses[TYPE_ORM]=orm; 097 accesses[TYPE_ACCESS_READ]=accessRead; 098 accesses[TYPE_ACCESS_WRITE]=accessWrite; 099 accesses[TYPE_REMOTE]=remote; 100 101 102 } 103 104 /** 105 * @return return default accessor (no restriction) 106 */ 107 public static SecurityManager getOpenSecurityManager() { 108 return new SecurityManagerImpl( 109 VALUE_YES, // Setting 110 VALUE_ALL, // File 111 VALUE_YES, // Direct Java Access 112 VALUE_YES, // Mail 113 VALUE_YES, // Datasource 114 VALUE_YES, // Mapping 115 VALUE_YES, // Remote 116 VALUE_YES, // Custom tag 117 VALUE_YES, // CFX Setting 118 VALUE_YES, // CFX Usage 119 VALUE_YES, // Debugging 120 VALUE_YES, // Search 121 VALUE_YES, // Scheduled Tasks 122 VALUE_YES, // Tag Execute 123 VALUE_YES, // Tag Import 124 VALUE_YES, // Tag Object 125 VALUE_YES, // Tag Registry 126 VALUE_YES, // Cache 127 VALUE_YES, // Gateway 128 VALUE_YES, // ORM 129 ACCESS_OPEN, 130 ACCESS_PROTECTED); 131 132 } 133 134 @Override 135 public short getAccess(int access) { 136 return accesses[access]; 137 } 138 public void setAccess(int access, short value) { 139 accesses[access]=value;; 140 } 141 142 @Override 143 public short getAccess(String access) throws SecurityException { 144 return getAccess(toIntAccessType(access)); 145 } 146 147 /** 148 * translate a string access type (cfx,file ...) to int type 149 * @param accessType 150 * @return return access value (all,local,none ...) for given type (cfx,file ...) 151 * @throws SecurityException 152 */ 153 private static int toIntAccessType(String accessType) throws SecurityException { 154 accessType=accessType.trim().toLowerCase(); 155 if(accessType.equals("setting")) return TYPE_SETTING; 156 else if(accessType.equals("file")) return TYPE_FILE; 157 else if(accessType.equals("direct_java_access")) return TYPE_DIRECT_JAVA_ACCESS; 158 //else if(accessType.equals("search")) return TYPE_SEARCH; 159 else if(accessType.equals("mail")) return TYPE_MAIL; 160 //else if(accessType.equals("scheduled_task")) return TYPE_SCHEDULED_TASK; 161 else if(accessType.equals("datasource")) return TYPE_DATASOURCE; 162 else if(accessType.equals("mapping")) return TYPE_MAPPING; 163 else if(accessType.equals("remote")) return TYPE_REMOTE; 164 else if(accessType.equals("custom_tag")) return TYPE_CUSTOM_TAG; 165 else if(accessType.equals("cfx_setting")) return TYPE_CFX_SETTING; 166 else if(accessType.equals("cfx_usage")) return TYPE_CFX_USAGE; 167 else if(accessType.equals("debugging")) return TYPE_DEBUGGING; 168 else if(accessType.equals("tag_execute")) return TYPE_TAG_EXECUTE; 169 else if(accessType.equals("tag_import")) return TYPE_TAG_IMPORT; 170 else if(accessType.equals("tag_object")) return TYPE_TAG_OBJECT; 171 else if(accessType.equals("tag_registry")) return TYPE_TAG_REGISTRY; 172 else if(accessType.equals("search")) return TYPE_SEARCH; 173 else if(accessType.equals("cache")) return TYPE_CACHE; 174 else if(accessType.equals("gateway")) return TYPE_GATEWAY; 175 else if(accessType.equals("orm")) return TYPE_ORM; 176 else if(accessType.startsWith("scheduled_task")) return TYPE_SCHEDULED_TASK; 177 else throw new SecurityException( 178 "invalid access type ["+accessType+"]", 179 "valid access types are [setting,file,direct_java_access,mail,datasource,mapping,custom_tag,cfx_setting" + 180 "cfx_usage,debugging]"); 181 182 } 183 184 /** 185 * translate a string access value (all,local,none,no,yes) to int type 186 * @param accessValue 187 * @return return int access value (VALUE_ALL,VALUE_LOCAL,VALUE_NO,VALUE_NONE,VALUE_YES) 188 * @throws SecurityException 189 */ 190 public static short toShortAccessValue(String accessValue) throws SecurityException { 191 accessValue=accessValue.trim().toLowerCase(); 192 if(accessValue.equals("all")) return VALUE_ALL; 193 else if(accessValue.equals("local")) return VALUE_LOCAL; 194 else if(accessValue.equals("none")) return VALUE_NONE; 195 else if(accessValue.equals("no")) return VALUE_NO; 196 else if(accessValue.equals("yes")) return VALUE_YES; 197 else if(accessValue.equals("1")) return VALUE_1; 198 else if(accessValue.equals("2")) return VALUE_2; 199 else if(accessValue.equals("3")) return VALUE_3; 200 else if(accessValue.equals("4")) return VALUE_4; 201 else if(accessValue.equals("5")) return VALUE_5; 202 else if(accessValue.equals("6")) return VALUE_6; 203 else if(accessValue.equals("7")) return VALUE_7; 204 else if(accessValue.equals("8")) return VALUE_8; 205 else if(accessValue.equals("9")) return VALUE_9; 206 else if(accessValue.equals("10")) return VALUE_10; 207 else throw new SecurityException("invalid access value ["+accessValue+"]", "valid access values are [all,local,no,none,yes,1,...,10]"); 208 209 } 210 public static short toShortAccessRWValue(String accessValue) throws SecurityException { 211 accessValue=accessValue.trim().toLowerCase(); 212 if(accessValue.equals("open")) return ACCESS_OPEN; 213 else if(accessValue.equals("close")) return ACCESS_CLOSE; 214 else if(accessValue.equals("protected")) return ACCESS_PROTECTED; 215 else throw new SecurityException("invalid access value ["+accessValue+"]", "valid access values are [open,protected,close]"); 216 217 } 218 219 /** 220 * translate a string access value (all,local,none,no,yes) to int type 221 * @param accessValue 222 * @param defaultValue when accessValue is invlaid this value will be returned 223 * @return return int access value (VALUE_ALL,VALUE_LOCAL,VALUE_NO,VALUE_NONE,VALUE_YES) 224 */ 225 public static short toShortAccessValue(String accessValue, short defaultValue){ 226 accessValue=accessValue.trim().toLowerCase(); 227 if(accessValue.equals("no")) return VALUE_NO; 228 else if(accessValue.equals("yes")) return VALUE_YES; 229 else if(accessValue.equals("all")) return VALUE_ALL; 230 else if(accessValue.equals("local")) return VALUE_LOCAL; 231 else if(accessValue.equals("none")) return VALUE_NONE; 232 else if(accessValue.equals("1")) return VALUE_1; 233 else if(accessValue.equals("2")) return VALUE_2; 234 else if(accessValue.equals("3")) return VALUE_3; 235 else if(accessValue.equals("4")) return VALUE_4; 236 else if(accessValue.equals("5")) return VALUE_5; 237 else if(accessValue.equals("6")) return VALUE_6; 238 else if(accessValue.equals("7")) return VALUE_7; 239 else if(accessValue.equals("8")) return VALUE_8; 240 else if(accessValue.equals("9")) return VALUE_9; 241 else if(accessValue.equals("10")) return VALUE_10; 242 else if(accessValue.equals("0")) return VALUE_NO; 243 else if(accessValue.equals("-1")) return VALUE_YES; 244 else return defaultValue; 245 246 } 247 248 public static short toShortAccessRWValue(String accessValue, short defaultValue){ 249 accessValue=accessValue.trim().toLowerCase(); 250 if(accessValue.equals("open")) return ACCESS_OPEN; 251 else if(accessValue.equals("close")) return ACCESS_CLOSE; 252 else if(accessValue.equals("protected")) return ACCESS_PROTECTED; 253 else return defaultValue; 254 255 } 256 257 /** 258 * translate a short access value (all,local,none,no,yes) to String type 259 * @param accessValue 260 * @return return int access value (VALUE_ALL,VALUE_LOCAL,VALUE_NO,VALUE_NONE,VALUE_YES) 261 * @throws SecurityException 262 */ 263 public static String toStringAccessValue(short accessValue) throws SecurityException { 264 switch(accessValue) { 265 case VALUE_NONE: return "none"; 266 //case VALUE_NO: return "no"; 267 case VALUE_YES: return "yes"; 268 //case VALUE_ALL: return "all"; 269 case VALUE_LOCAL: return "local"; 270 case VALUE_1: return "1"; 271 case VALUE_2: return "2"; 272 case VALUE_3: return "3"; 273 case VALUE_4: return "4"; 274 case VALUE_5: return "5"; 275 case VALUE_6: return "6"; 276 case VALUE_7: return "7"; 277 case VALUE_8: return "8"; 278 case VALUE_9: return "9"; 279 case VALUE_10: return "10"; 280 } 281 throw new SecurityException("invalid access value", "valid access values are [all,local,no,none,yes,1,...,10]"); 282 283 } 284 285 286 public static String toStringAccessRWValue(short accessValue) throws SecurityException { 287 switch(accessValue) { 288 case ACCESS_CLOSE: return "close"; 289 case ACCESS_OPEN: return "open"; 290 case ACCESS_PROTECTED: return "protected"; 291 } 292 throw new SecurityException("invalid access value", "valid access values are [open,close,protected]"); 293 294 } 295 296 @Override 297 public void checkFileLocation(Resource res) throws SecurityException { 298 checkFileLocation(null, res, null); 299 } 300 301 public void checkFileLocation(Config config, Resource res, String serverPassword) throws SecurityException { 302 if(res==null || !(res.getResourceProvider() instanceof FileResourceProvider)){ 303 return; 304 } 305 306 // All 307 if(getAccess(TYPE_FILE)==VALUE_ALL) return; 308 // Local 309 if(getAccess(TYPE_FILE)==VALUE_LOCAL) { 310 res=ResourceUtil.getCanonicalResourceEL(res); 311 312 // local 313 if(rootDirectory!=null) 314 if(ResourceUtil.isChildOf(res,rootDirectory)) return; 315 // custom 316 if(!ArrayUtil.isEmpty(customFileAccess)){ 317 for(int i=0;i<customFileAccess.length;i++){ 318 if(ResourceUtil.isChildOf(res,customFileAccess[i])) return; 319 } 320 } 321 if(isValid(config,serverPassword) || isAdminContext()) return; 322 throw new SecurityException(createExceptionMessage(res,true),"access is prohibited by security manager"); 323 } 324 // None 325 if(isValid(config,serverPassword)) return; 326 327 // custom 328 if(!ArrayUtil.isEmpty(customFileAccess)){ 329 res=ResourceUtil.getCanonicalResourceEL(res); 330 331 for(int i=0;i<customFileAccess.length;i++){ 332 if(ResourceUtil.isChildOf(res,customFileAccess[i])) return; 333 } 334 } 335 336 337 338 if(isAdminContext()) return; 339 throw new SecurityException(createExceptionMessage(res,false),"access is prohibited by security manager"); 340 } 341 342 private boolean isAdminContext() { 343 PageContext pc = ThreadLocalPageContext.get(); 344 try { 345 if(pc!=null && "/lucee".equals(pc.getBasePageSource().getMapping().getVirtualLowerCase())){ 346 return true; 347 } 348 } 349 catch (Throwable t) { 350 ExceptionUtil.rethrowIfNecessary(t); 351 } 352 return false; 353 } 354 355 private String createExceptionMessage(Resource res, boolean localAllowed) { 356 357 358 StringBuffer sb=new StringBuffer(localAllowed && rootDirectory!=null?rootDirectory.getAbsolutePath():""); 359 if(customFileAccess!=null){ 360 for(int i=0;i<customFileAccess.length;i++){ 361 if(sb.length()>0)sb.append(" | "); 362 sb.append(customFileAccess[i].getAbsolutePath()); 363 } 364 } 365 366 StringBuffer rtn=new StringBuffer("can't access ["); 367 rtn.append(res.getAbsolutePath()); 368 rtn.append("]"); 369 if(sb.length()>0){ 370 rtn.append(" "); 371 rtn.append((res.isDirectory()?"directory":"file")); 372 rtn.append(" must be inside ["); 373 rtn.append(sb.toString()); 374 rtn.append("]"); 375 } 376 return rtn.toString(); 377 378 } 379 380 private boolean isValid(Config config, String serverPassword) { 381 if(StringUtil.isEmpty(serverPassword,true)) { 382 try { 383 PageContextImpl pc = (PageContextImpl) ThreadLocalPageContext.get(); 384 serverPassword=pc.getServerPassword(); 385 } 386 catch (Throwable t) { 387 ExceptionUtil.rethrowIfNecessary(t); 388 } 389 } 390 config=ThreadLocalPageContext.getConfig(config); 391 392 if(config==null || StringUtil.isEmpty(serverPassword,true)) return false; 393 try { 394 config.getConfigServer(serverPassword); 395 return true; 396 } catch (PageException e) { 397 return false; 398 } 399 } 400 401 402 @Override 403 public SecurityManager cloneSecurityManager() { 404 SecurityManagerImpl sm = new SecurityManagerImpl(); 405 406 for(int i=0;i<accesses.length;i++) { 407 sm.accesses[i]=accesses[i]; 408 } 409 if(customFileAccess!=null)sm.customFileAccess=(Resource[]) ArrayUtil.clone(customFileAccess,new Resource[customFileAccess.length]); 410 sm.rootDirectory=rootDirectory; 411 return sm; 412 } 413 414 @Override 415 public Object clone() { 416 return cloneSecurityManager(); 417 } 418 419 public Resource[] getCustomFileAccess() { 420 if(ArrayUtil.isEmpty(customFileAccess)) return EMPTY_RESOURCE_ARRAY; 421 return (Resource[]) ArrayUtil.clone(customFileAccess, new Resource[customFileAccess.length]); 422 } 423 424 public void setCustomFileAccess(Resource[] fileAccess) { 425 this.customFileAccess=merge(this.customFileAccess,fileAccess); 426 } 427 428 public void setRootDirectory(Resource rootDirectory) { 429 this.rootDirectory=rootDirectory; 430 } 431 432 private static Resource[] merge(Resource[] first,Resource[] second) { 433 if(ArrayUtil.isEmpty(second)) return first; 434 if(ArrayUtil.isEmpty(first)) return second; 435 436 Resource[] tmp = new Resource[first.length+second.length]; 437 for(int i=0;i<first.length;i++){ 438 tmp[i]=first[i]; 439 } 440 for(int i=0;i<second.length;i++){ 441 tmp[first.length+i]=second[i]; 442 } 443 return tmp; 444 } 445}