Best practice for Datasource use in a CFC - coldfusion

I have an application which uses context sensitive datasources. Currently I keep the datasource information stored a such
reqeust.DB.Datasource = "DatasourceName";
request.DB.Username = "DatasourceUsername"
request.DB.Password = "DatasourcePassword"
I then overwrite the variables depending on the context, so each cfquery tag has the attributes datasource="#request.DB.Datesource#" ... etc ...
I want to start moving to more CFC centric frameworks like Coldbox, but I just don't see how this would work.
Do I need to pass in a datasource object into the init statement of the CFC? This seems like it would be a super PITA.

With CF9, you can this.datasource in Application.cfc as the default datasource. Unfortunately, it doesn't seem to have a way to set username/password
Either
A.) use an Dependency Injection framework such as ColdSpring (only suitable for singleton Services), Lightwire or Coldbox's own DI solution (Wirebox). and inject the datasource/username/password through the init constructor or setters.
B.) set <Datasources> in Coldbox.xml.cfm, see: http://wiki.coldbox.org/wiki/ConfigurationFile.cfm
<!--Datasource Setup, you can then retreive a datasourceBean
via the getDatasource("name") method: -->
<Datasources>
<Datasource alias="MyDSNAlias"
name="real_dsn_name"
dbtype="mysql"
username=""
password="" />
</Datasources>

Even if your objects only get initialized at request level, it seems like it should be less of a pain to work with in this fashion.
<cfscript>
request.DB.Datasource = "DatasourceName";
request.DB.Username = "DatasourceUsername";
request.DB.Password = "DatasourcePassword";
request.randomDAO = createObject('component','DAOStuff.randomDAO');
request.randomDAO.init(DBObject = request.DB);
request.someQuery = request.randomDAO.someGetter();
request.someOtherQuery = request.randomDAO.someOtherGetter();
request.aThirdQuery = request.randomDAO.aThirdGetter();
</cfscript>
As opposed to:
<cfscript>
request.DB.Datasource = "DatasourceName";
request.DB.Username = "DatasourceUsername";
request.DB.Password = "DatasourcePassword";
</cfscript>
<cfquery name="request.someQuery"
datasource=request.DB.Datasource
username=request.DB.Username
password=request.DB.Password>
--SOME SQL HERE
</cfquery>
<cfquery name="request.someOtherQuery"
datasource=request.DB.Datasource
username=request.DB.Username
password=request.DB.Password>
--SOME SQL HERE
</cfquery>
<cfquery name="request.aThirdQuery"
datasource=request.DB.Datasource
username=request.DB.Username
password=request.DB.Password>
--SOME SQL HERE
</cfquery>
If it is safe for your data objects to exist at an application level (assuming here that the data source for the object will not change at run-time and that you have written thread-safe CFCs) You can store and initialize DAOs at application level and then each request has wonderfully simple code like:
<cfscript>
request.someQuery = application.randomDAO.someGetter();
request.someOtherQuery = application.randomDAO.someOtherGetter();
request.aThirdQuery = application.randomDAO.aThirdGetter();
</cfscript>

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

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;
}
}

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

coldfusion 9 dynamically call method

I'm attempting to build a method call from strings that have been passed into an object that refer to another object.
normally when calling an object we write the code like this:
application.stObj.oNewsBusiness.getNews(argumentCollection=local.stArgs);
However what I have done is created an array that contains the object name, the method name and the argument collection.
<cfscript>
local.stArgs = {};
local.stArgs.nNewsID = 19;
local.stArgs.sAuthor = "John";
local.aData = [];
local.aData[1] = local.stArgs;
local.aData[2] = "stObj.oNewsBusiness";
local.aData[3] = "getNews";
</cfscript>
however i am struggling to recombine all this to be a method call.
UPDATE using suggestion but still with issue
While cfinvoke seems to work for:
<cfinvoke component="#application.stObj.oNewsBusiness#" method="#local.sMethod#" argumentcollection="#local.stArgs#" returnvariable="local.qData"></cfinvoke>
it doesn't work when doing something like:
<cfscript>
local.stArgs = local.aData[1];
local.sObject = local.aData[2];
local.sMethod = local.aData[3];
</cfscript>
<cfinvoke component="application.#local.sObject#" method="#local.sMethod#" argumentCollection="#local.stArgs#" returnvariable="local.qData"></cfinvoke>
it generates an error:
Could not find the ColdFusion component or interface application.stObj.oNewsBusiness
CFInvoke is generally used to handle dynamic method calls.
http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-7e0a.html
CFInvoke has an argumentcollection attribute so you can pass your arguments in the way you are used to.
Dan is correct CFInvoke is the way to go
<cfinvoke component="#mycomponentname#" method="get" arg1="#arg1#" arg2="#arg2#" arg3=..>
<cfinvoke component="application.#local.sObject#" method="#local.sMethod#"argumentCollection="#local.stArgs#" returnvariable="local.qData"></cfinvoke>
from your update won't work because there are no # signs around the component variable.
You could do
<cfset local.componentName = "application." & local.sObject>
<cfinvoke component="#local.componentName#" method="#local.sMethod#"argumentCollection="#local.stArgs#" returnvariable="local.qData"></cfinvoke>
There's probably an inline way of combining application. with the variable on the cfinvoke call, but I don't know off the top of my head.
Edit: Dan Wilson's comment does it better in an inline way.

