package railo.runtime.security; import railo.commons.io.res.Resource; import railo.commons.io.res.type.file.FileResourceProvider; import railo.commons.io.res.util.ResourceUtil; import railo.commons.lang.StringUtil; import railo.runtime.PageContext; import railo.runtime.PageContextImpl; import railo.runtime.config.Config; import railo.runtime.engine.ThreadLocalPageContext; import railo.runtime.exp.PageException; import railo.runtime.exp.SecurityException; import railo.runtime.type.util.ArrayUtil; /** * SecurityManager to control access to different services */ public final class SecurityManagerImpl implements Cloneable, SecurityManager { private static final Resource[] EMPTY_RESOURCE_ARRAY = new Resource[0]; private short[] accesses=new short[22]; private Resource rootDirectory; private Resource[] customFileAccess=EMPTY_RESOURCE_ARRAY; private SecurityManagerImpl() { } /** * create a new Accessor * @param setting * @param file * @param directJavaAccess * @param mail * @param datasource * @param mapping * @param customTag * @param cfxSetting * @param cfxUsage * @param debugging * @param search * @param scheduledTasks * @param tagExecute * @param tagImport * @param tagObject * @param tagRegistry * @param t * @param accessRead */ public SecurityManagerImpl(short setting, short file,short directJavaAccess, short mail, short datasource, short mapping, short remote, short customTag, short cfxSetting, short cfxUsage, short debugging, short search, short scheduledTasks, short tagExecute, short tagImport, short tagObject, short tagRegistry, short cache, short gateway, short orm, short accessRead, short accessWrite) { accesses[TYPE_SETTING]=setting; accesses[TYPE_FILE]=file; accesses[TYPE_DIRECT_JAVA_ACCESS]=directJavaAccess; accesses[TYPE_MAIL]=mail; accesses[TYPE_DATASOURCE]=datasource; accesses[TYPE_MAPPING]=mapping; accesses[TYPE_CUSTOM_TAG]=customTag; accesses[TYPE_CFX_SETTING]=cfxSetting; accesses[TYPE_CFX_USAGE]=cfxUsage; accesses[TYPE_DEBUGGING]=debugging; accesses[TYPE_SEARCH]=search; accesses[TYPE_SCHEDULED_TASK]=scheduledTasks; accesses[TYPE_TAG_EXECUTE]=tagExecute; accesses[TYPE_TAG_IMPORT]=tagImport; accesses[TYPE_TAG_OBJECT]=tagObject; accesses[TYPE_TAG_REGISTRY]=tagRegistry; accesses[TYPE_CACHE]=cache; accesses[TYPE_GATEWAY]=gateway; accesses[TYPE_ORM]=orm; accesses[TYPE_ACCESS_READ]=accessRead; accesses[TYPE_ACCESS_WRITE]=accessWrite; accesses[TYPE_REMOTE]=remote; } /** * @return return default accessor (no restriction) */ public static SecurityManager getOpenSecurityManager() { return new SecurityManagerImpl( VALUE_YES, // Setting VALUE_ALL, // File VALUE_YES, // Direct Java Access VALUE_YES, // Mail VALUE_YES, // Datasource VALUE_YES, // Mapping VALUE_YES, // Remote VALUE_YES, // Custom tag VALUE_YES, // CFX Setting VALUE_YES, // CFX Usage VALUE_YES, // Debugging VALUE_YES, // Search VALUE_YES, // Scheduled Tasks VALUE_YES, // Tag Execute VALUE_YES, // Tag Import VALUE_YES, // Tag Object VALUE_YES, // Tag Registry VALUE_YES, // Cache VALUE_YES, // Gateway VALUE_YES, // ORM ACCESS_OPEN, ACCESS_PROTECTED); } @Override public short getAccess(int access) { return accesses[access]; } public void setAccess(int access, short value) { accesses[access]=value;; } @Override public short getAccess(String access) throws SecurityException { return getAccess(toIntAccessType(access)); } /** * translate a string access type (cfx,file ...) to int type * @param accessType * @return return access value (all,local,none ...) for given type (cfx,file ...) * @throws SecurityException */ private static int toIntAccessType(String accessType) throws SecurityException { accessType=accessType.trim().toLowerCase(); if(accessType.equals("setting")) return TYPE_SETTING; else if(accessType.equals("file")) return TYPE_FILE; else if(accessType.equals("direct_java_access")) return TYPE_DIRECT_JAVA_ACCESS; //else if(accessType.equals("search")) return TYPE_SEARCH; else if(accessType.equals("mail")) return TYPE_MAIL; //else if(accessType.equals("scheduled_task")) return TYPE_SCHEDULED_TASK; else if(accessType.equals("datasource")) return TYPE_DATASOURCE; else if(accessType.equals("mapping")) return TYPE_MAPPING; else if(accessType.equals("remote")) return TYPE_REMOTE; else if(accessType.equals("custom_tag")) return TYPE_CUSTOM_TAG; else if(accessType.equals("cfx_setting")) return TYPE_CFX_SETTING; else if(accessType.equals("cfx_usage")) return TYPE_CFX_USAGE; else if(accessType.equals("debugging")) return TYPE_DEBUGGING; else if(accessType.equals("tag_execute")) return TYPE_TAG_EXECUTE; else if(accessType.equals("tag_import")) return TYPE_TAG_IMPORT; else if(accessType.equals("tag_object")) return TYPE_TAG_OBJECT; else if(accessType.equals("tag_registry")) return TYPE_TAG_REGISTRY; else if(accessType.equals("search")) return TYPE_SEARCH; else if(accessType.equals("cache")) return TYPE_CACHE; else if(accessType.equals("gateway")) return TYPE_GATEWAY; else if(accessType.equals("orm")) return TYPE_ORM; else if(accessType.startsWith("scheduled_task")) return TYPE_SCHEDULED_TASK; else throw new SecurityException( "invalid access type ["+accessType+"]", "valid access types are [setting,file,direct_java_access,mail,datasource,mapping,custom_tag,cfx_setting" + "cfx_usage,debugging]"); } /** * translate a string access value (all,local,none,no,yes) to int type * @param accessValue * @return return int access value (VALUE_ALL,VALUE_LOCAL,VALUE_NO,VALUE_NONE,VALUE_YES) * @throws SecurityException */ public static short toShortAccessValue(String accessValue) throws SecurityException { accessValue=accessValue.trim().toLowerCase(); if(accessValue.equals("all")) return VALUE_ALL; else if(accessValue.equals("local")) return VALUE_LOCAL; else if(accessValue.equals("none")) return VALUE_NONE; else if(accessValue.equals("no")) return VALUE_NO; else if(accessValue.equals("yes")) return VALUE_YES; else if(accessValue.equals("1")) return VALUE_1; else if(accessValue.equals("2")) return VALUE_2; else if(accessValue.equals("3")) return VALUE_3; else if(accessValue.equals("4")) return VALUE_4; else if(accessValue.equals("5")) return VALUE_5; else if(accessValue.equals("6")) return VALUE_6; else if(accessValue.equals("7")) return VALUE_7; else if(accessValue.equals("8")) return VALUE_8; else if(accessValue.equals("9")) return VALUE_9; else if(accessValue.equals("10")) return VALUE_10; else throw new SecurityException("invalid access value ["+accessValue+"]", "valid access values are [all,local,no,none,yes,1,...,10]"); } public static short toShortAccessRWValue(String accessValue) throws SecurityException { accessValue=accessValue.trim().toLowerCase(); if(accessValue.equals("open")) return ACCESS_OPEN; else if(accessValue.equals("close")) return ACCESS_CLOSE; else if(accessValue.equals("protected")) return ACCESS_PROTECTED; else throw new SecurityException("invalid access value ["+accessValue+"]", "valid access values are [open,protected,close]"); } /** * translate a string access value (all,local,none,no,yes) to int type * @param accessValue * @param defaultValue when accessValue is invlaid this value will be returned * @return return int access value (VALUE_ALL,VALUE_LOCAL,VALUE_NO,VALUE_NONE,VALUE_YES) */ public static short toShortAccessValue(String accessValue, short defaultValue){ accessValue=accessValue.trim().toLowerCase(); if(accessValue.equals("no")) return VALUE_NO; else if(accessValue.equals("yes")) return VALUE_YES; else if(accessValue.equals("all")) return VALUE_ALL; else if(accessValue.equals("local")) return VALUE_LOCAL; else if(accessValue.equals("none")) return VALUE_NONE; else if(accessValue.equals("1")) return VALUE_1; else if(accessValue.equals("2")) return VALUE_2; else if(accessValue.equals("3")) return VALUE_3; else if(accessValue.equals("4")) return VALUE_4; else if(accessValue.equals("5")) return VALUE_5; else if(accessValue.equals("6")) return VALUE_6; else if(accessValue.equals("7")) return VALUE_7; else if(accessValue.equals("8")) return VALUE_8; else if(accessValue.equals("9")) return VALUE_9; else if(accessValue.equals("10")) return VALUE_10; else if(accessValue.equals("0")) return VALUE_NO; else if(accessValue.equals("-1")) return VALUE_YES; else return defaultValue; } public static short toShortAccessRWValue(String accessValue, short defaultValue){ accessValue=accessValue.trim().toLowerCase(); if(accessValue.equals("open")) return ACCESS_OPEN; else if(accessValue.equals("close")) return ACCESS_CLOSE; else if(accessValue.equals("protected")) return ACCESS_PROTECTED; else return defaultValue; } /** * translate a short access value (all,local,none,no,yes) to String type * @param accessValue * @return return int access value (VALUE_ALL,VALUE_LOCAL,VALUE_NO,VALUE_NONE,VALUE_YES) * @throws SecurityException */ public static String toStringAccessValue(short accessValue) throws SecurityException { switch(accessValue) { case VALUE_NONE: return "none"; //case VALUE_NO: return "no"; case VALUE_YES: return "yes"; //case VALUE_ALL: return "all"; case VALUE_LOCAL: return "local"; case VALUE_1: return "1"; case VALUE_2: return "2"; case VALUE_3: return "3"; case VALUE_4: return "4"; case VALUE_5: return "5"; case VALUE_6: return "6"; case VALUE_7: return "7"; case VALUE_8: return "8"; case VALUE_9: return "9"; case VALUE_10: return "10"; } throw new SecurityException("invalid access value", "valid access values are [all,local,no,none,yes,1,...,10]"); } public static String toStringAccessRWValue(short accessValue) throws SecurityException { switch(accessValue) { case ACCESS_CLOSE: return "close"; case ACCESS_OPEN: return "open"; case ACCESS_PROTECTED: return "protected"; } throw new SecurityException("invalid access value", "valid access values are [open,close,protected]"); } @Override public void checkFileLocation(Resource res) throws SecurityException { checkFileLocation(null, res, null); } public void checkFileLocation(Config config, Resource res, String serverPassword) throws SecurityException { if(res==null || !(res.getResourceProvider() instanceof FileResourceProvider)){ return; } // All if(getAccess(TYPE_FILE)==VALUE_ALL) return; // Local if(getAccess(TYPE_FILE)==VALUE_LOCAL) { res=ResourceUtil.getCanonicalResourceEL(res); // local if(rootDirectory!=null) if(ResourceUtil.isChildOf(res,rootDirectory)) return; // custom if(!ArrayUtil.isEmpty(customFileAccess)){ for(int i=0;i<customFileAccess.length;i++){ if(ResourceUtil.isChildOf(res,customFileAccess[i])) return; } } if(isValid(config,serverPassword) || isAdminContext()) return; throw new SecurityException(createExceptionMessage(res,true),"access is prohibited by security manager"); } // None if(isValid(config,serverPassword)) return; // custom if(!ArrayUtil.isEmpty(customFileAccess)){ res=ResourceUtil.getCanonicalResourceEL(res); for(int i=0;i<customFileAccess.length;i++){ if(ResourceUtil.isChildOf(res,customFileAccess[i])) return; } } if(isAdminContext()) return; throw new SecurityException(createExceptionMessage(res,false),"access is prohibited by security manager"); } private boolean isAdminContext() { PageContext pc = ThreadLocalPageContext.get(); try { if(pc!=null && "/railo-context".equals(pc.getBasePageSource().getMapping().getVirtualLowerCase())){ return true; } } catch (Throwable t) {} return false; } private String createExceptionMessage(Resource res, boolean localAllowed) { StringBuffer sb=new StringBuffer(localAllowed && rootDirectory!=null?rootDirectory.getAbsolutePath():""); if(customFileAccess!=null){ for(int i=0;i<customFileAccess.length;i++){ if(sb.length()>0)sb.append(" | "); sb.append(customFileAccess[i].getAbsolutePath()); } } StringBuffer rtn=new StringBuffer("can't access ["); rtn.append(res.getAbsolutePath()); rtn.append("]"); if(sb.length()>0){ rtn.append(" "); rtn.append((res.isDirectory()?"directory":"file")); rtn.append(" must be inside ["); rtn.append(sb.toString()); rtn.append("]"); } return rtn.toString(); } private boolean isValid(Config config, String serverPassword) { if(StringUtil.isEmpty(serverPassword,true)) { try { PageContextImpl pc = (PageContextImpl) ThreadLocalPageContext.get(); serverPassword=pc.getServerPassword(); } catch (Throwable t) {} } config=ThreadLocalPageContext.getConfig(config); if(config==null || StringUtil.isEmpty(serverPassword,true)) return false; try { config.getConfigServer(serverPassword); return true; } catch (PageException e) { return false; } } @Override public SecurityManager cloneSecurityManager() { SecurityManagerImpl sm = new SecurityManagerImpl(); for(int i=0;i<accesses.length;i++) { sm.accesses[i]=accesses[i]; } if(customFileAccess!=null)sm.customFileAccess=(Resource[]) ArrayUtil.clone(customFileAccess,new Resource[customFileAccess.length]); sm.rootDirectory=rootDirectory; return sm; } @Override public Object clone() { return cloneSecurityManager(); } public Resource[] getCustomFileAccess() { if(ArrayUtil.isEmpty(customFileAccess)) return EMPTY_RESOURCE_ARRAY; return (Resource[]) ArrayUtil.clone(customFileAccess, new Resource[customFileAccess.length]); } public void setCustomFileAccess(Resource[] fileAccess) { this.customFileAccess=merge(this.customFileAccess,fileAccess); } public void setRootDirectory(Resource rootDirectory) { this.rootDirectory=rootDirectory; } private static Resource[] merge(Resource[] first,Resource[] second) { if(ArrayUtil.isEmpty(second)) return first; if(ArrayUtil.isEmpty(first)) return second; Resource[] tmp = new Resource[first.length+second.length]; for(int i=0;i<first.length;i++){ tmp[i]=first[i]; } for(int i=0;i<second.length;i++){ tmp[first.length+i]=second[i]; } return tmp; } }