Coldfusion 11 web service response object - coldfusion

I'm struggling to get CF11 to give me a web service response object with the same set of methods as CF8.
For example, one of the web services used to give me this in CF8:
And this is what I'm getting now in CF11:
As you can see, they are miles different!
I've tried to set wsversion="2", but didn't help at all:
<cfset wsArgs = {wsversion="2"}>
<cfset ws = createObject("webservice", "http://somewsurl.asmx?wsdl", wsArgs)>
<cfset nvArgs = { foo = "bar" }>
<cfset result = ws.createNameValues(argumentCollection = nvArgs)>
Same thing if I try wsversion="2" on cfinvoke:
<cfinvoke webservice="#ws#" method="createNameValues" argumentCollection="#nvArgs#" returnVariable="result" wsversion="2"/>
Any idea?
Note I'm getting basically the same issue on CF10.

ColdFusion 10 and 11 use Axis 2.0 by default, while previous versions of ColdFusion used Axis 1.0. If you depend on Axis 1.0 behavior in ColdFusion 10/11, specify wsversion=1 to force ColdFusion to use Axis 1.0.

Related

ColdFusion 2021 - How to handle SAML/SSO with multiple applications on same server

We have a server with about a dozen small applications each in their own subfolder of the server (//URL/app1, //URL/app2, etc).
I've got the basic SSO authentication round trip working. I set up my account with my IDP and have the response set to go to a common landing page (ACS URL). Since the landing page is currently shared with all the apps, it is in a separate folder distinct from the apps (//URL/sso/acsLandingPage.cfm)
I'm now working on my first app. I can detect the user is not logged in so I do a initSAMLAuthRequest(idp, sp, relayState: "CALLING_PAGE_URL") and that goes out, authenticates, then returns to the landing page.
But how do I redirect back to my target application and tell it the user is authenticated?
If I just do a <cflocation url="CALLING_PAGE_URL" /> the original app doesn't know about the SAML request.
Is there a function that I can call in the original app that will tell if the current browser/user has an open session?
Do I need to set up separate SP for each application so rather than one common landing page each app would have its own landing page so it can set session variables to pass back to the main application? (the IDP treats our apps as "one server", I can get separate keys if that is the best way to deal with this).
My current working idea for the ACS landing page is to parse the relayState URL to find out which application started the init request and then do something like this:
ACSLandingPage.cfm
<cfset response = processSAMLResponse(idp, sp) />
<cfif find(response.relaystate, 'app1')>
<cfapplication name="app1" sessionmanagement="true" />
<cfelseif find(response.relaystate, 'app2')>
<cfapplication name="app2" sessionmanagement="true" />
</cfif>
<cfset session.authenticated_username = response.nameid />
<cflocation url="#response.relaystate#" />
Not terribly ideal, but I think it might work.
I was hoping I was just overlooking something simple and really appreciate any help I can get.
Edit:
My above idea of using <cfapplication in the ACSLandingPage is not working because the <cfapplication keeps trying to assign it to a new session so that when I redirect back to the original app, it thinks it is in a different session so does not have access to the original session.authenticated-username.
Ok, here's how I ended up solving this problem. Probably not the "correct" solution, but it works for me.
The full code solution would be way too long and complicated and rely on too many local calls that would not make sense, so I'm trying to get this down to just some code snippets that will make sense to show how my solution works.
In each application, the Application.cfc looks a bit like this. Each app has a name set to the path of the Application.cfc. We do this because we often will run "training instances" of the codebase on the same server that point to an alternate DB schema so users can play around without corrupting production data.
component {
this.name = hash(getCurrentTemplatePath());
...
In the application's onRequestStart function it has something a bit like this:
cfparam(session.is_authenticated, false);
cfparam(session.auth_username, '');
cfparam(application._auth_struct, {}); // will be important later
// part 1
// there will be code in this block later in the description
// part 2
if (NOT session.is_authenticated OR session.auth_username EQ '') {
var returnURL = '#getPageContext().getRequest().getScheme()#://#cgi.server_name#/#cgi.http_url#'; // points back to this calling page
// start the call
InitSAMLAuthRequest({
'idp' : 'IDP_NAME',
'sp' : 'SP_NAME',
'relayState': returnURL
});
}
// log them in
if (session.is_authenticated AND session.auth_username NEQ '' AND NOT isUserLoggedIn()) {
... do cflogin stuff here ...
}
// throw problems if we are not logged in by this point
if (NOT isUserLoggedIn()) {
... if we don't have a logged in user by this point do error handling and redirect them somewhere safe ...
}
This initiates the SAML connection to our ID Provider. The provider does its stuff and returns the user to the file 'https://myserver/sso/ProcessSAMLResponse.cfm'.
processSAMLResponse uses the returnURL set in relayState to determine which application initiated the request so it can get a path to the app's Application.cfc.
<cfset response = ProcessSAMLResponse(idpname:"IDP_NAME", spname:"SP_NAME") />
<cfset returnURL = response.RELAYSTATE />
<cfif findNoCase("/app1", returnURL)>
<cfset appPath = "PHYSICAL_PATH_TO_APP1s_APPLICATION.CFC" />
<cfelseif findNoCase("/app2", returnURL)>
<cfset appPath = "PHYSICAL_PATH_TO_APP2s_APPLICATION.CFC" />
<cfelseif findNoCase("/app3", returnURL)>
<cfset appPath = "PHYSICAL_PATH_TO_APP3s_APPLICATION.CFC" />
...
</cfif>
<!--- initiate application --->
<cfapplication name="#hash(appPath)#" sessionmanagement="true"></cfapplication>
<!--- create a token (little more than a random string and a bit prettier than a UUID) --->
<cfset auth_token = hash(response.NAMEID & dateTimeFormat(now(), 'YYYYmmddHHnnssL'))/>
<cfset application._auth_struct[auth_token] = {
"nameid": lcase(response.NAMEID),
"expires": dateAdd('n', 5, now())
} />
<!--- append token (can also be done with a ?: if you are inclined) --->
<cfif NOT find("?", returnURL)>
<cfset returnURL &= "?auth_token=" & encodeForURL(auth_token) />
<cfelse>
<cfset returnURL &= "&auth_token=" & encodeForURL(auth_token) />
</cfif>
<!--- return to the calling page --->
<cflocation url="#returnURL#" addToken="No"/>
This throws it back to the application. So we go back into the application's onRequestStart to fill in that part 1 block from above:
cfparam(session.is_authenticated, false);
cfparam(session.auth_username, '');
// part 1
// look for an auth token
if (NOT session.is_authenticated AND session.auth_username EQ '' AND structKeyExists(URL, 'auth_token')) {
var auth_token = URL.auth_token;
// see if it exists in our auth struct (and has all fields)
if ( structKeyExists(application, "_auth_struct")
AND structKeyExists(application._auth_struct, auth_token)
AND isStruct(application._auth_struct[auth_token])
AND structKeyExists(application._auth_struct[auth_token], 'nameid')
AND structKeyExists(application._auth_struct[auth_token], 'expires')) {
// only load if not expired
if (application._auth_struct[auth_token].expires GT now()) {
session.is_authenticated = true;
session.auth_username = application._auth_struct[auth_token].nameid;
}
// remove token from struct to prevent replays
structDelete(application._auth_struct, auth_token);
} // token in auth struct?
// remove expired tokens
application._auth_struct = structFilter(application._auth_struct, function(key, value) {
return value.expires GT now();
});
} // auth_token?
// part 2
// .... from earlier
So that's how I solved the problem of multiple apps trying to use a single IDP/SP combination.
Important caveats:
This is all done on an intranet server, so my security is much more lax than it would be on a public facing server. (in particular, using an application variable to store the auth-tokens could be vulnerable to a massive DDOS type attack that would flood new sessions and fill available memory).
A subset of 1 - these apps get a few hundred users a day across all apps, if you have a site that gets thousands of hits a day, storing the tokens in application like I do may not be memory efficient enough for you.
My IDP is very constrained. It would be much nicer if I could just create distinct SP settings for each app and have the return calls go directly back to the calling app.
I skipped a few checks and error handling to keep the sample simple. You should do lots more tests on the values, especially to make sure the nameID is a valid user before the actual cflogin call.
Before calling initSAMLAuthRequest, you may want to add a session counter to prevent an infinite loop of authentication calls if something goes wrong (learned that the hard way).

queryEach or .each() not Working at CF 11, Why?

I was seaching in google better form to interate over "query" in coldfusion, since im new in the company im working, and im trying to get more from CF
Here my attempts:
My models:
<cffunction hint="Foo" name="Foo" access="public" returntype="query">
<!--- Argumentos --->
<cfargument hint="Something" name="ArgComBus" type="string" required="no" default="">
<cfargument hint="Other thing" name="ArgPar" type="string" required="no" default="">
<cfscript>
queryService = new Query();
queryService.setSql("
SELECT
column1,
column2,
FROM
tab_bar
WHERE
1=1
#arguments.ArgComBus#
");
queryService.setDataSource(session.Dsn);
if(Len(Trim(arguments.ArgPar))){
Evaluate(arguments.ArgPar);
}
queryResult = queryService.execute();
qBus = queryResult.getResult();
</cfscript>
<cfreturn qBus>
</cffunction>
My script
<cfscript>
arrFoo = arrayNew(1);
qFoo = this.Foo(
ArgComBus = " AND column1 = #variables.bar# ");
// First Attempt - The each method was not found.
qFoo.each(function (foo) {
arrFoo.append(foo);
});
// Second Attempt - Variable QUERYEACH is undefined.
queryEach(qFoo, function (foo) {
arrFoo.append(foo);
});
writeDump(arrFoo);
</cfscript>
My Server Dump
InstallKit Native Windows
appserver Tomcat
productlevel Developer
productname ColdFusion Server
productversion 11,0,05,293506
rootdir C:\CFusion11\cfusion
I even used getMetaData() on my query variable qFoo and that return that is array... so when i tried use something like that (trying to convert array in query?)
cfQuery = createObject("java", "coldfusion.sql.QueryTable").init(qFoo);
.each() and queryEach() same answer... i even tried use arrayEach() but return the object is coldfusion.sql.QueryTable and not array
You are running ColdFusion 11.
The queryEach() function was not added until ColdFusion 2016:
reference 1 (from cfdocs)
reference 2 (from Adobe docs)
Originally I had posted that the each() member function was not available in Adobe ColdFusion 11. Aquitaine pointed out in the comments that it actually is. I incorrectly referenced the Each() function for Lucee that works with collections. The Each() function related to this question is actually the script version of the ArrayEach() tag function. Which is available in ColdFusion 11 (it was actually added in ColdFusion 10). Sorry for the confusion.
The documentation may be wrong. I could not get the function to work as Each() except under ColdFusion 2018. For ColdFusion 11 I could only get it to work as ArrayEach().
reference 3 (from cfdocs)
reference 4 (from Adobe docs)
Here are a couple of examples on how to loop over a query in ColdFusion 11 (borrowed from cfdocs):
// Define our query
platform = ["Adobe ColdFusion", "Railo", "Lucee"];
myQuery = queryNew(" ");
queryAddColumn(myQuery, "platform", "CF_SQL_VARCHAR", platform);
// By row index
for (i = 1; i <= myQuery.recordCount; i++) {
writeOutput("<li>#myQuery["platform"][i]#</li>");
}
// By query
for (row in myQuery) {
writeOutput("<li>#row.platform#</li>");
}
// By arrayeach
writeOutput("<h3>By arrayeach:</h3>");
function printArray(vendor, index)
{
writeOutput("<li>#vendor#</li>");
}
arrayEach(platform,printArray);
I created a gist for you on TryCF.com so you can see this code in action and play around with it if you like. Just click here to run the code.

Best way to dynamically set host server in ColdFusion 10

I use the following to dynamically detect the host server. The importance for making it dynamic is that currently there are too many hard coded redirect such as:
http:s//mysite.com/hr/index.cfm
within my app.
When I'm moving from production site to development site and then back to production site, I have to manually change/comment out this http/https one by one and it is not only time consuming but also dangerous.
Here is the code I found that can detect the host server. Then I do the following:
<CFSET inet = CreateObject("java", "java.net.InetAddress")>
<CFSET inet = inet.getLocalHost()>
<CFSET HostServer = "#inet.getHostName()#">
<CFSET ThisHostServer = "#LEFT(HostServer,6)#">
<CFSWITCH expression="#Trim(ThisHostServer)#"><!--- Prod or Dev server --->
<CFCASE value="myprodsite.com">
<CFSET UseThisURL = "http://myprodsite.com">
</CFCASE>
<CFCASE value="mydevsite.com">
<CFSET UseThisURL = "http://myDevsite.com">
</CFCASE>
</CFSWITCH>
Then on each page where links or redirection exist, I just need to use:
#UseThisURL#/hr/index.cfm
My question is:
Where is the best way to set #UseThisURL# in the application?
I'm using ColdFusion 10 and Application.cfc in Linux env.
Should I set it as an application or a session scope?
Since everything will be in an application or session scope, when users are idle on a certain page and the application/session scope is expired, when user click on a link will it generate an error? How to prevent users from seeing error caused by using this technique? Please advice, thank you!
Best practice that I used is creating config.cfc which can contain function like getServerSpecificVariables() to return structure. this structure will be saved in your application scope since you don't want to create USEThisURL for every session start. When you need to reset simply clear your application scope. instantiate below config component inside onApplicationStart event in Application.cfc
Example
Config.cfc:
component{
public struct function getServerSpeceficVariables(){
var config = {};
var inet = CreateObject("java", "java.net.InetAddress");
inet = inet.getLocalHost();
HostServer = inet.getHostName();
ThisHostServer = LEFT(HostServer,6);
switch(Trim(ThisHostServer)){
case 'myprodsite.com':{
config.useThisURL = '';
break;
}
case 'mydevsite.com':{
config.useThisURL = '';
break;
}
}
return config;
}
}

access JRUN jndi environment vaiables from coldfusion (java)

I want to put some instance specific configuration information in JNDI. I looked at the information here:
http://www.adobe.com/support/jrun/working_jrun/jrun4_jndi_and_j2ee_enc/jrun4_jndi_and_j2ee_enc03.html
I have added this node to the web.xml:
<env-entry>
<description>Administrator e-mail address</description>
<env-entry-name>adminemail</env-entry-name>
<env-entry-value>admin#mystore.com</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
In coldfusion I have tried several different approaches to querying the data:
<cfset ctx = createobject("java","javax.naming.InitialContext") >
<cfset val = ctx.lookup("java:comp/env") >
That lookup returns a jrun.naming.JRunNamingContext. If i preform a lookup on ctx for the specific binding I am adding I get an error.
<cfset val = ctx.lookup("java:comp/env/adminemail") >
No such binding: adminemail
Preforming a listBindings returns an empty jrun.naming.JRunNamingEnumeration.
<cfset val = ctx.listBindings("java:comp/env") >
I only want to put a string value (probably several) into the ENC (or any JNDI directory at this point).
Never used it, but I got curious so I decided to try ... with no success.
I found this though, hopefully it helps you.
http://www.adobe.com/livedocs/coldfusion/7/htmldocs/wwhelp/wwhimpl/common/html/wwhelp.htm?context=ColdFusion_Documentation&file=00001570.htm

How do I create a ColdFusion web service client that uses WS-Security?

I've exposed several web services in our product using Java and WS-Security. One of our customers wants to consume the web service using ColdFusion. Does ColdFusion support WS-Security? Can I get around it by writing a Java client and using that in ColdFusion?
(I don't know much about ColdFusion).
I'm assuming you mean you need to pass the security in as part of the SOAP header. Here's a sample on how to connect to a .Net service. Same approach should apply w/ Java, just the url's would be different.
<cfset local.soapHeader = xmlNew()>
<cfset local.soapHeader.TheSoapHeader = xmlElemNew(local.soapHeader, "http://someurl.com/", "TheSoapHeader")>
<cfset local.soapHeader.TheSoapHeader.UserName.XmlText = "foo">
<cfset local.soapHeader.TheSoapHeader.UserName.XmlAttributes["xsi:type"] = "xsd:string">
<cfset local.soapHeader.TheSoapHeader = xmlElemNew(local.soapHeader, "http://webserviceUrl.com/", "TheSoapHeader")>
<cfset local.soapHeader.TheSoapHeader.Password.XmlText = "bar">
<cfset local.soapHeader.TheSoapHeader.Password.XmlAttributes["xsi:type"] = "xsd:string">
<cfset theWebService = createObject("webservice","http://webserviceUrl.com/Webservice.asmx?WSDL")>
<cfset addSOAPRequestHeader(theWebService, "ignoredNameSpace", "ignoredName", local.soapHeader, false)>
<cfset aResponse = theWebService.SomeMethod(arg1)>
Hope this is what you needed.
This is probably more accurate to produce the 'simple' xml soap header. The example above is missing a few lines.
Local['soapHeader'] = xmlNew();
Local['soapHeader']['UsernameToken'] = xmlElemNew(local.soapHeader, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "UsernameToken");
Local['soapHeader']['UsernameToken']['username'] = xmlElemNew(local.soapHeader, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "username");
Local['soapHeader']['UsernameToken']['username'].XmlText = Arguments.szUserName;
Local['soapHeader']['UsernameToken']['username'].XmlAttributes["xsi:type"] = "xsd:string";
Local['soapHeader']['UsernameToken']['password'] = xmlElemNew(local.soapHeader, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "password");
Local['soapHeader']['UsernameToken']['password'].XmlText = Arguments.szPassword;
Local['soapHeader']['UsernameToken']['password'].XmlAttributes["xsi:type"] = "xsd:string";
addSOAPRequestHeader(ws, "ignoredNameSpace", "ignoredName", Local.soapHeader, false);
I've never done any ws-security, and don't know if ColdFusion can consume it or not, but to answer your secondary question:
Can I get around it by writing a java client and using that in coldfusion?
Yes, absolutely. ColdFusion can easily use Java objects and methods.