root/coldbox/trunk/system/interceptors/security.cfc @ 1737

Revision 1737, 19.0 kB (checked in by lmajano, 5 years ago)

Minor fixes

Line 
1<!-----------------------------------------------------------------------
2********************************************************************************
3Copyright 2005-2008 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
4www.coldboxframework.com | www.luismajano.com | www.ortussolutions.com
5********************************************************************************
6
7Author     :    Luis Majano
8Date        :   02/29/2008
9Description :
10
11This interceptor provides security to an application. It is very flexible
12and customizable. It bases off on the ability to secure events by creating
13rules. This interceptor will then try to match a rule to the incoming event
14and the user's credentials on roles and/or permissions.
15       
16For the latest usage, please visit the wiki.
17----------------------------------------------------------------------->
18<cfcomponent name="security"
19                         hint="This is a security interceptor"
20                         output="false"
21                         extends="coldbox.system.interceptor">
22
23<!------------------------------------------- CONSTRUCTOR ------------------------------------------->
24
25        <cffunction name="Configure" access="public" returntype="void" hint="This is the configuration method for your interceptors" output="false" >
26                <cfscript>
27                        /* Start processing properties */
28                        if( not propertyExists('useRegex') or not isBoolean(getproperty('useRegex')) ){
29                                setProperty('useRegex',true);
30                        }
31                        if( not propertyExists('useRoutes') or not isBoolean(getproperty('useRoutes')) ){
32                                setProperty('useRoutes',false);
33                        }
34                        if( not propertyExists('debugMode') or not isBoolean(getproperty('debugMode')) ){
35                                setProperty('debugMode',false);
36                        }
37                        /* Source Checks */
38                        if( not propertyExists('rulesSource') ){
39                                throw(message="The rulesSource property has not been set.",type="interceptors.security.settingUndefinedException");
40                        }
41                        if( not reFindnocase("^(xml|db|ioc|ocm)$",getProperty('rulesSource')) ){
42                                throw(message="The rules source you set is invalid: #getProperty('rulesSource')#.",
43                                          detail="The valid sources are xml,db,ioc, and ocm.",
44                                          type="interceptors.security.settingUndefinedException");
45                        }
46                        /* Query Checks */
47                        if( not propertyExists("queryChecks") or not isBoolean(getProperty("queryChecks")) ){
48                                setProperty("queryChecks",true);
49                        }
50                        /* PreEvent Security */
51                        if( not propertyExists("preEventSecurity") or not isBoolean(getProperty("preEventSecurity")) ){
52                                setProperty("preEventSecurity",false);
53                        }
54                       
55                        /* Now Call sourcesCheck */
56                        RulesSourceChecks();
57                       
58                        /* Create the internal properties now */
59                        setProperty('rules',Arraynew(1));
60                        setProperty('rulesLoaded',false);
61                </cfscript>
62        </cffunction>
63
64<!------------------------------------------- INTERCEPTION POINTS ------------------------------------------->
65
66        <!--- After Aspects Load --->
67        <cffunction name="afterAspectsLoad" access="public" returntype="void" output="false" >
68                <!--- ************************************************************* --->
69                <cfargument name="event"                 required="true" type="coldbox.system.beans.requestContext" hint="The event object.">
70                <cfargument name="interceptData" required="true" type="struct" hint="interceptData of intercepted info.">
71                <!--- ************************************************************* --->
72                <cfscript>
73                        var oValidator = "";
74                       
75                        /* Load Rules */
76                        switch( getProperty('rulesSource') ){
77                                case "xml" : {
78                                        loadXMLRules();
79                                        break;
80                                }
81                                case "db" : {
82                                        loadDBRules();
83                                        break;
84                                }
85                                case "ioc" : {
86                                        loadIOCRules();
87                                        break;
88                                }               
89                        }//end of switch
90                       
91                        /* See if using validator */
92                        if( propertyExists('validator') ){
93                                /* Try to create Validator */
94                                try{
95                                        /* Create it */
96                                        oValidator = CreateObject("component",getProperty('validator'));
97                                        /* Verify the init */
98                                        if( structKeyExists(oValidator, "init") ){
99                                                oValidator = oValidator.init(controller);
100                                        }
101                                        /* Cache It */
102                                        setValidator(oValidator);
103                                }
104                                catch(Any e){
105                                        throw("Error creating validator",e.message & e.detail, "interceptors.security.validatorCreationException");
106                                }
107                        }
108                       
109                        /* See if using validator from ioc */
110                        if( propertyExists('validatorIOC') ){
111                                /* Try to create Validator */
112                                try{
113                                        setValidator( getPlugin("ioc").getBean(getProperty('validatorIOC')) );
114                                }
115                                catch(Any e){
116                                        throw("Error creating validator",e.message & e.detail, "interceptors.security.validatorCreationException");
117                                }
118                        }
119                </cfscript>
120        </cffunction>
121       
122        <!--- pre-process --->
123        <cffunction name="preProcess" access="public" returntype="void" output="false" >
124                <!--- ************************************************************* --->
125                <cfargument name="event"                 required="true" type="coldbox.system.beans.requestContext" hint="The event object.">
126                <cfargument name="interceptData" required="true" type="struct" hint="interceptData of intercepted info.">
127                <!--- ************************************************************* --->
128                <cfscript>
129                        /* Load OCM rules */
130                        if( getProperty('rulesSource') eq "ocm" and not getProperty('rulesLoaded') ){
131                                loadOCMRules();
132                        }
133                       
134                        /* Execute Rule processing */
135                        processRules(arguments.event,arguments.interceptData,arguments.event.getCurrentEvent());
136                       
137                </cfscript>
138        </cffunction>
139       
140        <!--- pre-event --->
141        <cffunction name="preEvent" access="public" returntype="void" output="false" >
142                <!--- ************************************************************* --->
143                <cfargument name="event"                 required="true" type="coldbox.system.beans.requestContext" hint="The event object.">
144                <cfargument name="interceptData" required="true" type="struct" hint="interceptData of intercepted info.">
145                <!--- ************************************************************* --->
146                <cfscript>
147                       
148                        /* Execute Rule processing */
149                        processRules(arguments.event,arguments.interceptData,arguments.interceptData.processedEvent);
150                       
151                </cfscript>
152        </cffunction>
153       
154        <!--- Process Rules --->
155        <cffunction name="processRules" access="public" returntype="void" hint="Process security rules. This method is called from an interception point" output="false" >
156                <!--- ************************************************************* --->
157                <cfargument name="event"                 required="true" type="coldbox.system.beans.requestContext" hint="The event object.">
158                <cfargument name="interceptData" required="true" type="struct" hint="interceptData of intercepted info.">
159                <cfargument name="currentEvent"  required="true" type="string" hint="The event to check">
160                <!--- ************************************************************* --->
161                <cfscript>
162                        var x = 1;
163                        var rules = getProperty('rules');
164                        var rulesLen = arrayLen(rules);
165                        var rc = event.getCollection();
166                       
167                        /* Loop through Rules */
168                        for(x=1; x lte rulesLen; x=x+1){
169                                /* is current event in this whitelist pattern? then continue to next rule */
170                                if( isEventInPattern(currentEvent,rules[x].whitelist) ){
171                                        if( getProperty('debugMode') ){
172                                                getPlugin("logger").logEntry("information","#currentEvent# found in whitelist: #rules[x].whitelist#");
173                                        }
174                                        continue;
175                                }
176                                /* is currentEvent in the secure list and is user in role */
177                                if( isEventInPattern(currentEvent,rules[x].securelist) ){
178                                        /* Verify if user is logged in and in a secure state */
179                                        if( _isUserInValidState(rules[x]) eq false ){
180                                                /* Log if Necessary */
181                                                if( getProperty('debugMode') ){
182                                                        getPlugin("logger").logEntry("warning","User not in appropriate roles #rules[x].roles# for event=#currentEvent#");
183                                                }
184                                                /* Redirect */
185                                                if( getProperty('useRoutes') ){
186                                                        /* Save the secured URL */
187                                                        rc._securedURL = "#cgi.script_name##cgi.path_info#";
188                                                        if( cgi.query_string neq ""){
189                                                                rc._securedURL = rc._securedURL & "?#cgi.query_string#";
190                                                        }
191                                                        /* Route to safe event */
192                                                        setNextRoute(route=rules[x].redirect,persist="_securedURL");
193                                                }
194                                                else{
195                                                        /* Save the secured URL */
196                                                        rc._securedURL = "#cgi.script_name#";
197                                                        if( cgi.query_string neq ""){
198                                                                rc._securedURL = rc._securedURL & "?#cgi.query_string#";
199                                                        }
200                                                        /* Route to safe event */
201                                                        setNextEvent(event=rules[x].redirect,persist="_securedURL");
202                                                }
203                                                break;
204                                        }//end user in roles
205                                        else{
206                                                if( getProperty('debugMode') ){
207                                                        //User is in role. continue.
208                                                        getPlugin("logger").logEntry("information","Secure event=#currentEvent# matched and user is in roles=#rules[x].roles#. Proceeding");
209                                                }
210                                                break;
211                                        }
212                                }//end if current event did not match a secure event.
213                                else{
214                                        if( getProperty('debugMode') ){
215                                                getPlugin("logger").logEntry("information","#currentEvent# Did not match this rule: #rules[x].toString()#");
216                                        }
217                                }                                                       
218                        }//end of rules checks
219                </cfscript>
220        </cffunction>
221       
222        <!--- Register a validator --->
223        <cffunction name="registerValidator" access="public" returntype="void" hint="Register a validator object with this interceptor" output="false" >
224                <cfargument name="validatorObject" required="true" type="any" hint="The validator object to register">
225                <cfscript>
226                        /* Test if it has the correct method on it */
227                        if( structKeyExists(arguments.validatorObject,"userValidator") ){
228                                setValidator(arguments.validatorObject);
229                        }
230                        else{
231                                throw(message="Validator object does not have a 'userValidator' method ",type="interceptors.security.validatorException");
232                        }
233                </cfscript>
234        </cffunction>   
235       
236<!------------------------------------------- PRIVATE METHDOS ------------------------------------------->
237       
238        <!--- isEventInPattern --->
239        <cffunction name="_isUserInValidState" access="private" returntype="boolean" output="false" hint="Verifies that the user is in any role">
240                <!--- ************************************************************* --->
241                <cfargument name="rule" required="true" type="struct" hint="The rule we are validating.">
242                <!--- ************************************************************* --->
243                <cfset var thisRole = "">
244               
245                <!--- Verify if using validator --->
246                <cfif isValidatorUsed()>
247                        <!--- Validate via Validator --->
248                        <cfreturn getValidator().userValidator(arguments.rule,getPlugin("messagebox"),controller)>
249                <cfelse>
250                        <!--- Loop Over Roles --->
251                        <cfloop list="#arguments.rule.roles#" index="thisRole">
252                                <cfif isUserInRole(thisRole)>
253                                        <cfreturn true>
254                                </cfif>
255                        </cfloop>       
256                        <cfreturn false>
257                </cfif>
258        </cffunction>
259       
260        <!--- isEventInPattern --->
261        <cffunction name="isEventInPattern" access="private" returntype="boolean" output="false" hint="Verifies that the current event is in a given pattern list">
262                <!--- ************************************************************* --->
263                <cfargument name="currentEvent"         required="true" type="string" hint="The current event.">
264                <cfargument name="patternList"          required="true" type="string" hint="The list to test.">
265                <!--- ************************************************************* --->
266                <cfset var pattern = "">
267                <!--- Loop Over Patterns --->
268                <cfloop list="#arguments.patternList#" index="pattern">
269                        <!--- Using Regex --->
270                        <cfif getProperty('useRegex')>
271                                <cfif reFindNocase(pattern,arguments.currentEvent)>
272                                        <cfreturn true>
273                                </cfif>
274                        <cfelseif FindNocase(pattern,arguments.currentEvent)>
275                                        <cfreturn true>
276                        </cfif>
277                </cfloop>       
278                <cfreturn false>       
279        </cffunction>
280               
281        <!--- Load XML Rules --->
282        <cffunction name="loadXMLRules" access="private" returntype="void" output="false" hint="Load rules from XML file">
283                <cfscript>
284                        /* Validate the XML File */
285                        var rulesFile = "";
286                        var xmlRules = "";
287                        var x=1;
288                        var node = "";
289                       
290                        /* Try to locate the file path */
291                        rulesFile = locateFilePath(getProperty('rulesFile'));
292                        /* Validate Location */
293                        if( len(rulesFile) eq 0 ){
294                                throw('Security Rules File could not be located: #getProperty('rulesFile')#. Please check again.','','interceptors.security.rulesFileNotFound');
295                        }
296                       
297                        /* Set the correct expanded path now */
298                        setProperty('rulesFile',rulesFile);
299                        /* Read in and parse */
300                        xmlRules = xmlSearch(XMLParse(rulesFile),"/rules/rule");
301                        /* Loop And create Rules */
302                        for(x=1; x lte Arraylen(xmlRules); x=x+1){
303                                node = structnew();
304                                node.whitelist = trim(xmlRules[x].whitelist.xmlText);
305                                node.securelist = trim(xmlRules[x].securelist.xmlText);
306                                node.roles = trim(xmlRules[x].roles.xmlText);
307                                node.permissions = trim(xmlRules[x].permissions.xmlText);
308                                node.redirect = trim(xmlRules[x].redirect.xmlText);
309                                ArrayAppend(getProperty('rules'),node);
310                        }
311                        /* finalize */
312                        setProperty('rulesLoaded',true);       
313                </cfscript>
314        </cffunction>
315       
316        <!--- Load DB Rules --->
317        <cffunction name="loadDBRules" access="private" returntype="void" output="false" hint="Load rules from the database">
318                <cfset var qRules = "">
319               
320                <!--- Let's get our rules from the DB --->
321                <cfquery name="qRules" datasource="#getProperty('rulesDSN')#">
322                <cfif propertyExists('rulesSQL') and len(getProperty('rulesSQL'))>
323                        #getProperty('rulesSQL')#
324                <cfelse>
325                        SELECT *
326                          FROM #getProperty('rulesTable')#
327                        <cfif propertyExists('rulesOrderBy') and len(getProperty('rulesOrderBy'))>
328                        ORDER BY #getProperty('rulesOrderBy')#
329                        </cfif>
330                </cfif>
331                </cfquery>
332               
333                <!--- validate query --->
334                <cfset validateRulesQuery(qRules)>
335               
336                <!--- let's setup the array of struct Rules now --->
337                <cfset setProperty('rules', queryToArray(qRules))>
338                <cfset setProperty('rulesLoaded',true)>
339        </cffunction>
340       
341        <!--- Load XML Rules --->
342        <cffunction name="loadIOCRules" access="private" returntype="void" output="false" hint="Load rules from an IOC bean">
343                <cfset var qRules = "">
344                <cfset var bean = "">
345               
346                <!--- Get rules from IOC Container --->
347                <cfset bean = getPlugin("ioc").getBean(getproperty('rulesBean'))>
348               
349                <cfif propertyExists('rulesBeanArgs') and len(getProperty('rulesBeanArgs'))>
350                        <cfset qRules = evaluate("bean.#getproperty('rulesBeanMethod')#( #getProperty('rulesBeanArgs')# )")>
351                <cfelse>
352                        <!--- Now call method on it --->
353                        <cfinvoke component="#bean#" method="#getProperty('rulesBeanMethod')#" returnvariable="qRules" />
354                </cfif>
355               
356                <!--- validate query --->
357                <cfset validateRulesQuery(qRules)>
358               
359                <!--- let's setup the array of struct Rules now --->
360                <cfset setProperty('rules', queryToArray(qRules))>
361                <cfset setProperty('rulesLoaded',true)>
362        </cffunction>
363       
364        <!--- Load XML Rules --->
365        <cffunction name="loadOCMRules" access="private" returntype="void" output="false" hint="Load rules from the OCM">
366                <cfset var qRules = "">
367               
368                <!--- Get Rules From OCM --->
369                <cfif not getColdboxOCM().lookup(getProperty('rulesOCMkey'))>
370                        <cfthrow message="No key #getProperty('rulesOCMKey')# in the OCM." type="interceptors.security.invalidOCMKey">
371                <cfelse>
372                        <cfset qRules = getColdboxOCM().get(getProperty('rulesOCMKey'))>
373                </cfif>
374               
375                <!--- validate query --->
376                <cfset validateRulesQuery(qRules)>
377               
378                <!--- let's setup the array of struct Rules now --->
379                <cfset setProperty('rules', queryToArray(qRules))>
380                <cfset setProperty('rulesLoaded',true)>
381        </cffunction>
382       
383        <!--- ValidateRules Query --->
384        <cffunction name="validateRulesQuery" access="private" returntype="void" output="false" hint="Validate a query as a rules query, else throw error.">
385                <!--- ************************************************************* --->
386                <cfargument name="qRules" type="query" required="true" hint="The query to check">
387                <!--- ************************************************************* --->
388                <cfset var validColumns = "whitelist,securelist,roles,permissions,redirect">
389                <cfset var col = "">
390               
391                <!--- Verify only if used --->
392                <cfif getProperty("queryChecks")>
393                        <!--- Validate Query --->
394                        <cfloop list="#validColumns#" index="col">
395                                <cfif not listfindnocase(arguments.qRules.columnlist,col)>
396                                        <cfthrow message="The required column: #col# was not found in the rules query" type="interceptors.security.invalidRuleQuery">
397                                </cfif>
398                        </cfloop>
399                </cfif>
400        </cffunction>
401       
402        <!--- queryToArray --->
403        <cffunction name="queryToArray" access="private" returntype="array" output="false" hint="Convert a rules query to our array format">
404                <!--- ************************************************************* --->
405                <cfargument name="qRules" type="query" required="true" hint="The query to convert">
406                <!--- ************************************************************* --->
407                <cfscript>
408                        var x =1;
409                        var y =1;
410                        var node = "";
411                        var rtnArray = ArrayNew(1);
412                        var columns = arguments.qRules.columnlist;
413                       
414                        /* Loop over Rules */
415                        for(x=1; x lte qRules.recordcount; x=x+1){
416                                /* Create Row Node */
417                                node = structnew();
418                               
419                                /* Create Node with all columns */
420                                for(y=1; y lte listLen(columns); y=y+1){
421                                        node[listgetAt(columns,y)] = qRules[listgetAt(columns,y)][x];
422                                }
423                               
424                                /* Append it to the array */
425                                ArrayAppend(rtnArray,node);
426                        }
427                        /* return array */
428                        return rtnArray;
429                </cfscript>
430        </cffunction>
431       
432        <!--- rules sources check --->
433        <cffunction name="RulesSourceChecks" access="private" returntype="void" output="false" hint="Validate the rules source property" >
434                <cfscript>
435                        switch( getProperty('rulesSource') ){
436                               
437                                case "xml" :
438                                {
439                                        /* Check if file property exists */
440                                        if( not propertyExists('rulesFile') ){
441                                                throw(message="Missing setting for XML source: rulesFile ",type="interceptors.security.settingUndefinedException");
442                                        }
443                                        break;
444                                }//end of xml check
445                               
446                                case "db" :
447                                {
448                                        /* Check for DSN */
449                                        if( not propertyExists('rulesDSN') ){
450                                                throw(message="Missing setting for DB source: rulesDSN ",type="interceptors.security.settingUndefinedException");
451                                        }
452                                        /* Check for table */
453                                        if( not propertyExists('rulesTable') ){
454                                                throw(message="Missing setting for DB source: rulesTable ",type="interceptors.security.settingUndefinedException");
455                                        }
456                                        /* Optional DB settings are checked when loading rules. */
457                                        break;
458                                }//end of db check
459                               
460                                case "ioc" :
461                                {
462                                        /* Check for bean */
463                                        if( not propertyExists('rulesBean') ){
464                                                throw(message="Missing setting for ioc source: rulesBean ",type="interceptors.security.settingUndefinedException");
465                                        }
466                                        if( not propertyExists('rulesBeanMethod') ){
467                                                throw(message="Missing setting for ioc source: rulesBeanMethod ",type="interceptors.security.settingUndefinedException");
468                                        }
469                                       
470                                        break;
471                                }//end of ioc check
472                               
473                                case "ocm" :
474                                {
475                                        /* Check for bean */
476                                        if( not propertyExists('rulesOCMkey') ){
477                                                throw(message="Missing setting for ioc source: rulesOCMkey ",type="interceptors.security.settingUndefinedException");
478                                        }
479                                        break;
480                                }//end of OCM check                     
481                       
482                        }//end of switch statement                     
483                </cfscript>
484        </cffunction>
485       
486        <!--- Get/Set Validator --->
487        <cffunction name="getvalidator" access="private" output="false" returntype="any" hint="Get validator">
488                <cfreturn instance.validator/>
489        </cffunction>   
490        <cffunction name="setvalidator" access="private" output="false" returntype="void" hint="Set validator">
491                <cfargument name="validator" type="any" required="true"/>
492                <cfset instance.validator = arguments.validator/>
493        </cffunction>
494       
495        <!--- Check if using validator --->
496        <cffunction name="isValidatorUsed" access="private" returntype="boolean" hint="Check to see if using the validator" output="false" >
497                <cfreturn structKeyExists(instance, "validator")>
498        </cffunction>
499       
500</cfcomponent>
Note: See TracBrowser for help on using the browser.