CFWheels - Validate two different forms using the same model - model-view-controller

I'm new to wheels (and I'm sure I'll be posting here a lot) so bear with me.
I have two forms "register" and "login" under the controller of "user". So my URL's look like.
/user/register/
/user/login/
At the moment in my models folder I simply have user.cfc with validation for the "register" page inside the init method - this works just fine.
So essentially...my question is...regarding validation for my login form; do I have to always place validation into the init method or in a different one? If so, how do I do this? Each form of course has different fields...so I need to know some logic on detecting what form is currently in play.
Hope this makes sense. For reference, my user.cfc model currently looks like this:
<cfcomponent extends="Model" output="true">
<cffunction name="init">
<cfset validate( property='userName', method='validateAlphaNumeric') />
<cfset validatesPresenceOf( properties='userName') />
<cfset validatesUniquenessOf( properties='userName') />
<cfset validatesFormatOf( property='userEmail', type='email', message="Email address is not in a valid format.") />
<cfset validatesPresenceOf( properties='userEmail') />
<cfset validatesUniquenessOf( properties='userEmail') />
<cfset validatesPresenceOf( properties='userPassword') />
<cfset validatesConfirmationOf( property='userPassword') />
<cfset validatesLengthOf( property="userToken", allowBlank=true) />
</cffunction>
<cffunction name="validateAlphaNumeric" access="private">
<cfif REFind("[^A-Za-z0-9]", this.userName, 1)>
<cfset addError( property="userName", message="User name can only contain letters and numbers." ) />
</cfif>
</cffunction>
</cfcomponent>
Thanks,
Michael.

Michael,
You do need to put model validations in your init() method; Wheels requires that. However, I am not sure you would want or need to use model validation for the login page/call.
Unless I am missing something, you're not actually altering the model (i.e., creating a new or updating an existing user) when a user logs into a site. You are simply authenticating them (checking their username/password combo) against your DB values.
If it were me, I'd use client-side validation for login (fields are completed, etc.) and model validation for register.
HTH!
Craig

Go to this URL and scroll down to: Use "when", "condition", or "unless" to Limit the Scope of Validation
http://cfwheels.org/docs/1-1/chapter/object-validation
For your case, you could probably go with when="onCreate"

Related

ColdFusion - Capture Login Date / Time and Logout Date / Time

I want to know if there is a way to capture a users id when their session times out. I can capture if they click a logout button. I just want to be able to capture if their session times out.
Added this below
<cffunction name="onSessionEnd" access="public" returntype="void">
<cfquery name="logout" datasource="#application.datasource#">
update user
set logout_date = CURRENT_TIMESTAMP
where profile_id = #session.user_id#
</cfquery>
<cfreturn true />
</cffunction>
So I did the above It is not recording the logout date when the session times out.
Adding this comment as an answer for better formatting
Leigh deserves the credit. If you add your answer I will remove this one in favor of yours.
Given the example that you just added to your answer, you are doing it wrong. As Leigh pointed out in his comments you need to reference the session scope differently inside the OnSessionEnd method.
You are also missing the arguments for the OnSessionEnd method.
You will probably also want to make sure the variables are defined before trying to use them.
You should also use <cfqueryparam> within your queries.
Put it all together and it should look something like this:
<cffunction name="OnSessionEnd" access="public" returntype="void" output="false">
<cfargument name="SessionScope" type="struct" required="true" />
<cfargument name="ApplicationScope" type="struct" required="false" default="#StructNew()#" />
<cfif StructKeyExists(ARGUMENTS.ApplicationScope,"datasource") AND StructKeyExists(ARGUMENTS.SessionScope,"user_id")>
<cfquery name="local.logout" datasource="#ARGUMENTS.ApplicationScope.datasource#">
update user
set logout_date = CURRENT_TIMESTAMP
where profile_id = <cfqueryparam cfsqltype="cf_sql_integer" value="#ARGUMENTS.SessionScope.user_id#" />
</cfquery>
<cfelse>
<!--- variables are not defined, do something else here? --->
</cfif>
<cfreturn />
</cffunction>
Also, the request scope is not available within this method because it is not called by a request.
So had to change the application variable to a session variable only one that worked for me.
<cffunction name="onSessionEnd" access="public" returntype="void">
<cfquery name="logout" datasource="#session.datasource#">
update user
set logout_date = CURRENT_TIMESTAMP
where profile_id = #session.user_id#
</cfquery>
<cfreturn true />
</cffunction>

