how to determine the rollback cause in a cftransaction - coldfusion

I have set of inserts that are wrapped in a <cftransaction> block, and I am getting a error and the insert is being rolled back.
Here is the code in question stubbed for space:
<cffunction name="InsertTCUV" access="public">
<cfargument name="vehicle required="true" type="xml" />
//Parsing the xml document here
<cftransaction>
<cfquery name="TCUVinsert datasource="mydb">
INSERT INTO tcuv
VALUES(...)
<cfquery>
<cfquery name="qLatestTCUVID" datasource="mydb">
SELECT TOP 1 tcuv_id FROM dbo.tcuv ORDER BY tcuv_id DESC
</cfquery>
<cfset curTCUVID = qLatestTCUVID.tcuv_ID>
<cfset optionsResult = insertOptions(curTCUVID,vehicle>
<cfset imagesResult = insertImages(curTCUVID,vehicle)>
<cfset standardFeaturesResult = insertStandardFeatures(curTCUVID,vehicle
</cftransaction>
</cffunction>
<cffunction name="insertOptions" access="private">
<cfargument name="TCUVID required="true type="numeric" />
<cfargument name="vehicleInfo" required="true" type="xml" />
<cfset var result = "good">
<cftry>
<cfset optionNode = xmlSearch(arguments.vehicleInfo[1], "p:RemarketingOption">
<cfloop index="i" from="1" to="#arrayLen(optionNode)#">
<cfset optionNodeNotes = XmlSearch(optionNode[#i#], "p:OptionNotes")>
<cfset optionNotes = "">
<cfloop index="j" from="1" to="#ArrayLen(optionNotesNodes)#">
<cfoutput>
<cfset optionNotes = optionNotes & " " & #optionNotesNodes[j].xmlText#>
</cfoutput>
</cfloop>
<cfquery name="insertOptions" datasource="mydb">
INSERT INTO dbo.tcuv_options
VALUES (
<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.TCUVID#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionID")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionTypeCode")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionShortDescription")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#optionNotes#">
)
</cfquery>
</cfloop>
<cfcatch type="database">
//dumping cfcatch.* information
<cfset result = "error"
</cfcatch>
</cftry>
<cfreturn result>
</cffunction>
This whole thing is in a loop on the calling page, and everything works fine the first time through the loop. One the second pass, the TCUVInsert works, but I get to the insertOptions function, a coldfusion error is thrown saying that, variable insertOptions is undefined, and when I get the database, the second row isn't there, which tells me there was an error and the insert rolled back. So, there is an error with the insert of the options, and coldfusion isn't giving me the right error to diagnose it. So either I have to look in the database logs, which apparently are not setup, or try to extract the error from the cftransaction block, which I'm not sure how to do.
coldfusion 9, sql server 2008 r2
Any thoughts?

<cffunction name="insertOptions" access="private">
You are overwriting the function by using the same name for a query variable:
<cfquery name="insertOptions" datasource="imports">
Interestingly it is all because the query name was not var scoped. Functions are stored in the variables scope of the component. So by failing to localize the query name, you end up overwriting the function stored in variables.insertOptions when you run the query. Because insert statements do not return a resultset, that variable ends up being undefined. Hence the error. In this case the solution is to either scope the query name, or better yet remove it entirely (since it is not populated anyway).
Just one more reason to always var/local scope function variables - yes, query names too!

Related

How to counter this SQL Injection risk (Coldfusion)?

Recently, a security risk company we hired identified a SQL injection risk for a specific parameter of a function in our Coldfusion application. The argument is correctly captured as a cfargument:
<cfargument name="status" required="false" type="string">
and also correctly paramed as a cfprocparam:
<cfif isDefined('ARGUMENTS.status') AND ARGUMENTS.status NEQ "">
<cfprocparam type="in" value="#ARGUMENTS.status#" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
<cfelse>
<cfprocparam type="in" null="yes" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
</cfif>
And then it's used in the stored proc this way, building a where clause for a SQL query:
if (len(#search_status) > 0)
begin
set #strWhere = #strWhere + ' AND e1.cps_txt LIKE '+ '''%'+#search_status+'%'''
end
Of course, paraming to make sure the value is a varchar can't prevent a string of malicious code being entered and passed. This seems like it could easily be a larger problem, too, since it's far from the only place in our application where string arguments are passed to cfc functions and then used in SQL queries.
I'm wondering if the best approach to remedy the security risk in this one case is to compare the argument's value to all of the possible expected values (there's about twenty of them, stored in a table), and if it doesn't match one of them, then to throw an error, or just pass to the stored proc as null.
Had to tackle this at a previous employer. The trick is to pass true or false to the null argument and to avoid writing all this conditional code that calls different versions of the procparam with or without the attribute. Just always have the attribute and put the conditional logic in the value.
<cfprocparam type="in"
dbvarname="#search_status"
cfsqltype="cf_sql_varchar"
value="#ARGUMENTS.status#"
null="#!(structKeyExists(arguments, "status") AND (len(arguments.status) NEQ 0))#">
Depending on the complexity of the condition, you can simply pass it as a variable.
<cfset statusNull = true>
<cfif structKeyExists(arguments, "status") AND (len(arguments.status) NEQ 0)>
<cfset statusNull = false>
</cfif>
<cfprocparam type="in"
dbvarname="#search_status"
cfsqltype="cf_sql_varchar"
value="#ARGUMENTS.status#"
null="#statusNull#">
And I'd like to reiterate the comment from SOS that mentions dynamic SQL. We also eliminated dynamic SQL from thousands of stored procedures and replaced it with the conditional check from his linked message. This is so much easier to read and debug than dealing with acres of string concatenation code.
WHERE ( #OrderId IS NULL OR tblOrder.OrderId = #OrderId )
AND ( #OrderCustomer IS NULL OR tblOrder.OrderCustomer = #OrderCustomer )
I would also avoid adding a query just to look up valid values for status before passing the argument to the this function/proc. That just adds overhead to you system for every call to this process.
I think you pretty much answered your own question. Here's an approach I've used in the past to handle validations using accepted values stored in a small lookup table. Do this before calling your stored procedure. I'll use generic names since I don't know your table and column names.
<!--- Validation code prior to running stored proc --->
<cfif isDefined('ARGUMENTS.status') AND ARGUMENTS.status NEQ "">
<!--- Query acceptable values --->
<cfquery name="qValidateSearchStatus" datasource="myDsn">
select
search_status
from
lookup_table
</cfquery>
<cfif listFind(valueList(qValidateSearchStatus.search_status), ARGUMENTS.status)>
<cfset statusValid = true>
<cfset statusNull = false>
<cfelse>
<cfset statusValid = false>
<cfset statusNull = false>
</cfif>
<cfelse>
<cfset statusValid = true>
<cfset statusNull = true>
</cfif>
<cfif statusValid>
<!--- Call stored procedure --->
<cfstoredproc procedure="myStoredProc" datasource="myDsn">
<cfif statusNull>
<cfprocparam type="in" null="yes" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
<cfelse>
<cfprocparam type="in" value="#ARGUMENTS.status#" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
</cfif>
</cfstoredproc>
<cfelse>
<!--- Process error handling --->
</cfif>

cftransaction will only run first cfquery, skips over second

I'm trying to write to two different tables, both in the same database. In one CFTRANSACTION with two CFQUERY, the first CFQUERY will INSERT properly but the second (also INSERT) is just skipped over. No errors are thrown, I can see the data in the first table, and commenting out the first INSERT will allow the second to go through as desired.
A simplified version of my code is:
<cffunction name="insertReport">
<cfset var strReturn="">
<cftransaction>
<cftry>
<cfquery name="updateTable1" datasource="DB1">
...
</cfquery>
<cfquery name="UpdateTable2" datasource="DB1">
...
</cfquery>
<cfcatch type="any">
<cfset errMsg = "#cfcatch.Message#">
</cfcatch>
</cftry>
<cfif trim(errMsg) eq ''>
<cftransaction action="commit">
<cfelse>
<cftransaction action="rollback">
<cfset strReturn = "Error: #errMsg#.">
</cfif>
</cftransaction>
<cfreturn strReturn>
</cffunction>
This is probably something really simple, but I'm stuck. Any help is appreciated.
Update
I just tried the below code on CF11 and it worked fine. The only error I received was that errMsg was undefined, which it isn't in your code until the <cfcatch> occurs. I defined errMsg and re-ran -- it was successful.
<cffunction name="insertReport">
<cfset var strReturn="">
<cfset errMsg = "">
<cftransaction>
<cftry>
<cfquery name="updateTable1" datasource="DS1">
INSERT INTO ...
</cfquery>
<cfquery name="UpdateTable2" datasource="DS1">
INSERT INTO ...
</cfquery>
<cfcatch type="any">
<cfset errMsg = "#cfcatch.Message#">
</cfcatch>
</cftry>
<cfif trim(errMsg) eq ''>
<cftransaction action="commit">
<cfelse>
<cftransaction action="rollback">
<cfset strReturn = "Error: #errMsg#.">
</cfif>
</cftransaction>
<cfreturn strReturn>
</cffunction>
<cfoutput>#insertReport()#</cfoutput>
Regarding multiple datasources
According to this Adobe forum and this SO post, you must commit changes to each datasource before continuing to the next.
According to the SO post, this will work:
<cftransaction action="begin">
<cfquery datasource="foo">
select * from foo_test
</cfquery>
<cftransaction action="commit"><!-- Commit before switching DSNs --->
<cfquery datasource="bar">
select * from bar_test
</cfquery>
</cftransaction>
Note that if you can access your data via one datasource using three part names, (e.g. fooDB.dbo.table ) you can write to different databases in one <cftransaction>
Thanks to everyone who helped. It turns out the problem was the fact that we're using CFQUERYPARAMs in the CFQUERY instead of hard-coding the values, and they were throwing Null Pointer exceptions.
It seems like those CFQUERYPARAM elements would write to the database properly when we only had one CFQUERY, but still threw that Null Pointer exception that would skip over the second CFQUERY. Because the Null Pointer only threw a "#cfcatch.type#" value, not "#cfcatch.Message#" or "#cfcatch.detail#" that our error-checking was looking for, we didn't flag the problem.
We're looking unto using jTDS now to see if that will solve the issue.

Coldfusion - Complex object error when trying to cfloop over query. Version discrepancy?

Preamble: So we have some working applications that we need to move over to a new server because we are retiring an older one, and as a result I've had to install a new instance of CF.
This application works fine on the older server, which is running ColdFusion version "9,0,0,251028" Standard edition (via ColdFusion Administrator).
On the newer server, I'm using CF 2016 version 2016.0.0.298074 Developer edition (It's the first thing that popped up on a google search so I went with it).
Now the issue: there is a piece of code giving an error that says:
Complex object types cannot be converted to simple values.
The expression has requested a variable or an intermediate expression result as a simple value. However, the result cannot be converted to a simple value. Simple values are strings, numbers, boolean values, and date/time values. Queries, arrays, and COM objects are examples of complex values.
The most likely cause of the error is that you tried to use a complex value as a simple one. For example, you tried to use a query variable in a cfif tag.
The error occurred in G:/Gumbo/components/modules/resource/ResourceAdmin.cfc: line 282
Called from G:/Gumbo/admin/modules/resource/action.cfm: line 34
Called from G:/Gumbo/admin/action.cfm: line 19
281 cfloop query="getseq">
282 <cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
283 </cfloop>
The offending line is 282. The code in question:
<cfloop query="getseq">
<cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
</cfloop>
From my research, I've noted that apparently cfloop doesn't work with query parameters on some versions of ColdFusion, but what I don't understand is why a NEWER version is therefore causing me this error.
So my question is:
Is there a way to reacquire this older version of CF somehow? Keep in mind I have the CF9 source folder on my older computer, but I wasn't sure if there's a way I can take the source files and move them over or manually install it or the ins and outs of that. Could it be as easy as copying the older source files into the new CF source on the newer server?
What would be an easy alternative to change the code mentioned? I am completely unfamiliar with CF as this is an older project that I inherited upon taking this job. I'd prefer to get the exact version on the newer system, but changing the code is the only viable alternative.
Any insight would be appreciated.
EDIT:
Here is the entire offending function:
<cffunction name="updateResource" access="public" output="false" displayname="Update a Resource">
<cfset VARIABLES.dateUpdated=DateAdd("d", 0, arguments.dateUpdated)>
<cfquery datasource="#this.datasource#">
update md_rlm_resource
set published=<cfqueryparam cfsqltype="cf_sql_tinyint" value="#arguments.published#">,
resourceName=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceName#">,
resourceNumber=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceNumber#">,
resourceAuthor=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceAuthor#">,
resourceFile=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceFile#">,
dateUpdated=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#VARIABLES.dateUpdated#">,
shortDescription=<cfqueryparam cfsqltype="cf_sql_longvarchar" value="#arguments.shortDescription#">
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfquery name="getseq" datasource="#this.datasource#">
select displaySeq, resourceCategoryID
from md_rlm_resourcecategoryrel
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfquery datasource="#this.datasource#">
delete from md_rlm_resourcecategoryrel
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfif IsDefined("arguments.resourceCategoryIDs")>
<cfset resourceCategoryID = ArrayNew(1)>
<cfset resourceCategoryID = ListToArray(arguments.resourceCategoryIDs)>
<cfif #ListLen(arguments.resourceCategoryIDs)# gt 1>
<cfset tmp1 = #ArrayLen(resourceCategoryID)#>
<cfelse>
<cfset tmp1 = "1">
</cfif>
<cfloop INDEX="idx" FROM="1" TO="#tmp1#">
<cfset newseq = 1>
<cfif #tmp1# gt 1>
<cfset temp=resourceCategoryID[idx]>
<cfelse>
<cfset temp=resourceCategoryID>
</cfif>
<cfloop query="getseq">
<cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
</cfloop>
<cfquery datasource="#this.datasource#">
insert into md_rlm_resourcecategoryrel
(resourceCategoryID, resourceID, displaySeq)
values
(
<cfif #tmp1# gt 1>
<cfqueryparam CFSQLTYPE="cf_sql_integer" VALUE="#resourceCategoryID[idx]#">,
<cfelse>
<cfqueryparam CFSQLTYPE="cf_sql_integer" VALUE="#resourceCategoryID#">,
</cfif>
<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.resourceID#">,
<cfqueryparam cfsqltype="cf_sql_float" value="#newseq#">)
</cfquery>
</cfloop>
</cfif>
<cfquery datasource="#this.datasource#">
DELETE FROM md_rlm_resourceregionrel
WHERE resourceID=<cfqueryparam value="#arguments.resourceid#" cfsqltype="cf_sql_integer">
</cfquery>
<cfloop index="regionele" list="#arguments.regionID#" delimiters=",">
<cfquery datasource="#this.datasource#">
INSERT INTO md_rlm_resourceregionrel (resourceID, regionID)
VALUES (<cfqueryparam value="#arguments.resourceid#" cfsqltype="cf_sql_integer">, #regionele#)
</cfquery>
</cfloop>
</cffunction>
From this:
<cfset resourceCategoryID = ListToArray(arguments.resourceCategoryIDs)>
...
<cfif #tmp1# gt 1>
<cfset temp=resourceCategoryID[idx]>
<cfelse>
<cfset temp=resourceCategoryID><!--- temp is now an array --->
</cfif>
temp (terrible variable name, btw) could be an array.
And later you do this:
<cfif getseq.resourceCategoryID IS temp>
You cannot compare arrays with the IS operator: IS compares simple values. That's why you're seeing the error.
As an aside, you're not var-ing any of your variables in that code, which is fairly poor form, and has potential for "unexpected behaviour" in your code.

Coldfusion OutOfMemoryError (CF9/Wheels)

I have a function which loops over a query and updates a database row for each item. After about 7000 iterations it's throwing an out of memory error - Java heap space.
Is there anything obviously wrong with this code ?
<cfloop query=loc.fixItems>
<cfset loc.count = loc.count + 1>
<cfset var categoryName = loc.fixItems.categoryName>
<cfinvoke component="Item" method="updateCode"
itemId="#loc.fixItems.itemId#" code="#loc.fixItems.newCode#"/>
<!--- Increment counter for category --->
<cfif structKeyExists(categoryMap, categoryName)>
<cfset var inc = structFind(categoryMap, categoryName) + 1>
<cfset structUpdate(categoryMap, categoryName, inc)>
<cfelse>
<cfset structInsert(categoryMap, categoryName, 1)>
</cfif>
</cfloop>
and in the update component:
<cffunction name="updateCode">
<cfargument name="itemId" type="numeric" required="yes">
<cfargument name="code" type="string" required="yes">
<cfset var loc = {}>
<cfquery name="loc.update">
update items
set code = <cfqueryparam value="#code#">
where id = <cfqueryparam value="#itemId#">
</cfquery>
</cffunction>
Don't use cfinvoke to create your Item component every iteration of your fixItems query. Create it once before that using createObject and simply call the updateCode method each time directly on the object.
The following can be done:
Change your <cfqueryparam> to use the appropriate cf_sql type. Are code and id really strings?
Don't give your <cfquery> a name. You are not keeping the result anyway. var loc doesn't help either
Bump up you memory to the JVM Addtional approach Use Java 7 and G1GC
Every 100 to 1000 iterations do a forced Garbage Collect
Update your data in bulk. XML based table variables can do this.
Make your function silent
Consider ORM on this

Strange database error in Coldfusion

I have two queries:
<cfquery name="getChairReview" datasource="#application.dsn#">
SELECT*
FROM eval_reviews
WHERE faculty = <cfqueryparam cfsqltype="cf_sql_numeric" value="#HTMLEditFormat(trim(arguments.id))#">
AND evalYear = <cfqueryparam cfsqltype="cf_sql_numeric" value="#HTMLEditFormat(trim(arguments.evalYear))#">
AND levels = <cfqueryparam cfsqltype="cf_sql_varchar" value="chair">
</cfquery>
<cfreturn getChairReview>
</cffunction>
<cffunction name="getDeanReview" access="public" returntype="query">
<cfargument name="id" type="numeric" required="yes">
<cfargument name="evalYear" type="numeric" required="yes">
<cfset var getDeanReview = QueryNew("")>
<cfquery name="getDeanReview" datasource="#application.dsn#">
SELECT*
FROM eval_reviews
WHERE faculty = <cfqueryparam cfsqltype="cf_sql_numeric" value="#HTMLEditFormat(trim(arguments.id))#">
AND evalYear = <cfqueryparam cfsqltype="cf_sql_numeric" value="#HTMLEditFormat(trim(arguments.evalYear))#">
AND levels = 'dean'
</cfquery>
<cfreturn getDeanReview>
</cffunction>
Both of them work fine when evalYear is 2012. If I send in 2011 I get an error on getDeanReview that states: [Macromedia][SQLServer JDBC Driver][SQLServer]Error converting data type varchar to numeric
The line it gives this error on is the AND levels = 'dean' (note: I had taken out the cfqueryparam tag to make sure I didn't goof that up.)
I find this strange because these two queries are almost identical except one has chair as a param and the other dean. (Yes, I could have had one function to do the job...with extra param for level)
I changed 'dean' to 'chair' with evalYear still 2011...guess what? It worked. This is quite odd...
levels is a varchar field.
Any ideas?
That's weird.
Okay, for grits & shins, try making the second query:
and levels like '%dean%'
Also, try it the way you have it, but remove the line about year and let it select everything regardless of year... see what your output looks like.
Lastly.. I don't think you need to worry about HTMLEditFormat in your value params in cfqueryparam.
Rob