Can onCFCRequest, or a similar function, be used to write data from a cfc to the caller request scope? - coldfusion

I'd like to initiate some logging of requests and, more importantly, queries within my application. Lucee makes this pretty easy, and I'm able to log all queries within any given page run with a few lines of code within the onRequestEnd function:
<cfset getQueries = getPageContext().variablesScope()>
<cfset queryArr = ArrayNew(2)>
<cfset x = 0>
<cfloop collection="#getQueries#" item="key">
<cfif IsQuery(getQueries[key])>
<cftry>
<cfset thisQ = getQueries[key]>
<cfset thisQT = thisQ.getExecutionTime() / 1000000>
<cfset thisSQL = thisQ.getSQL().getSQLString()>
<cfset x = x + 1>
<cfset queryArr[x][1] = thisQT>
<cfset queryArr[x][2] = thisSQL>
<cfcatch>
</cfcatch>
</cftry>
</cfif>
</cfloop>
This gives me an array with each query's SQL (with bind variables represented with ?) and the execution time. I can insert this into a logging database and have all kinds of fun with it.
The problem is that I have many pages that call CFCs, which run queries and then return data:
<cfset someVar = createObject("component","cfc.test").getSomeData(ID=7)>
After I wrote my logging code I realized that the queries within CFCs (the bulk of my data processing) were not being recorded as, of course, they run in their own scope. I'm looking for an easy way to record queries within CFCs as well.
I had never heard of onCFCRequest before a few minutes ago (when Google enlightened me), and I cannot find much information on its use. I was hoping it may work like onRequest, and I could do something like this:
<cffunction name="onCFCRequest">
<cfargument type="String" name="cfcName" required=true/>
<cfargument type="String" name="methodName" required=true/>
<cfargument type="struct" name="args" required=true/>
<cfset caller.getLog = getLog>
</cffunction>
However, that does not work, nor do any of the other variations I tried. In fact, this function doesn't appear to work at all, as this code did not insert anything into my test table:
<cffunction name="onCFCRequest">
<cfargument type="String" name="cfcName" required=true/>
<cfargument type="String" name="methodName" required=true/>
<cfargument type="struct" name="args" required=true/>
<cfquery>
insert into testTable (f1, f2)
values (<cfqueryparam cfsqltype="cf_sql_varchar" value="test1">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="test2">)
</cfquery>
</cffunction>
What is the proper way to use onCFCRequest, and can it be used to write data from a cfc's local scope to the parent/caller request scope? If not, is there another function (like an onCFCRequestEnd) that can be used to do so? I do not want to have to make largescale changes to every cfQuery or cfc file in my application to accomplish this goal.
I am aware there are some commercial applications designed to do this far better that I can with just a few hours of coding. However, this is for a personal site that I can't afford to spend money on, and the experience of doing it myself is enlightening.

As your actual question is how to log all query executions no matter where they happened, I think there are two approaches to do that.
Enabling Logging
Whenever a query is executed, you can write the SQL string and the parameters into a log.
You can set up the logging to a database table of your wish within the Lucee Admin.
Once that's set up, you need to call <cflog> and use the name of the specified log within the file attribute.
Downside to this approach is that you have to manually place a <cflog> whenever you execute a query. This might be mitigated by centralizing all database communication in a function which also does the logging. So you whenever you want to query something, you'd call that function instead of calling <cfquery> manually.
Enabling debugging
Lucee's debugging functionality allows you to output all queries executed within a page run besides other useful information.
You first need to add a debugging template:
Important note: The predefined templates only output the data to the generated HTML of your website. Though you have the possibility to create your own debugging template. In order to do that you need to create your own template CFC within lucee-server/context/context/admin/debug/ and provide it with a unique name. There's also a slide show of how to create your own debugging template of the head behind Lucee, Gert Franz, which explains in more detail how to do that.
When done correctly, you should see the name of your template listed in the template types drop-down list.
Then, all you need to do is to enable debugging of database activity:

Related

How can you get a list of the datasources used to generate a page in coldfusion?