ColdFusion User login fails after session timeout

We’ve recently moved to cf11 for a project and have hit on an unusual problem:
When a user lets their session timeout, and they try to login back in it takes two attempts for them to successfully log in.
When a user manually logs out, they have no problems logging in.
This problem didn’t happen in CF8. I’ve examined user scopes and cannot see a difference. I’ve tried adding the logout code before sign-in in the hope I can make the state the same. Neither has worked. Is this a known problem? And do you have a suggestion to what I can try?
EDIT:
I have an Application.cfc and result.cfm in the root of the project.
I have a signin/ folder for pages that are not logged in. That contains signin.cfm and onsignin.cfm that handles the authentication.
When running the code, wait for the session to time out before logging in again with the same user name again.
signin/signin.cfm
<form action="onsignin.cfm" method="POST" >
User name: <input name="login" type="text" />
<input type="submit" value="Login" name="btnSubmit" >
</form>
signin/onsignin.cfm
<cffunction name="authenticate" access="public" returntype="void">
<cfargument name="login" />
<cflogin idletimeout="3600">
<cfloginuser name="#arguments.login#" password="1234AbCd" roles="admin,developer,login_session,login_signoff" />
</cflogin>
<cfreturn />
</cffunction>
<cfscript>
authenticate(trim(Login));
writeOutput("At /signin/onsignin.cfm<br/>");
writeOutput("IsUserLoggedIn: #IsUserLoggedIn()#<br /><hr/>");
</cfscript>
<cflocation url="../result.cfm" addToken="no" /><!--- before we get to result.cfm onRequestStart() in Application.cfc is triggered. --->
Application.cfc
<cfcomponent>
<cfset THIS.Name = "LoginTest" />
<cfset THIS.SessionManagement = true />
<cfset THIS.ClientManagement = false />
<cfset THIS.LoginStorage = "session" />
<cfset THIS.setClientCookies = false />
<cfset This.sessiontimeout= createTimeSpan(0,0,0,20)/><!--- 20second timeout to show the session problem --->
<cffunction name="outputCurrentLoginState" access="private">
<cfargument name="currentFunction" type="string" required="true"/>
<cfargument name="TargetPage" type="string" required="true"/>
<cfscript>
writeOutput("In function: #arguments.currentFunction# state is <br />");
writeOutput("TargetPage: #arguments.TargetPage# <br />");
writeOutput("GetAuthUser: #GetAuthUser()#<br />");
writeOutput("GetUserRoles: #GetUserRoles()#<br />");
writedump(session);
writedump(cookie);
writeOutput("IsUserLoggedIn: #IsUserLoggedIn()#<br /><hr />");
/*use writeLog() to view to Console */
</cfscript>
</cffunction>
<cffunction name="OnRequestStart" access="public" returntype="boolean">
<cfargument name="TargetPage" type="string" required="true"/>
<cfset outputCurrentLoginState("OnRequestStart",arguments.TargetPage)/>
<cfif not IsDefined("Cookie.CFID")>
<CFLOCK SCOPE="SESSION" TYPE="exclusive" TIMEOUT="5">
<cfcookie name="CFID" value="#SESSION.CFID#" secure="false" httpOnly="true" />
<cfcookie name="CFTOKEN" value="#SESSION.CFTOKEN#" secure="false" httpOnly="true" />
</CFLOCK>
</cfif>
<cfreturn true />
</cffunction>
<cffunction name="OnRequest" access="public">
<cfargument name="TargetPage" type="string" required="true"/>
<cfset outputCurrentLoginState("OnRequest",arguments.TargetPage)/>
<cfif findNoCase("signin/", arguments.TargetPage)>
<cflogout/>
</cfif>
<cfinclude template="#ARGUMENTS.TargetPage#" />
</cffunction>
</cfcomponent>
result.cfm
<cfscript>
writeOutput("GetAuthUser: #GetAuthUser()#<br />");
writeOutput("GetUserRoles: #GetUserRoles()#<br />");
writeOutput("--- IsUserLoggedIn: #IsUserLoggedIn()#<br />");
writeOutput("At /result.cfm<hr/>");
</cfscript>
Update: Now I have the test code above that fails for cf11, I tried it on a cf8 server and in cf8 it works as I expect it to. When the session times out, the user does not have any problem creating a new session. It is only in cf11 were it fails.
I have a work around that will be good enough for my use case.
Old code:
<cflogin idletimeout="3600">
<cfloginuser name="#yourlogin#" password="#yourpassword#" roles="#yourroles#" />
</cflogin>
New code:
<cflogin idletimeout="3600" allowconcurrent="false">
<cfloginuser name="#yourlogin##createUUID()#" password="#yourpassword#" roles="#yourroles#" />
</cflogin>
Adding a random value to the cfloginuser name attribute should make everyone unique, even if they use the same credentials when you validate them against a database.
It’s a bit of a hack, but it gets around the issue.
Thank you for everyone’s help… Especially Miguel for picking up on allowconcurrent.
I've added the following comment on 3839458:
Another workaround is duplicate the cflogin. Example:
<cflogin ..>
<cflogin ..>
When both have allowconcurrent=true (the default), then both will run. isUserLoggedIn() returns YES after the 1st, but the 1st login actually fails. The 2nd runs and logs in correctly.
Repro attached as Application.cfc
When THIS.loginStorage="cookie", then the issue does not reoccur if an old cfauthorization cookie is still present.
Thanks!,
-Aaron
Update
There is already a bug opened with Adobe about this issue - Bug 3839458. I suggest you add your experience and vote on that bug. It currently has a status of "ToTrack" with a reason of "PRNeedInfo". I have voted for it as well and added a reference to this question for more info.
Promoted from the comments
I think the problem is that you have set the idletimeout attribute of the <cflogin> tag to a value (3600 seconds) which is longer than your session timeout value (20 seconds). That is not a valid approach. I realize that you have set the session timeout at 20 seconds for ease of testing. However, it does not make sense to have a longer idletimeout than session timeout when you are setting your loginStorage to be session. The <cflogin> tag is attempting to keep the user's session active but the session itself is being torn down outside of it's control. That is asking for trouble in my opinion. Keep the idletimeout setting less than or at least equal to the session timeout setting.
I suspect the difference you are seeing between ColdFusion versions might have to do with a new attribute that was added to the <cflogin> tag in ColdFusion 11. The allowconcurrent attribute (documentation reference). By default that setting is set to true meaning concurrent login sessions should be allowed. Try setting that attribute to false, as in, <cflogin idletimeout="3600" allowconcurrent="false">. I believe that will make the <cflogin> tag work like it used to in ColdFusion 8.
You mention in the comments that setting allowconcurrent to false is not an acceptable solution. Why? That is how it is working for you on ColdFusion 8. I feel that the behavior you are seeing is "working as designed". However, if you feel that this is a bug then please enter a bug for it with Adobe.
As an aside, I also noticed that you are setting ClientManagement and setClientCookies both to false. Yet in your OnRequestStart method you are setting those cookies. Why? They are not needed for session management. The ColdFusion server will handle that for you.