cfwheels nested properties issue

I'm trying to get this to work...
I have a five tables that I"m trying to tie together: properties, languages, propertyLanguages, buildings and buildingTranslations
properties,languages and propertylanguages is a typical many-to-many relationship that I have working. What I'm trying to do next is have the buildings, which are linked to the property and have text fields for each language that will go inside the buildingtranslations.
I've setup the foreign keys for the propertylanguages, buildings and buildingtranslations
I'm just not sure how to setup the model and the controller when creating/updating building records
edit
I've managed to create a view in mssql that represents the relationship
hopefully this makes it easier to see the relationships.
I want to create and edit Buildings with the translation fields included (and updated in the database)
The languages are assigned at the property level. The building that is linked to the property through the propertyid uses the languages available (through propertylanguages[where propertyid = building.propertyid]) to determine the buildingTranslations required for the building
Hopefully this helps some:
models/Building.cfc
hasMany(name="BuildingTranslations", foreignKey="yrhBuildingId");
belongsTo(name="Property", foreignKey="yrhPropertyId");
controllers/Buildings.cfc
function new () {
building = model("Building").new();
building.yrhPropertyId = params.yrhPropertyId; //assuming this was passed in
requiredLanguages = model("PropertyLanguages").findAll(where="yhrPropertyId=#building.yhrPropertyId#");
}
function create () {
building = model("Building").new(params.Building);
building.save();
requiredLanguages = model("PropertyLanguages").findAll(where="yhrPropertyId=#building.yhrPropertyId#");
for (var i = 1; i <= requiredLanguages.recordCount; i++)
{
buildingTranslation = model("BuildingTranslation").new();
buildingTranslation.yrhBuildingId = building.id;
buildingTranslation.yrhLanguageId = requiredLanguages.yrhLanguageId[i];
buildingTranslation.langName = params.BuildingTranslations[requiredLanguages.yrhLanguageId[i]];
buildingTranslation.save();
}
redirectTo(action="list");
}
here's the approach I've taken
<cfset viewBuildingNames = model("yrhBuildingNamesView").findAll(where="yrhBuildingId=#params.key#")> <!--- FIND ALL BUILDING NAMES --->
<cfset yrhbuilding = model("Yrhbuilding").findByKey(key=params.key)> <!--- CREATE BUILDING MODEL --->
<cfset yrhproperty = model("YrhProperty").findByKey(key=yrhbuilding.yrhPropertyId, include="YrhPropertyLanguages")> <!--- language info through property--->
<cfset yrhbuilding.yrhproperty = yrhproperty>
<cfset yrhbuilding.yrhBuildingTranslations = ArrayNew(1)>
<cfloop query="viewBuildingNames">
<cfset yrhBuildingTranslation = model("yrhBuildingTranslation").new(yrhBuildingId=#yrhBuildingId#, yrhLanguageId=#yrhLanguageId#, langName=#LANGNAME#)>
<cfset ArrayAppend(yrhbuilding.yrhBuildingTranslations, yrhBuildingTranslation)>
</cfloop>
it's sort of half cfwheels way. it relies on the view created in the database
once the mode; is created, the updating works, but I don't get an error message on the empty langNames, just an error, Which I can live with.
I'm planning on adding another layer of items residing under the building, that will require the same connection to the propertyLanguages, While it should still work OK I'm getting increasingly queasy about the cfwheels magic taking care of these things. I might be switching to handling complex relationships directly.