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