Component Level Properties In Application.cfc - coldfusion

If I have component level properties in my application.cfc will they get evaluated every time a page is it or will they only get evaluated when the application is created?
<cfcomponent>
<cfscript>
this.name = "WARM2_Demo";
this.applicationTimeout = CreateTimeSpan(1,0,0,0);
this.setClientCookies = true;
this.setDomainCookies = false;
this.sessionManagement = true;
this.sessionTimeout = CreateTimeSpan(0,0,30,0);
this.clientManagement = false;
this.scriptProtect = true;
this.appDirectory = getDirectoryFromPath(getCurrentTemplatePath());
this.fileSeparator = createObject("java","java.lang.System").getProperty("file.separator");
....
</cfscript>
<cffunction name="OnApplicationStart" returntype="boolean">
<cfscript>
setupApplication();
return true;
</cfscript>
</cffunction>
....
</cfcomponent>

The pseudo-constructor of Application.cfc is executed every request.
Basically behind the scenes an instance of Application.cfc is created every request, and that instantiation behaves just like any other CFC instantiation: the pseudo-constructor bit - the stuff inside the CFCOMPONENT tags but outwith any CFFUNCTION / function declarations - is run.
After the pseudo-constructor is run, any appropriate event handler / interceptor methods are run, eg: onRequestStart().
NB: you could test this very easily yourself by just outputting something in there. It'll show up on the screen on every request (make it a getTickCount() or createUuid() call so you can see it changing).

evaluated every time a page is requested, I think.
stick them in Application scope in onApplicationStart()
but honestly, fileSeparator?? Just use /, it'll work on Windows and *nix OS just fine. :)

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).

cfinvoke with two different methods

I have two cfinvoke, I need to use them in one cfm
<cfinvoke component="cfc/queries" method="getProjects" searchString="#Session.Auth.pref_name#" view="#Session.Auth.view#" returnvariable="Projects">
<cfinvoke component="cfc/queries" method="projectDetails" searchString="#URL.id#" projectsuffix="#URL.suffix#" returnvariable="Details">
to return two queries, but when I coding like this way it's not working.
I'm still new to the ColdFusion and I don't know how to fix that.
Since both functions are in the same CFC, you wouldn't want to use cfinvoke since it recreates the object each time it's called. Instead, use a new or a createObject().
<cfset queries = new location.to.cfc.queriesCFC()>
Then you can just reference the functions.
<cfset Projects =
queries.getProjects(
searchString=session.Auth.pref_name,
view = session.Auth.view
)
>
<cfset Details =
queries.projectDetails(
searchString=url.id,
projectsuffix=url.suffix
)
>
You may want to sanitize url.id and url.suffix before you pass them through. This will help with injection issues.
What does getProjects() do?
We can write like as below,
<!--- Object creation --->
<cfset query = CreateObject("component", "cfc.queries")/>
<!--- Function call --->
<cfset Projects = query.getProjects( searchString = session.Auth.pref_name, view = session.Auth.view )>
<cfset Details = query.projectDetails( searchString = session.Auth.pref_name, view = session.Auth.view )>

Using "var this" inside remote CFC methods

