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