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