In ColdFusion the cflock tag provides a timeout in seconds. My problem is that I want to unlock the code when certain other code is completed. For example, I have code that says:
<!---need some kind of lock here --->
<!---this code is in a loop --->
<cffile action = 'append'
file = '#path#'/temp.cfm'
output = '#myvariable#'>
Later in the program the temp file is picked up, to be combined with other information:
<cffile action = 'read'
file = '#path#/temp.cfm'
variable = 'xyz'>
But I can't guess how long it will take to get from the creation of temp to the reading of it. That may depend for example on the number of records in a MySql file, or could timeout because of slow traffic, or for other reasons.
So what I'd like to do is unlock temp after the read is completed. In other words, I'd like a cfunlock tag, but there doesn't seem to be one. Does anyone know how to do this?
The cflock timeout parameter does not specify how long the code can take to run, it specifies the time to wait for a lock to be provided before timing out. When the code inside your cflock block is complete, your cflock will be unlocked.
If the "Later in the program" read is actually later in the same single synchronous request as the original append/write, then you don't even need the lock, unless there is a risk of multiple concurrent requests.
If the read is in a separate request to the write, but you know the write will always start first, then you just need a type="exclusive" lock on the write and a type="readonly" lock on the read, using the same lock name, then the readonly lock will wait for the code in the exclusive lock to complete.
If your read might start before your write, and you know you need to wait for the first/next write state to be reached before the read would be valid, then that's when you would need to add some polling logic of your own to check whether the desired state has been reached before reading. There are many ways of achieving that - essentially after the write has completed, you could flag that state in an application variable or in your database, or rename the file after the write is complete, and then before your read code, check for the expected state in a loop and wait and check again until that state is reached, before continuing with the read.
The following solution requires no lock. It makes use of a persistent scope, for example, request, session or application. Choose the scope appropriate to your case.
<!--- Placed before the file-append code --->
<cfset session.isTempFileAppendComplete=false>
...
<!---this code is in a loop --->
<cffile action = 'append'
file = '#path#'/temp.cfm'
output = '#myvariable#'>
...
<!--- Placed after the file-append code --->
<cfset session.isTempFileAppendComplete=true>
<cfif session.isTempFileAppendComplete>
<cffile action = 'read'
file = '#path#/temp.cfm'
variable = 'xyz'>
</cfif>
If there are going to be multiple concurrent users, with the possibility of each having multiple concurrent requests, then use an application-scoped or a named lock. However, I assume a single concurrent user.
To avoid concurrent requests, add session locks (not named locks, as these are in application scope):
<cflock timeout = "10" scope = "session" type = "exclusive">
<cfset session.isTempFileAppendComplete=false>
</cflock>
...
<cflock timeout = "600" scope = "session" type = "exclusive"><!---decide on timeout estimate --->
<!---this code is in a loop --->
<cffile action = 'append'
file = '#path#'/temp.cfm'
output = '#myvariable#'>
</cflock>
...
<cflock timeout = "600" scope = "session" type = "exclusive">
<!--- Placed after the file-append code --->
<cfset session.isTempFileAppendComplete=true>
</cflock>
<cflock timeout = "60" scope = "session" type = "readOnly">
<cfif session.isTempFileAppendComplete>
<cffile action = 'read'
file = '#path#/temp.cfm'
variable = 'xyz'>
</cfif>
</cflock>
Related
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).
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;
}
}
I have a series of files being generated with the following code:
<cfloop list="#clients#" index="idx">
<cfexecute name="c:\windows\system32\cmd.exe" arguments='/c #APPLICATION.basepath#scripts\foldersize\dirP.bat #idx#'/>
<cfexecute name="c:\windows\system32\cmd.exe" arguments='/c #APPLICATION.basepath#scripts\foldersize\dirA.bat #idx#'/>
</cfloop>
This fires off a number of requests to list those directories and pipe the findstr filter through to the respective files simultaneously...
What I need though is a way to ensure that the files are completely created before I use CFFILE on them to read in the output...
I'd use FileExists(), but it acts a little prematurely and I need it to be dead certain that the file is complete... Any ideas how to resolve this?
Setting a high timeout doesn't actually validate file write completion. It just allows for code to run longer. Also, just being able to read the file doesn't mean it is written either. What you need to do is watch the file size for changes. But you have to be careful not to cause a blocking condition when getting the file info. An attempt to read the file without creating a non-blocking read could disrupt the file write and cause it to fail. This can be accomplished using the following code.
Given a file, the code will run till the file is written and not locked. This code is a few years old but you should get the idea.
<cffunction name="isFileWritten" access="public" returntype="string">
<cfargument name="source" type="struct" required="yes"/>
<cfscript>
thisFile = source;
fileRead = createObject("java", "java.io.FileInputStream");
thisThread = CreateObject("java", "java.lang.Thread");
loopCT = 1;
while(1 EQ 1)
{
try
{
fileRead.init(thisFile);
break;
}
catch(any ecpt)
{
thisThread.sleep(1000);
}
incrementValue(loopCT);
if(loopCT GT 60)
{
fileRead.close();
return;
}
}
loopCT = 1;
while(1 EQ 1)
{
sizeA = fileRead.available();
thisThread.sleep(1000);
sizeB = fileRead.available();
if(sizeA EQ sizeB)
{
thisThread.sleep(1000);
sizeC = fileRead.available();
if(sizeC EQ sizeB)
{
break;
}
}
incrementValue(loopCT);
if(loopCT GT 60)
{
fileRead.close();
return;
}
}
fileRead.close();
</cfscript>
<cfreturn>
You really should show your batch code. Presumably your batch scripts produce files with a particular name or extension, and you then you later use FileExists() to look for the existence of a file with the appropriate name to process.
I suspect the simplest solution would be to modify your batch scripts such that it first creates a file with an alternate name that is not recognized. When the file is complete, have your batch script use MOVE to rename the file to the correct name. In this way your FileExists() test can be sure that the file is complete.
<cfexecute> has the timeout parameter. Just set it to a high number.
It might be necessary to increase the overall request timeout.
<cfsetting requesttimeout="300">
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."
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. :)