When you add a debug IP to review the debug information from the collection of templates that are parsed to present the page, it includes a list of all queries from that page.
Is it possible to get access to that object for inspection?
I'm looking at trying to automate the documentation which data sources are in use for which page requests. We have a large number of small web apps that access different databases and have different dependencies. I'm hoping to find a way to automate the documentation of these dependencies rather than having to manually review all code for all of the webapps.
Not sure if the object doesn't get created until after the page creation is too far gone to actually do anything with the data, but who knows...
Here is a snippet of code that you can add to the end of your template to get a list of datasources used on the page:
<cfobject action="CREATE" type="JAVA" class="coldfusion.server.ServiceFactory" name="factory">
<cfset cfdebugger = factory.getDebuggingService()>
<cfset qEvents = cfdebugger.getDebugger().getData()>
<cftry>
<cfquery dbtype="query" name="cfdebug_qryDSN">
SELECT DISTINCT DATASOURCE FROM qEvents WHERE type = 'SqlQuery'
</cfquery>
<cfcatch type="Any">
<cfset cfdebug_qryDSN = queryNew('DATASOURCE')>
</cfcatch>
</cftry>
<cfdump var="#cfdebug_qryDSN#" label="cfdebug_qryDSN">
PS: most of the inspiration for this snippet came from {cfusion 10 home}\cfusion\wwwroot\WEB-INF\debug\classic.cfm. You can get some good ideas on how to gain access to debugger objects/data from this file.
For anyone stumbling across this....
If your [cfroot]/cfusion/lib/neo-datasource.xml file is WDDX encoded and you're not sandboxed, you can use the following (tested on CF2021)
<cflock type="readonly" scope="Server" timeout="5">
<CFSET LibPath=Server.System.Properties["coldfusion.libPath"]>
</cflock>
<CFFILE action="Read" file="#LibPath#/neo-datasource.xml" variable="DatasourcesWDDX">
<cfwddx action="wddx2cfml" input="#DatasourcesWDDX#" output="Datasources">
<cfoutput>#StructKeyList(Datasources[1])#</cfoutput>
<cfdump var=#Datasources#>
The first position of the Datasources array holds a structure containing information on each configured datasource with the main key being the name of the datasource.
Here's an idea that'll work for each application which uses an Application.cfc.
Enable Request Debugging Output in CF Administrator.
Configure Debugging IP Addresses so that every page receives debugging information.
Assuming that Select Debugging Output Format is set to classic.cfm, short circuit {cfusion 10 home}\cfusion\wwwroot\WEB-INF\debug\classic.cfm by making <cfreturn> the first executable statement in classic.cfm. This will prevent any pages from seeing the debug output.
In Application.cfc::OnRequestEnd() do what Scott Jibben suggested. You can wrap Scott's idea in an <cfif IsDebugMode()>.

ColdFusion security by checking ARGUMENTS.TargetPage in Application.onRequestStart?

I have a ColdFusion app in which I wish to restrict access to certain pages, based on some criteria. I am currently doing it like this, in Application.cfc:
<cffunction name="OnRequestStart" access="public" returntype="boolean" output="true">
<cfargument name="TargetPage" type="string" required="true" />
<cfif not SESSION.isAdmin and REFindNoCase("/admin",ARGUMENTS.TargetPage) >
<!--- Deny non-admin access to admin pages. --->
<cfinclude template="/notauth.cfm">
<cfreturn false />
</cfif>
<cfreturn true />
</cffunction>
My main concern is: How vulnerable is the general approach of checking TargetPage against a regex, and are there ways to improve the security of this design? Specifically, I'm concerned about avoiding "canonical representation vulnerabilities." See here.
For example, using just a REFind instead of REFindNoCase would let people slide right on through if they went to "/ADMIN/". Are there are other things to watch out for here?
I know there are other designs, like using another Application.cfc in a subfolder, or doing checks right in the page code. But I like the idea of having all my security code in one place. So please only suggest those in your answer if there's no way to do the above securely, or if it's just really a bad idea for some reason. Thanks.
I'm sure there are reams of this stuff on the internets but here is my take on it :)
They way I would solve your specific example is to maintain a database list of scripts that are restricted (a blacklist) unless you are a member of a certain group (i.e. you are an admin).
You can make this as complicated as you wish but for a simple start you could compare the full script name (CGI.SCRIPT_NAME) to a query of queries representing blacklisted pages you store in the APPLICATION scope that you loaded in onApplicationStart() called qRestrictedList.
So in onRequestStart you could do the following:
<cfquery name="qThisPageRestricted" dbtype="query">
SELECT * FROM qRestrictedList
WHERE ScriptName = '#CGI.SCRIPT_NAME#'
</cfquery>
<cfif qThisPageRestricted.recordCount and not SESSION.isAdmin>
<cfinclude template="/notauth.cfm">
<cfreturn false />
</cfif>
Even better, you can expand on this at a later date by wrapping all this in a 'authentication' CFC and creating user groups and levels, i.e. move your logic out of onRequestStart() and encapsulate it.
But as a start, storing the data in the database might be a more maintainable way for you to get this done and provide a better foundation for future changes to how your authentication works.
I hope this helps.
It may worth to make regex a bit stricter:
REFindNoCase("\/admin\/([A-Za-z_]+)\.cfm", ARGUMENTS.thePage)
A better approach would be to put an application.cfc in the /admin directory that controls access (maybe based on a SESSION variable set through logging in as an admin), and have that "child" application.cfc reference the parent one if necessary.
See this question for an example on how to do this: Extending application.cfc in a subdirectory

