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    }