CFWheels - Issue with filters 'except'. Conflicting action names

I have a filter setup that runs a function which checks if the user session is present on certain actions, like so;
<cffunction name="init">
<cfset filters(through="checkLogin", except="login,register,signin,create,home,profile") />
</cffunction>
The problem is, these are the action names...which conflict from other controllers I have.
For example, I have 2 controllers 'user' and 'link'. Each of these has an action called create, so that my URL's are like so:
/user/create/
/link/create/
How can the filter know with which controller to associate it with? Is there a way to prefix certain 'actions' in the 'except' clause with the controller name too?
For example, maybe something like:
<cffunction name="init">
<cfset filters(through="checkLogin", except="user/login,user/register,user/signin,link/create,main/home,user/profile") />
</cffunction>
I remember trying this but it didn't work and borked.
Hope you understand what I'm saying here. I don't want to have to name every action in separate controllers totally unique names.
Thanks,
Michael.
You can use basic inheritance to accomplish this:
<!--- controllers/Controller.cfc --->
<cffunction name="init">
<cfargument name="checkLoginExcept" type="string" required="false" default="">
<cfset filters(through="checkLogin", except=arguments.checkLoginExcept)>
</cffunction>
Then in any child controller (user, for example), you can specify which actions to exclude. This works well because the parent controller should really know nothing about its children. It implements what it cares about and nothing else.
<!--- controllers/User.cfc --->
<cffunction name="init">
<cfset super.init(checkLoginExcept="login,register,signIn,profile")>
</cffunction>
If another child always wants for checkLogin to run, then it shouldn't pass a value for checkLoginExcept:
<!--- controllers/Foo.cfc --->
<cffunction name="init">
<cfset super.init()>
</cffunction>