Split A CFC file into multiple files

I have been asked to update an old project. When i went into the cfc file it had over 3000 lines of code and over 100 cffunctions. I was wondering if i could split the cfc into multiple files whose cffunctions are logically grouped without having to change the code in any other pages.
Run into a similar problem. I created the new cfcs and modified the original functions to call functions within the new cfcs.
For e.g
<cffunction name="GetStuff" access="remote" returntype="Struct">
<cfreturn createObject("component","myNewCFC").GetStuff(argumentCollection=arguments)/>
</cffunction>
Refactor, Refactor, refactor...
simplest way might be using cfinclude to inject functions (mixin's)
Question implies there's enough client code using this object that changing the calls elsewhere if the object's broken apart apart is burdensome. In this case treat the existing object as a Facade - that is an object that provides unified interface to an underlying class hierarchy.
The way to approach producing the hierarchy is identifying those functions that should go together. Whenever I come across this problem the functions usually do not share any state, rather they are like static java methods, but if there are functions that share state they are a good candidate for this grouping. Otherwise it's usually functions that share the same input parameters or tend to have the same verbiage in their name (i.e. saveMyData, loadMyData, etc...).
Given that example, copy these functions into a new CFC (e.g. MyData) - at this point you may change the function names to eliminate repetition or improve their clarity (e.g. MyData.load()). Back in the original object (i.e. BigCFC) remove these functions' implementation and instead delegate the call to the newly created CFC (you may consider making the new CFC part of the old's composition). So it would look something like this:
<cffunction name="loadMyData">
<cfargument name="id" type="numeric"/>
<cfreturn variables.myData.load(arguments.id)/>
</cffunction>
Where variables.myData would be setup as part of the CFC's initialization.
Taking this approach means your existing client code is unaffected by the change, but still breaks apart everything into logic groupings, and positions new code to use the more granular CFCs.
It's an old question and I just came across it randomly, but I thought I'd chime in here as it's something I've had to deal with on many occasions.
If the goal is simply to organise things better from a code management perspective (rather than say, to specifcially reduce the amount of methods in each CFC) then I'd advocate breaking the CFC down into multiple CFM pages and including them in the CFC. From a code management perspective, you can group several functions into a well named CFM file and it all becomes a lot easier to deal with. The calling code remains the same, as all the functions are still being instantiated in the CFC as before.
I have a bit of code I use in my init methods that automatically includes all the CFM files it finds in the same folder, and I house a single base.cfc in each folder along with the grouped functions.
e.g.
<cfscript>
// Set CFC name
Variables.sCFCName = 'appUtils';
// Set folder
Variables.sCFCFolder = GetDirectoryFromPath(GetCurrentTemplatePath());
// Get CFC files
Variables.qCFCFiles = directoryList(Variables.sCFCFolder, true, 'query');
</cfscript>
<!--- Init function --->
<cffunction name="init" access="public" returnType="any" output="false" hint="Constructor">
<cfargument name="DSN" type="string" default="" hint="Datasource" />
<!--- Set DSN --->
<cfset Variables.DSN = Arguments.DSN />
<cfreturn this />
</cffunction>
<!--- Include CFC files --->
<cfoutput query="Variables.qCFCFiles">
<cfif Variables.qCFCFiles.type EQ 'file' AND GetToken(Variables.qCFCFiles.name, 2, '.') EQ 'cfm'>
<cfinclude template="#Variables.qCFCFiles.Name#" />
</cfif>
</cfoutput>

