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    }