I've inherited a project where there are a number of remote CFC's opened up for some Ajax requests and inside most methods in the CFC have the following:
<cfset var this.response = true />
Now I've never seen the var and this scope used together like this so I'm really not sure what to make of it so I guess my questions is:
Are there any issues with how this was coded? If so, are they major enough that I should put in
the effort to update all the CFC's to something like <cfset var
req.response = true />?
Here is a quick example of what I'm seeing:
<cfcomponent>
<cffunction name="check_foo" access="remote" returnformat="plain">
<cfargument
name = "isfoo"
type = "string"
required = "false"
default = "nope"
hint = "I check the string for foo"
/>
<cfscript>
/*setup new response*/
var this.response = false;
/*check for foo*/
if( !findnocase( "foo", arguments.isfoo ) ) {
/*no foo!*/
this.response = false;
}
return this.response;
</cfscript>
</cffunction>
</cfcomponent>
.
Updates:
Based on the feedback/answers below I've replace all instances of var this. Thanks again to everyone that helped out!
.
update: upon checking your dump, the "this" in var this is still this this scope, not local.this.
It is setting the response to the this scope, and it works in this case because because the CFC is instantiated every time it's being invoked remotely. However, it'd be best to rename this into something else to ensure thread-safety in case the method is invoked by other CFC as public method.
Using var this is the same as using this.
Dumping the local scope will include local variables as well as the Arguments and This scopes. (Can't find this documented; but I get this result in a bare cfc, and you got it in your screenshots.)
Because your function is access="remote" you'll be getting a new instance of the cfc on every call, and therefore a bare This scope. So those are "safe", but still a bad idea.
If there is any use of var this in non-remote functions then you will be getting undesired persistence and may suffer race conditions that result is invalid data.
Relevant CF documentation:
"Methods that are executed remotely through Flash Remoting and web services always create a new instance of the CFC before executing the method."
"Variable values in the This scope last as long as the CFC instance exists and, therefore, can persist between calls to methods of a CFC instance."

How do I call a second function within the same CFC in the APPLICATION scope?

I am using ColdFusion 9.0.1.
Let me start by stating that I may not be asking the right question. Since each function works independently and fails only when one function calls another, I am thinking that the problem is in how the function is called.
I am creating an application variable that contains a structure. The structure contains the reference to an object, orders.cfc.
if (not isDefined("APPLICATION.AppInfo") or not isStruct(APPLICATION.AppInfo)) {
APPLICATION.AppInfo = structNew();
APPLICATION.AppInfo.objOrders = createObject("component", "globaladmin.orders");
}
I am able to successfully access the methods in the orders.cfc like this:
OrderItemList = APPLICATION.AppInfo.objOrders.orderItemList(URL.Customer);
I have methods in the orders.cfc that call other methods in the order.cfc, kind of like this (faked for simplicity):
<cffunction name="orderItemList">
<cfscript>
LOCAL.RandomNumber = getRandomNumber();
return LOCAL.RandomNumber;
</cfscript>
</cffunction>
<cffunction name="getRandomNumber">
<cfscript>
LOCAL.SomeNumber= randRange(0,10);
return LOCAL.SomeNumber;
</cfscript>
</cffunction>
I get this error:
Entity has incorrect type for being called as a function. The symbol you provided getRandomNumber is not the name of a function.
I figured maybe I can't reference a function within the same CFC without creating an object first, so I do this:
<cffunction name="orderItemList">
<cfscript>
LOCAL.RandomNumber = APPLICATION.AppInfo.objOrders.getRandomNumber();
return LOCAL.RandomNumber;
</cfscript>
</cffunction>
Then, I'd get this error:
Either there are no methods with the specified method name and argument types, or the method getRandomNumber is overloaded with arguments types that ColdFusion can't decipher reliably. If this is a Java object and you verified that the method exists, you may need to use the javacast function to reduce ambiguity.
How should I call a second function within the same CFC?
The first thing I would try is var scoping your all your variables within your functions:
<cffunction name="orderItemList">
<cfscript>
var RandomNumber = getRandomNumber();
return RandomNumber;
</cfscript>
</cffunction>
<cffunction name="getRandomNumber">
<cfscript>
var SomeNumber= randRange(0,10);
return SomeNumber;
</cfscript>
</cffunction>
If that doesn't solve the problem, let me know and we can explore further.
edit
Okay, now that the local scope issue is resolved, try this:
<cffunction name="orderItemList">
<cfscript>
LOCAL.RandomNumber = THIS.getRandomNumber();
return LOCAL.RandomNumber;
</cfscript>
</cffunction>
<cffunction name="getRandomNumber">
<cfscript>
LOCAL.SomeNumber= randRange(0,10);
return LOCAL.SomeNumber;
</cfscript>
</cffunction>

What's the best way to unit test a cfc that uses a Java object for a lot of its functionality?

I have a cfc that relies heavily on a Java object (created via JavaLoader) for a lot of its core functionality that I'd like to write some tests for and I'm not sure what the best way to do this is. Here is an example of a method I'd like to write a test for with instance.note being a java object.
<cffunction name="getNotes" returntype="Array" access="public" output="false" hint="I return a list of a users notebooks" >
<cfargument name="maxCount" type="numeric" required="false" default="9999" hint="The maximum number of notes to get" />
<cfscript>
if(arguments.maxCount)
return instance.note.listNotes(maxCount);
else
return instance.note.listNotes();
</cfscript>
</cffunction>
One thing I thought of doing is creating a stub CFC that has the same method names and similar return values and then mocking that stub and injecting it?
Can't you just write meaningful assertions on the result, i.e. on the array of notes? Looking at that code, the only things I'd test are a) when you pass a maxCount, does your resultant array honor that size? b) without maxCount, is the list of notes the length that you'd expect? Because that's all your code does. I'd test your code, not the code of the underlying java object.
When we needed to unit test CF functions that relied upon Java objects (which we did a LOT of), we used Mockito to mock the Java objects.
So, hoping this code snippet makes sense, its been almost a year since I've done this:
<cfcomponent displayname="TestWhatever" extends="mxunit.framework.TestCase" output="false">
<cffunction name="setUp" access="public" returntype="void">
<cfscript>
// named it mk for keeping it short
variables.mk = createObject("java","org.mockito.Mockito");
//Create the mock object
variables.mockNote = mk.mock(createObject("java","com.company.whatever.note").getClass());
// Mock Data
fullList = {whatever listNotes() returns}
partialList3 = {whatever listNotes(3) returns}
//some common mocking
mk.when(variables.mockNote.listNotes()).thenReturn(fullList);
mk.when(variables.mockNote.listNotes(mk.eq(3))).thenReturn(partialList3);
mk.when(variables.rootOrgObj.guid()).thenReturn("root");
// Assign the mock object to where your CFC expects it.
instance.note = variables.mockNote
</cfscript>
</cffunction>
</cfcomponent>
Having said that, if your sample function is real, there's really no point in unit testing it. Its simply not doing anything but being a proxy to the java object. There is no significant logic in there to test.
Since you have a default on the cfargument, it will always exist (again, I think so, its been a year since I've done CF), so your guard statement isn't even required - the first code path will always be called, with the passed maxCount if specified, or with 9999 if not.
I took Edward's answer and implemented it like so:
I used the JavaLoader library to create my mockito object.
variables.cfEvernote = "";
variables.classLoader = createObject("component", "resources.JavaLoader").
init(["#expandPath('../lib/mockito-all-1.8.5.jar')#",
"#expandPath('../lib/CFEvernote.jar')#",
"#expandPath('../lib/libthrift.jar')#",
"#expandPath('../lib/evernote-api-1.18.jar')#"]);
variables.mockito = variables.classLoader.create("org.mockito.Mockito").init();
Then in the setup method of my munit test I create my new mock java object:
<cffunction name="setUp" access="public" output="false" returntype="void">
<cfscript>
variables.cfEvernote = createObject("component","com.714studios.cfevernote.CFEvernote").
Init(variables.configArray[1],variables.configArray[2],
"sandbox.evernote.com",
"http://localhost/cfevernote/callback.cfm"
"#ExpandPath('../lib')#");
variables.mockCFEvernote = variables.mockito.mock(variables.classLoader.create("com.sudios714.cfevernote.CFEvernote").
Init("123","S1","232","sandbox.evernote.com","mock").getClass());
variables.cfEvernote.setCFEvernote(mockCFEvernote);
</cfscript>
Then in my tests I create my mock behavior like so.
<cffunction name="test..." returntype="void" access="public" output="false" >
<cfscript>
var notebooks = "";
var expected = 12;
var i = 0;
var retArray = createObject("Java","java.util.ArrayList");
var actual = "";
for(i = 1; i lte 12; i = i + 1){
retArray.Add("");
}
variables.mockito.when(mockCFEvernote.listNotebooks(12)).thenReturn(retArray);
notebooks = variables.cfEvernote.getNotebooks(12);
actual = arrayLen(notebooks);
assertEquals(expected,actual);
</cfscript>
I've also blogged about it in a bit more detail here - http://blog.bittersweetryan.com/2011/07/unit-testing-coldfusion-components-that.html.