CFWheels - How can I validate a form in a model then do a lookup and output a different message?

In my "link" model I have some basic validation, one of which is to check if the link the user is submitting is already in the database.
If the link IS already submitted to the database, I want to let them know that and forward them to the link that was previously submitted (to a URL basically).
How can I do this? My model looks like this so far:
<cfcomponent extends="Model" output="true">
<cffunction name="init">
<cfset validatesPresenceOf( property='linkURL') />
<cfset validatesFormatOf( property='linkURL', type='url', message="Your link isn't a valid URL.") />
<cfset validatesUniquenessOf( property='linkURL') />
</cffunction>
</cfcomponent>
Very basic. The validatesUniquenessOf() works great, but I'd like to do a bit more in my validation logic. If I was doing it without the framework...I'd of course do some standard logic, but I'd like to work the way wheels needs me to.
Thanks again CFWHEELS!
This falls out of the general use case for validatesUniquenessOf(), but there are ways around that using addError and errorsOn.
I would do this in the model:
<cfcomponent extends="Model">
<cffunction name="init">
<cfset validatesPresenceOf( property='linkURL') />
<cfset validatesFormatOf( property='linkURL', type='url', message="Your link isn't a valid URL.") />
<cfset validate("validateUniqueUrl") />
</cffunction>
<cffunction name="validateUniqueUrl" access="private">
<cfscript>
if (this.exists(where="linkURL='#this.linkURL#'")) {
this.addError(property="linkURL", name="linkExists", message="The link you entered already exists.");
}
</cfscript>
</cffunction>
</cfcomponent>
The reason I would do that is so that you'll have a named error to check for in the controller (called linkExists).
Then in your controller:
<cfcomponent extends="Controller">
<cffunction name="create">
<cfscript>
link = model("link").new(params.link);
local.linkExistsErrors = link.errorsOn(property="linkURL", name="linkExists");
if (link.save()) {
// Whatever you want to do on success
}
else if (ArrayLen(local.linkExistsErrors)) {
flashInsert(error=local.linkExistsErrors[1].message);
Location(url=link.linkURL, addToken=false); // Need to use Location or cflocation for hard-coded URLs
}
else {
// Whatever you want to do on other types of validation failures
}
</cfscript>
</cffunction>
</cfcomponent>
API Resources
errorsOn: http://cfwheels.org/docs/function/errorson
validate: http://cfwheels.org/docs/function/validate
addError: http://cfwheels.org/docs/function/adderror
Could you not supply the submitted url as a link in the message attribute of validatesUniquenessOf()? This way the user will get the error and will be able to follow the link in the message. Otherwise, I think you will need to use cflocation to send the user to the linUrl value if the validatesUniquenessOf() functions returns false.