What is wrong with my recursive method call?

I have a search function that executes a stored procedure and returns results. If there are no results, I want to try running the function one more time with a more generalized search. So, I put a cfif into my code -
<cfif results.recordCount EQ 0 And Not arguments.searchForPotentialMatches>
<cfset arguments.searchForPotentialMatches = True />
<cfinvoke method="thisMethod" argumentCollection="#arguments#" />
</cfif>
Basically, if there were no results AND I haven't already tried a generalized search, it should invoke this method again. Then, in the beginning of the method, before calling the stored procedure, I check if searchForPotentialMatches is true, and if it is, I generalize the search query.
There seems to be a problem, though... When I try to run this, it hangs - until there's a timeout with the stored procedure. Through debugging and outputting variables, I've been able to see that it gets to the stored procedure, and then gets stuck trying to execute it. However, using the original function before these rerun changes, if I do the regular search and then the generalized search in 2 separate calls, it executes correctly. So I'm not sure why it fails when I try to build this in programmatically... What am I doing wrong?
Could really be any number of things. Is all of this code inside of a cfc? Is that cfc in a persistent scope and have you properly var'd all your variables?
Can you execute the stored proc under both normal and generalized conditions standalone without issue?
Try pasting in more of your code (including the first call to the stored proc) so we can try to trace your data flow a bit more.
Recursion is:
seductively simple in theory and a pain in the ass in practice - to debug.
often necessary to walk trees or traverse graphs, but when one can do without, do without.
So as you wrote, I'd lose the recursion, and do it sequentially. Absent any more code as #scrittler requested, I'd rewrite as such:
<cfcomponent output="false">
<cffunction name="search" output="false" access="public" returntype="any" hint="I am called by the client">
<!--- <cfargument/> tags --->
<!--- what ever you need to do with the arg before actually searching --->
<cfset var results = doSearch(argumentCollection=arguments)>
<cfif NOT results.recordcount>
<!--- whatever you need to change about the args to perform a generalized search --->
<cfset var results = doSearch(argumentCollection=arguments)>
</cfif>
<cfreturn results>
</cffunction>
<cffunction name="doSearch" output="false" access="private" returntype="query" hint="I run the query">
<!--- <cfargument/> tags --->
<!--- results query (i.e. call to sproc)--->
<cfreturn results>
</cffunction>
</cfcomponent>
What is your access attribute on the function tag, have you given it a value that leaves the function unable to call itself?
This feels unfair... But the issue was with something completely different. The recursive call works correctly, but there was another field that was getting changed due to a check in the function before calling the stored procedure and causing the stored proc to hang. Sorry about that, and thanks for all your help!

Override onMissingTemplate handling in Application.cfc

I want to handle a scenario where user hits a url of /somePage.cfm when that template doesn't exist and use a template from another directory. I know I can do this via rewrites in apache etc. but I don't really want to store logic in there so I gave trying to override onTemplateMissing behaviour in my Application.cfc.
It seems to be working fine in my testing but I'm worried by doing this hacky solution I'm short cutting some parts that I haven't seen yet (e.g. methods that I'm not currently using such as onSessionStart etc.) and may run into issues in the future.
Here is what I'm currently doing:
<cffunction name="onMissingTemplate">
<cfargument name="targetPage" />
<!--- Handle any templates that we're really loading from elsewhere --->
<cfif isFooTemplate(arguments.targetPage)>
<cfset onRequestStart(arguments.targetPage) />
<cfset onRequest(arguments.targetPage) />
<cfset onRequestEnd(arguments.targetPage) />
<cfreturn true />
</cfif>
<cfreturn false />
</cffunction>
Note that also in my onRequest method I'm doing further handling for templates that isFooTemplate() would return true to.
I don't think this is a hacky solution. This is what the method is for, and on returning false, ColdFusion will invoke the standard error handler you setup in the administrator if you want.
The only case were onSessionStart() hasn't run is if the user hits the onMissingTemplate() on the first ever page request. If you for some reason need the user to have a session, you can check for the existence of the session scope, since the session scope is supposed to be available in the onMissingTemplate() method and handle appropriately.
It's actually onMissingTemplate not onTemplateMissing; and this is a recommended practice, not 'hacky' at all. You're fine doing it this way.