Coldfusion 8 - Best practice for DAO AJAX access?

I'm fairly new to using OO concepts such as DAO's, Gateways etc and I'm trying to work out the best way to implement an AJAX accessible CFC whilst trying not to repeat lot's of code.
I have the following DAO which holds CRUD methods for my DB table and takes the application DSN as an argument in it's constructor:
<cfcomponent name="property_imageDAO" displayname="property_imageDAO" output="false" hint="">
<!--- pseudo constructor --->
<cfscript>
variables.dsn = application.dsn;
</cfscript>
<!--- constructor --->
<cffunction name="init" access="public" output="false" returntype="any"
hint="Constructor for this CFC">
<!--- take DSN as argument --->
<cfargument name="dsn" type="string" required="true" hint="The datasource name" />
<!--- put dsn in variables scope so we can use it throughout the CFC --->
<cfset variables.dsn = arguments.dsn />
<!--- return this CFC --->
<cfreturn this />
</cffunction>
<!--- CRUD methods (create, read, update, delete) --->
<!--- CREATE: inserts a new property_image into the database --->
<cffunction name="createRecord" access="remote" output="true"
hint="Creates a new property_image record and returns a struct containing a boolean (success) indicating the success or
failure of the operation, an id (id), and a string (message) containing a message">
<!--- take property_image bean as argument --->
<cfargument name="property_image" type="any" required="true" />
<!--- initialize variables --->
<cfset var results = StructNew() />
<cfset var qInsertproperty_image = 0 />
<!--- defaults --->
<cfset results.success = true />
<cfset results.message = "The record was inserted successfully." />
<!--- insert the property_image --->
<cftry>
<cfquery name="qInsertproperty_image" datasource="#variables.dsn#">
INSERT INTO property_image (
name,
alt
)
VALUES (
<cfqueryparam value="#arguments.property_image.getname()#" cfsqltype="cf_sql_varchar" />,
<cfqueryparam value="#arguments.property_image.getalt()#" cfsqltype="cf_sql_varchar" />
)
</cfquery>
<cfcatch type="database">
<cfset results.success = false />
<cfset results.message = "Inserting the record failed. The error details if available are as follows: " & CFCATCH.Detail />
</cfcatch>
</cftry>
<!--- return the struct --->
<cfreturn StructCopy(results) />
</cffunction>
Should I add functionality to this DAO to make it AJAX accessible or should I create another DAO specifically for remote access?
Thanks
I think there's probably as many variations of a solution to this as there will be people to suggest one, but here's one take on it.
I'd not openthe DAO up to REMOTE access, I'd leave that as PACKAGE (and only be accessed by some business object in the same package). I'd also have some sort of facade sitting in front of that lot which handles the remote calls, as well as things like validating whether the call coming in remotely IS ALLOWED to be making the call. You don't want just anyone sticking stuff in your DB! The facade should deal with the auth side of things, then if all OK pass the call to the business object which then uses the DAO to access the DB.
I would not have that try/catch stuff in your DAO either. The best way to notify calling code that something went wrong is for an exception to be thrown. Then the calling code can decide what to do with it (whether to deal with it some way, ignore it, or re-bubble it to the sitewide error handling).
I suggest looking at ColdSpring and its ability to create remote proxies and its ability to secure them using AOP. This is a great way to expose only certain parts of a CFC to remote access and to control by whom they are accessed.
Both topics are covered in the ColdSpring Quick Start guide: http://www.coldspringframework.org/coldspring/examples/quickstart/
I also did a presentation on how to do this. You can see a recording here: http://textiles.online.ncsu.edu/online/Viewer/?peid=a4227aeb1ad84fa89eeb3817f075af5b1d

Resources