I am attempting to do the following
1) Stop a user from logging into the website when a flag is set that indicates maintenance in progress
2) If a user has already logged in, and subsequently the maintenance flag set to true, then the very next time the user hits the website, they are denied access
When I run the code under VFP umbrella, i.e. go into foxpro the process works
When I run it as an EXE outside the umbrella, it fails. I'm a little perplexed as how to trouble shoot this. Any help is appreciated.
I sub classed wwprocess and wwusersercurity with the following
DEFINE CLASS mcgBaseProcess AS wwProcesscAuthenticationMode = "UserSecurity"
cAuthenticationUserSecurityClass = "mcgUserSecurity"
lEnableSessionState = .T.* -- works on development computer not on server ... always shows maintenenace in progress
FUNCTION OnProcessInit
LOCAL lcPageFile
lcSessionCookieName = THIS.cSessionKey
lcSessionId = THIS.oRequest.GetCookie(lcSessionCookieName)
lcSessionCookieName = THIS.cSessionKey
Process.InitSession("")
session = this.osession
lcPageFile = lower(JUSTFNAME(Request.GetPhysicalPath()))IF ! INLIST(UPPER(lcPageFile), 'LOGIN', 'LOGON', 'DEFAULT')
llvalidsession = session.IsValidSession(lcSessionId)
IF ! llValidSession
session.endSession(lcSessionid)
ENDIF
ENDIFIF !This.Authenticate() && If they're not, a login form should appear
RETURN
ENDIFENDFUNC
FUNCTION OnAuthenticateUser(tcUserName, tcPassword, tcErrorMsg)
This.oUserSecurity = CREATEOBJECT(This.cAuthenticationUserSecurityClass)
IF !This.oUserSecurity.Authenticate(m.tcUserName, m.tcPassword)
*** Set lcErrorMsg to pass back via REF parm
tcErrorMsg = this.oUserSecurity.cErrorMsg
IF "expired" $ tcErrorMsg
* TODO: Response.Redirect(Server.oConfig.cRedirectSignupTo)
ENDIFRETURN .F.
ELSE
session.setSessionVar('username', tcusername)
session.setSessionvar('subky',this.oUserSECURITY.Ouser.subky)
session.setSessionvar('admin',this.oUSERSECURITY.oUser.Admin)
session.setSessionvar('admin2',IIF( this.oUSERSECURITY.oUser.Admin = .t., 'yes', 'no'))
session.setSessionvar('email',this.oUSERSECURITY.oUser.email)
session.setSessionvar('logoncount',this.oUSERSECURITY.oUser.logoncount)
session.setSessionvar('sessionid',session.getsessionid())
This.cAuthenticatedUser = tcUserName
Response.Redirect('mcglogin.mcg')
ENDIF
RETURN .T.
ENDFUNC************************************************************************
* wwWebPage :: Authenticate
****************************************
*** Function: Handles authenticating of a specific user.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION Authenticate(lcValidUserName,lcErrorMessage,llNoForcedLogin)
LOCAL lcUsername, lcPassword, lcErrorMsg, loLogin, lcHtml, lcCurrentUrl, l_lMaintenance
IF EMPTY(lcValidUserName)
lcValidUserName = ""
ENDIF
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()DO CASE
*** Logout: Note only works with UserSecurity. Basic cannot be undone
CASE UPPER(lcValidUserName) = "LOGOUT"
THIS.lEnableSessionState = .T.
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,"")
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .t.
CASE THIS.cAuthenticationMode == "UserSecurity" OR ;
this.cAuthenticationMode == "Custom"*** Require Session State
this.lEnableSessionState = .T.*** Check and see if we already authenticated
lcUserName = Session.GetSessionVar(this.cAuthenticationUserSecurityKey)
IF !EMPTY(lcUserName) AND ! odataUtils.IsMaintenanceInProgress()
*** Yup - already authenticated return .T.
this.cAuthenticatedUser = lcUsername
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
ENDIFIF llNoForcedLogin
RETURN .F.
ENDIF
lcCurrentUrl = Request.GetCurrentUrl()
lcErrorMsg = ""
lcUsername = ""
lcPassword = ""
*** Only try to authenticate on Postback
IF Request.IsPostBack()
*** Retrieve the default form field values
lcUsername = Request.Form("WebLogin_txtUsername")
lcPassword = Request.Form("WebLogin_txtPassword")
*** Check and see whether this user is authenticated
IF this.OnAuthenticateUser(lcUserName,lcPassword,@lcErrorMsg)
*** Assign auth user and write into Session
IF EMPTY(this.cAuthenticatedUser)
this.cAuthenticatedUser = lcUsername
ENDIF
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,lcUsername)
*** Override Page setting so it doesn't
*** look like a Postback after validation
IF VARTYPE(__WebPage) = "O"
__WebPage.IsPostBack = .F.
ENDIF
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
*RETURN .T.
ENDIF
*** If we failed to auth redisplay login
*** with an error message
ENDIF
*** Display a generic login form on a GET operation
IF EMPTY(Response.cStyleSheet)
Response.cStyleSheet = this.ResolveUrl("~/westwind.css")
ENDIF
*** You display a custom page by creating a template and showing that
*** instead. Just make sure the page has WebLogin_txtUsername and WebLogin_txtPassword
*** Form variables
*** Respone.ExpandTemplate( this.oConfig.cHtmlPagePath + "CustomLogin.wc")
*** Generate Page output
LOCAL loLogin as wwWebLogin
loLogin = CREATEOBJECT([WWC_WEBLOGINCONTROL])
loLogin.Id = "WebLogin"
loLogin.width = "350px"
loLogin.Username = lcUsername
* -- RMM
loLogin.UserMessage = IIF( l_lMaintenance, 'Sorry for the inconvenience. Try back later.' , this.cAuthenticationUserMessage)*loLogin.UserMessage = this.cAuthenticationUserMessage
loLogin.Center = .t.
loLogin.UserSecurityClass = this.cAuthenticationUserSecurityClass
loLogin.ErrorMessage = IIF( EMPTY(lcErrorMsg), IIF( l_lMaintenance, 'Maintenance in Progress',space(0)), lcErrorMsg)
lcHtml = loLogin.Render()
lcHtml = "<br /><br />" + CRLF +;
"<form method='POST' action=''>" + CRLF + ;
lcHtml + CRLF +;
"</form>"THIS.StandardPage("Case Works Account Login",lcHtml)
CASE THIS.cAuthenticationMode = "Basic"
IF EMPTY(lcValidUsername)
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIFIF UPPER(lcValidUserName) = "WCINI"
lcValidUsername = Request.GetWcIniValue("AdminAccount")
IF EMPTY(lcValidUsername)
* - RMM
IF ! l_lmaintenance
RETURN .T.
ENDIF
* Return .t.
ENDIF
ENDIF*** Retrieve the actual username
lcUsername = JUSTSTEM( Request.GetAuthenticatedUser() )
this.cAuthenticatedUser = lcUsernameIF !EMPTY(lcUserName)
IF UPPER(lcValidUserName) == "ANY" AND !EMPTY(lcUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF UPPER(lcUsername) == UPPER(lcValidUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF AT(",",lcValidUserName) > 0
lcValidUserName = "," + lcValidUsername + ","
IF AT(","+lcUserName+",",lcValidUserName) > 0
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIF
ENDIF
ENDIF
IF llNoForcedLogin
RETURN .f.
ENDIFthis.cAuthenticatedUser = lcUsername
*** Prompt for Authentication from the user
* -- RMM
IF ! l_lMaitenance
Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
endif
*Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
ENDCASE
RETURN .F.
ENDFUNC
* wwProcess :: Authenticate
ENDDEFINE
I also sub classed wwUserSercurity
DEFINE CLASS mcgUserSecurity AS wwUserSecuritycerrormsg = "You Owe Us Money."
*-- Alias of the user file.
calias = "subscriber"*-- Filename for the user file.
cfilename = "subscriber"************************************************************************
* wwUserSecurity :: GetUser
****************************************
*** Function: Retrieves a user data record object without affecting the
*** currently active user. Pass in a PK or Username and Password.
*** GetUser always returns a record unless the record cannot be found. Use Logon to check for Active and Expired status.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION GetUser(lcPK, lcPassword)*** lcPK could also be the username
THIS.lError = .F.IF .NOT. THIS.OPEN()
THIS.lError = .T.
THIS.SetError("Couldn't open "+THIS.cfilename)
RETURN .F.
ENDIFTHIS.lNewUser = .F.
*** Allow retrieving a blank user record
IF lcPK="BLANK"
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .T.
ENDIF* -- RMM 2/21/13
SELECT (THIS.cfilename)
*** If 2 parameters we have username and password
*** Otherwise it's a PK
IF PCOUNT()>1
IF THIS.lCaseSensitive
LOCATE FOR username=PADR(lcPK, LEN(username)) .AND. ;
PASSWORD = PADR(lcPassword, LEN(password))
ELSE
LOCATE FOR LOWER(username)=PADR(LOWER(lcPK), LEN(username)) .AND. ;
LOWER(PASSWORD) = PADR(LOWER(lcPassword), LEN(password))
ENDIF
* -- RMM
l_lFound = FOUND()
* -- is there maintenance going on?
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()
DO case
CASE l_lFound AND ! l_lMaintenance
SCATTER MEMO NAME THIS.ouser
RETURN .T.
CASE l_lFound AND l_lMaintenance
THIS.SetError("Maintenance In Progress")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.
OTHERWISE
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.ENDCASE
* -- RMM 9/25/14
*!* *** Found: Load the record into THIS.oUser
*!* IF FOUND()
*!* SCATTER MEMO NAME THIS.ouser
*!*
*!* * -- testing how to store varaibles with sessions
*!* RETURN .T.
*!* ENDIF
*!*
*!* *** No match - return blank user object and error message
*!* THIS.SetError("Invalid Password or Username")
*!* SCATTER BLANK MEMO NAME THIS.ouser
*!* RETURN .F.
ENDIF*** A PK was passed in
* LOCATE FOR pk = PADR(lcPK,LEN(pk))
* -- RMM 2/21/13
SELECT (THIS.cfilename)
LOCATE FOR subky = PADR(lcPK,LEN(subky))*** Found: set THIS.oUser record
IF FOUND()
SCATTER MEMO NAME THIS.ouser
RETURN .T.
ENDIF*** No match - blank user record and error message
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouserRETURN .F.
ENDFUNC
* wwUserSecurity :: GetUser
Not sure - code should run identically whether you run from within VFP or an EXE assuming the rest of the environment is set up the same. Make sure you have your paths set up the same and are using the same data paths etc. If unsure run the EXE out of the same folders as the EXE...
+++ Rick ---
Rick,
I am attempting to do the following
1) Stop a user from logging into the website when a flag is set that indicates maintenance in progress
2) If a user has already logged in, and subsequently the maintenance flag set to true, then the very next time the user hits the website, they are denied access
When I run the code under VFP umbrella, i.e. go into foxpro the process works
When I run it as an EXE outside the umbrella, it fails. I'm a little perplexed as how to trouble shoot this. Any help is appreciated.
I sub classed wwprocess and wwusersercurity with the following
DEFINE CLASS mcgBaseProcess AS wwProcesscAuthenticationMode = "UserSecurity"
cAuthenticationUserSecurityClass = "mcgUserSecurity"
lEnableSessionState = .T.* -- works on development computer not on server ... always shows maintenenace in progress
FUNCTION OnProcessInit
LOCAL lcPageFile
lcSessionCookieName = THIS.cSessionKey
lcSessionId = THIS.oRequest.GetCookie(lcSessionCookieName)
lcSessionCookieName = THIS.cSessionKey
Process.InitSession("")
session = this.osession
lcPageFile = lower(JUSTFNAME(Request.GetPhysicalPath()))IF ! INLIST(UPPER(lcPageFile), 'LOGIN', 'LOGON', 'DEFAULT')
llvalidsession = session.IsValidSession(lcSessionId)
IF ! llValidSession
session.endSession(lcSessionid)
ENDIF
ENDIFIF !This.Authenticate() && If they're not, a login form should appear
RETURN
ENDIFENDFUNC
FUNCTION OnAuthenticateUser(tcUserName, tcPassword, tcErrorMsg)
This.oUserSecurity = CREATEOBJECT(This.cAuthenticationUserSecurityClass)
IF !This.oUserSecurity.Authenticate(m.tcUserName, m.tcPassword)
*** Set lcErrorMsg to pass back via REF parm
tcErrorMsg = this.oUserSecurity.cErrorMsg
IF "expired" $ tcErrorMsg
* TODO: Response.Redirect(Server.oConfig.cRedirectSignupTo)
ENDIFRETURN .F.
ELSE
session.setSessionVar('username', tcusername)
session.setSessionvar('subky',this.oUserSECURITY.Ouser.subky)
session.setSessionvar('admin',this.oUSERSECURITY.oUser.Admin)
session.setSessionvar('admin2',IIF( this.oUSERSECURITY.oUser.Admin = .t., 'yes', 'no'))
session.setSessionvar('email',this.oUSERSECURITY.oUser.email)
session.setSessionvar('logoncount',this.oUSERSECURITY.oUser.logoncount)
session.setSessionvar('sessionid',session.getsessionid())
This.cAuthenticatedUser = tcUserName
Response.Redirect('mcglogin.mcg')
ENDIF
RETURN .T.
ENDFUNC************************************************************************
* wwWebPage :: Authenticate
****************************************
*** Function: Handles authenticating of a specific user.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION Authenticate(lcValidUserName,lcErrorMessage,llNoForcedLogin)
LOCAL lcUsername, lcPassword, lcErrorMsg, loLogin, lcHtml, lcCurrentUrl, l_lMaintenance
IF EMPTY(lcValidUserName)
lcValidUserName = ""
ENDIF
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()DO CASE
*** Logout: Note only works with UserSecurity. Basic cannot be undone
CASE UPPER(lcValidUserName) = "LOGOUT"
THIS.lEnableSessionState = .T.
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,"")
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .t.
CASE THIS.cAuthenticationMode == "UserSecurity" OR ;
this.cAuthenticationMode == "Custom"*** Require Session State
this.lEnableSessionState = .T.*** Check and see if we already authenticated
lcUserName = Session.GetSessionVar(this.cAuthenticationUserSecurityKey)
IF !EMPTY(lcUserName) AND ! odataUtils.IsMaintenanceInProgress()
*** Yup - already authenticated return .T.
this.cAuthenticatedUser = lcUsername
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
ENDIFIF llNoForcedLogin
RETURN .F.
ENDIF
lcCurrentUrl = Request.GetCurrentUrl()
lcErrorMsg = ""
lcUsername = ""
lcPassword = ""
*** Only try to authenticate on Postback
IF Request.IsPostBack()
*** Retrieve the default form field values
lcUsername = Request.Form("WebLogin_txtUsername")
lcPassword = Request.Form("WebLogin_txtPassword")
*** Check and see whether this user is authenticated
IF this.OnAuthenticateUser(lcUserName,lcPassword,@lcErrorMsg)
*** Assign auth user and write into Session
IF EMPTY(this.cAuthenticatedUser)
this.cAuthenticatedUser = lcUsername
ENDIF
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,lcUsername)
*** Override Page setting so it doesn't
*** look like a Postback after validation
IF VARTYPE(__WebPage) = "O"
__WebPage.IsPostBack = .F.
ENDIF
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
*RETURN .T.
ENDIF
*** If we failed to auth redisplay login
*** with an error message
ENDIF
*** Display a generic login form on a GET operation
IF EMPTY(Response.cStyleSheet)
Response.cStyleSheet = this.ResolveUrl("~/westwind.css")
ENDIF
*** You display a custom page by creating a template and showing that
*** instead. Just make sure the page has WebLogin_txtUsername and WebLogin_txtPassword
*** Form variables
*** Respone.ExpandTemplate( this.oConfig.cHtmlPagePath + "CustomLogin.wc")
*** Generate Page output
LOCAL loLogin as wwWebLogin
loLogin = CREATEOBJECT([WWC_WEBLOGINCONTROL])
loLogin.Id = "WebLogin"
loLogin.width = "350px"
loLogin.Username = lcUsername
* -- RMM
loLogin.UserMessage = IIF( l_lMaintenance, 'Sorry for the inconvenience. Try back later.' , this.cAuthenticationUserMessage)*loLogin.UserMessage = this.cAuthenticationUserMessage
loLogin.Center = .t.
loLogin.UserSecurityClass = this.cAuthenticationUserSecurityClass
loLogin.ErrorMessage = IIF( EMPTY(lcErrorMsg), IIF( l_lMaintenance, 'Maintenance in Progress',space(0)), lcErrorMsg)
lcHtml = loLogin.Render()
lcHtml = "<br /><br />" + CRLF +;
"<form method='POST' action=''>" + CRLF + ;
lcHtml + CRLF +;
"</form>"THIS.StandardPage("Case Works Account Login",lcHtml)
CASE THIS.cAuthenticationMode = "Basic"
IF EMPTY(lcValidUsername)
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIFIF UPPER(lcValidUserName) = "WCINI"
lcValidUsername = Request.GetWcIniValue("AdminAccount")
IF EMPTY(lcValidUsername)
* - RMM
IF ! l_lmaintenance
RETURN .T.
ENDIF
* Return .t.
ENDIF
ENDIF*** Retrieve the actual username
lcUsername = JUSTSTEM( Request.GetAuthenticatedUser() )
this.cAuthenticatedUser = lcUsernameIF !EMPTY(lcUserName)
IF UPPER(lcValidUserName) == "ANY" AND !EMPTY(lcUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF UPPER(lcUsername) == UPPER(lcValidUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF AT(",",lcValidUserName) > 0
lcValidUserName = "," + lcValidUsername + ","
IF AT(","+lcUserName+",",lcValidUserName) > 0
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIF
ENDIF
ENDIF
IF llNoForcedLogin
RETURN .f.
ENDIFthis.cAuthenticatedUser = lcUsername
*** Prompt for Authentication from the user
* -- RMM
IF ! l_lMaitenance
Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
endif
*Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
ENDCASE
RETURN .F.
ENDFUNC
* wwProcess :: Authenticate
ENDDEFINE
I also sub classed wwUserSercurity
DEFINE CLASS mcgUserSecurity AS wwUserSecuritycerrormsg = "You Owe Us Money."
*-- Alias of the user file.
calias = "subscriber"*-- Filename for the user file.
cfilename = "subscriber"************************************************************************
* wwUserSecurity :: GetUser
****************************************
*** Function: Retrieves a user data record object without affecting the
*** currently active user. Pass in a PK or Username and Password.
*** GetUser always returns a record unless the record cannot be found. Use Logon to check for Active and Expired status.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION GetUser(lcPK, lcPassword)*** lcPK could also be the username
THIS.lError = .F.IF .NOT. THIS.OPEN()
THIS.lError = .T.
THIS.SetError("Couldn't open "+THIS.cfilename)
RETURN .F.
ENDIFTHIS.lNewUser = .F.
*** Allow retrieving a blank user record
IF lcPK="BLANK"
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .T.
ENDIF* -- RMM 2/21/13
SELECT (THIS.cfilename)
*** If 2 parameters we have username and password
*** Otherwise it's a PK
IF PCOUNT()>1
IF THIS.lCaseSensitive
LOCATE FOR username=PADR(lcPK, LEN(username)) .AND. ;
PASSWORD = PADR(lcPassword, LEN(password))
ELSE
LOCATE FOR LOWER(username)=PADR(LOWER(lcPK), LEN(username)) .AND. ;
LOWER(PASSWORD) = PADR(LOWER(lcPassword), LEN(password))
ENDIF
* -- RMM
l_lFound = FOUND()
* -- is there maintenance going on?
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()
DO case
CASE l_lFound AND ! l_lMaintenance
SCATTER MEMO NAME THIS.ouser
RETURN .T.
CASE l_lFound AND l_lMaintenance
THIS.SetError("Maintenance In Progress")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.
OTHERWISE
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.ENDCASE
* -- RMM 9/25/14
*!* *** Found: Load the record into THIS.oUser
*!* IF FOUND()
*!* SCATTER MEMO NAME THIS.ouser
*!*
*!* * -- testing how to store varaibles with sessions
*!* RETURN .T.
*!* ENDIF
*!*
*!* *** No match - return blank user object and error message
*!* THIS.SetError("Invalid Password or Username")
*!* SCATTER BLANK MEMO NAME THIS.ouser
*!* RETURN .F.
ENDIF*** A PK was passed in
* LOCATE FOR pk = PADR(lcPK,LEN(pk))
* -- RMM 2/21/13
SELECT (THIS.cfilename)
LOCATE FOR subky = PADR(lcPK,LEN(subky))*** Found: set THIS.oUser record
IF FOUND()
SCATTER MEMO NAME THIS.ouser
RETURN .T.
ENDIF*** No match - blank user record and error message
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouserRETURN .F.
ENDFUNC
* wwUserSecurity :: GetUser
West Wind Technologies
Making waves on the Web
from Maui
IF RLOCK("mcgInfo")
UNLOCK IN mcgInfo
ENDIF
Note I didn't lock the record ... but somehow FoxPro was not refreshing the table. Also, after trying different mechanisms, including many hours of nonsense .... this simple code fixed it! Can't take credit for getting this in play, Paul Mrozowski suggested this ... viola!
Roy,
Not sure - code should run identically whether you run from within VFP or an EXE assuming the rest of the environment is set up the same. Make sure you have your paths set up the same and are using the same data paths etc. If unsure run the EXE out of the same folders as the EXE...
+++ Rick ---
Rick,
I am attempting to do the following
1) Stop a user from logging into the website when a flag is set that indicates maintenance in progress
2) If a user has already logged in, and subsequently the maintenance flag set to true, then the very next time the user hits the website, they are denied access
When I run the code under VFP umbrella, i.e. go into foxpro the process works
When I run it as an EXE outside the umbrella, it fails. I'm a little perplexed as how to trouble shoot this. Any help is appreciated.
I sub classed wwprocess and wwusersercurity with the following
DEFINE CLASS mcgBaseProcess AS wwProcesscAuthenticationMode = "UserSecurity"
cAuthenticationUserSecurityClass = "mcgUserSecurity"
lEnableSessionState = .T.* -- works on development computer not on server ... always shows maintenenace in progress
FUNCTION OnProcessInit
LOCAL lcPageFile
lcSessionCookieName = THIS.cSessionKey
lcSessionId = THIS.oRequest.GetCookie(lcSessionCookieName)
lcSessionCookieName = THIS.cSessionKey
Process.InitSession("")
session = this.osession
lcPageFile = lower(JUSTFNAME(Request.GetPhysicalPath()))IF ! INLIST(UPPER(lcPageFile), 'LOGIN', 'LOGON', 'DEFAULT')
llvalidsession = session.IsValidSession(lcSessionId)
IF ! llValidSession
session.endSession(lcSessionid)
ENDIF
ENDIFIF !This.Authenticate() && If they're not, a login form should appear
RETURN
ENDIFENDFUNC
FUNCTION OnAuthenticateUser(tcUserName, tcPassword, tcErrorMsg)
This.oUserSecurity = CREATEOBJECT(This.cAuthenticationUserSecurityClass)
IF !This.oUserSecurity.Authenticate(m.tcUserName, m.tcPassword)
*** Set lcErrorMsg to pass back via REF parm
tcErrorMsg = this.oUserSecurity.cErrorMsg
IF "expired" $ tcErrorMsg
* TODO: Response.Redirect(Server.oConfig.cRedirectSignupTo)
ENDIFRETURN .F.
ELSE
session.setSessionVar('username', tcusername)
session.setSessionvar('subky',this.oUserSECURITY.Ouser.subky)
session.setSessionvar('admin',this.oUSERSECURITY.oUser.Admin)
session.setSessionvar('admin2',IIF( this.oUSERSECURITY.oUser.Admin = .t., 'yes', 'no'))
session.setSessionvar('email',this.oUSERSECURITY.oUser.email)
session.setSessionvar('logoncount',this.oUSERSECURITY.oUser.logoncount)
session.setSessionvar('sessionid',session.getsessionid())
This.cAuthenticatedUser = tcUserName
Response.Redirect('mcglogin.mcg')
ENDIF
RETURN .T.
ENDFUNC************************************************************************
* wwWebPage :: Authenticate
****************************************
*** Function: Handles authenticating of a specific user.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION Authenticate(lcValidUserName,lcErrorMessage,llNoForcedLogin)
LOCAL lcUsername, lcPassword, lcErrorMsg, loLogin, lcHtml, lcCurrentUrl, l_lMaintenance
IF EMPTY(lcValidUserName)
lcValidUserName = ""
ENDIF
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()DO CASE
*** Logout: Note only works with UserSecurity. Basic cannot be undone
CASE UPPER(lcValidUserName) = "LOGOUT"
THIS.lEnableSessionState = .T.
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,"")
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .t.
CASE THIS.cAuthenticationMode == "UserSecurity" OR ;
this.cAuthenticationMode == "Custom"*** Require Session State
this.lEnableSessionState = .T.*** Check and see if we already authenticated
lcUserName = Session.GetSessionVar(this.cAuthenticationUserSecurityKey)
IF !EMPTY(lcUserName) AND ! odataUtils.IsMaintenanceInProgress()
*** Yup - already authenticated return .T.
this.cAuthenticatedUser = lcUsername
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
ENDIFIF llNoForcedLogin
RETURN .F.
ENDIF
lcCurrentUrl = Request.GetCurrentUrl()
lcErrorMsg = ""
lcUsername = ""
lcPassword = ""
*** Only try to authenticate on Postback
IF Request.IsPostBack()
*** Retrieve the default form field values
lcUsername = Request.Form("WebLogin_txtUsername")
lcPassword = Request.Form("WebLogin_txtPassword")
*** Check and see whether this user is authenticated
IF this.OnAuthenticateUser(lcUserName,lcPassword,@lcErrorMsg)
*** Assign auth user and write into Session
IF EMPTY(this.cAuthenticatedUser)
this.cAuthenticatedUser = lcUsername
ENDIF
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,lcUsername)
*** Override Page setting so it doesn't
*** look like a Postback after validation
IF VARTYPE(__WebPage) = "O"
__WebPage.IsPostBack = .F.
ENDIF
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
*RETURN .T.
ENDIF
*** If we failed to auth redisplay login
*** with an error message
ENDIF
*** Display a generic login form on a GET operation
IF EMPTY(Response.cStyleSheet)
Response.cStyleSheet = this.ResolveUrl("~/westwind.css")
ENDIF
*** You display a custom page by creating a template and showing that
*** instead. Just make sure the page has WebLogin_txtUsername and WebLogin_txtPassword
*** Form variables
*** Respone.ExpandTemplate( this.oConfig.cHtmlPagePath + "CustomLogin.wc")
*** Generate Page output
LOCAL loLogin as wwWebLogin
loLogin = CREATEOBJECT([WWC_WEBLOGINCONTROL])
loLogin.Id = "WebLogin"
loLogin.width = "350px"
loLogin.Username = lcUsername
* -- RMM
loLogin.UserMessage = IIF( l_lMaintenance, 'Sorry for the inconvenience. Try back later.' , this.cAuthenticationUserMessage)*loLogin.UserMessage = this.cAuthenticationUserMessage
loLogin.Center = .t.
loLogin.UserSecurityClass = this.cAuthenticationUserSecurityClass
loLogin.ErrorMessage = IIF( EMPTY(lcErrorMsg), IIF( l_lMaintenance, 'Maintenance in Progress',space(0)), lcErrorMsg)
lcHtml = loLogin.Render()
lcHtml = "<br /><br />" + CRLF +;
"<form method='POST' action=''>" + CRLF + ;
lcHtml + CRLF +;
"</form>"THIS.StandardPage("Case Works Account Login",lcHtml)
CASE THIS.cAuthenticationMode = "Basic"
IF EMPTY(lcValidUsername)
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIFIF UPPER(lcValidUserName) = "WCINI"
lcValidUsername = Request.GetWcIniValue("AdminAccount")
IF EMPTY(lcValidUsername)
* - RMM
IF ! l_lmaintenance
RETURN .T.
ENDIF
* Return .t.
ENDIF
ENDIF*** Retrieve the actual username
lcUsername = JUSTSTEM( Request.GetAuthenticatedUser() )
this.cAuthenticatedUser = lcUsernameIF !EMPTY(lcUserName)
IF UPPER(lcValidUserName) == "ANY" AND !EMPTY(lcUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF UPPER(lcUsername) == UPPER(lcValidUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF AT(",",lcValidUserName) > 0
lcValidUserName = "," + lcValidUsername + ","
IF AT(","+lcUserName+",",lcValidUserName) > 0
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIF
ENDIF
ENDIF
IF llNoForcedLogin
RETURN .f.
ENDIFthis.cAuthenticatedUser = lcUsername
*** Prompt for Authentication from the user
* -- RMM
IF ! l_lMaitenance
Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
endif
*Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
ENDCASE
RETURN .F.
ENDFUNC
* wwProcess :: Authenticate
ENDDEFINE
I also sub classed wwUserSercurity
DEFINE CLASS mcgUserSecurity AS wwUserSecuritycerrormsg = "You Owe Us Money."
*-- Alias of the user file.
calias = "subscriber"*-- Filename for the user file.
cfilename = "subscriber"************************************************************************
* wwUserSecurity :: GetUser
****************************************
*** Function: Retrieves a user data record object without affecting the
*** currently active user. Pass in a PK or Username and Password.
*** GetUser always returns a record unless the record cannot be found. Use Logon to check for Active and Expired status.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION GetUser(lcPK, lcPassword)*** lcPK could also be the username
THIS.lError = .F.IF .NOT. THIS.OPEN()
THIS.lError = .T.
THIS.SetError("Couldn't open "+THIS.cfilename)
RETURN .F.
ENDIFTHIS.lNewUser = .F.
*** Allow retrieving a blank user record
IF lcPK="BLANK"
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .T.
ENDIF* -- RMM 2/21/13
SELECT (THIS.cfilename)
*** If 2 parameters we have username and password
*** Otherwise it's a PK
IF PCOUNT()>1
IF THIS.lCaseSensitive
LOCATE FOR username=PADR(lcPK, LEN(username)) .AND. ;
PASSWORD = PADR(lcPassword, LEN(password))
ELSE
LOCATE FOR LOWER(username)=PADR(LOWER(lcPK), LEN(username)) .AND. ;
LOWER(PASSWORD) = PADR(LOWER(lcPassword), LEN(password))
ENDIF
* -- RMM
l_lFound = FOUND()
* -- is there maintenance going on?
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()
DO case
CASE l_lFound AND ! l_lMaintenance
SCATTER MEMO NAME THIS.ouser
RETURN .T.
CASE l_lFound AND l_lMaintenance
THIS.SetError("Maintenance In Progress")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.
OTHERWISE
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.ENDCASE
* -- RMM 9/25/14
*!* *** Found: Load the record into THIS.oUser
*!* IF FOUND()
*!* SCATTER MEMO NAME THIS.ouser
*!*
*!* * -- testing how to store varaibles with sessions
*!* RETURN .T.
*!* ENDIF
*!*
*!* *** No match - return blank user object and error message
*!* THIS.SetError("Invalid Password or Username")
*!* SCATTER BLANK MEMO NAME THIS.ouser
*!* RETURN .F.
ENDIF*** A PK was passed in
* LOCATE FOR pk = PADR(lcPK,LEN(pk))
* -- RMM 2/21/13
SELECT (THIS.cfilename)
LOCATE FOR subky = PADR(lcPK,LEN(subky))*** Found: set THIS.oUser record
IF FOUND()
SCATTER MEMO NAME THIS.ouser
RETURN .T.
ENDIF*** No match - blank user record and error message
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouserRETURN .F.
ENDFUNC
* wwUserSecurity :: GetUser
Hmmm... is this a busy site then under load?
RLOCK() will force a buffer refresh and ensure you get current data when READING. Without that it's possible to get stale data from VFP as VFP caches data in memory. RLOCK() will force the buffers to be flushed and any subsequent data access to read again from disk.
There are also other ways such as GO RECNO() on the current table to force buffer refreshes from disk. Lots more info here too: http://fox.wikis.com/wc.dll?Wiki~VFPTableConcurrency
Not exactly the most clear topic, but lots of issues and workarounds suggested there.
I ran into this problem years ago on a very busy site and there we had to instrument all sorts of workarounds to make this work reliably, but this should only happen with FoxPro data. SQL data should be no problem.
+++ Rick ---
Rick ... Got it fixed and believe it or not .... it was FoxPro ... the following code cured it
IF RLOCK("mcgInfo")
UNLOCK IN mcgInfo
ENDIF
Note I didn't lock the record ... but somehow FoxPro was not refreshing the table. Also, after trying different mechanisms, including many hours of nonsense .... this simple code fixed it! Can't take credit for getting this in play, Paul Mrozowski suggested this ... viola!
Roy,
Not sure - code should run identically whether you run from within VFP or an EXE assuming the rest of the environment is set up the same. Make sure you have your paths set up the same and are using the same data paths etc. If unsure run the EXE out of the same folders as the EXE...
+++ Rick ---
Rick,
I am attempting to do the following
1) Stop a user from logging into the website when a flag is set that indicates maintenance in progress
2) If a user has already logged in, and subsequently the maintenance flag set to true, then the very next time the user hits the website, they are denied access
When I run the code under VFP umbrella, i.e. go into foxpro the process works
When I run it as an EXE outside the umbrella, it fails. I'm a little perplexed as how to trouble shoot this. Any help is appreciated.
I sub classed wwprocess and wwusersercurity with the following
DEFINE CLASS mcgBaseProcess AS wwProcesscAuthenticationMode = "UserSecurity"
cAuthenticationUserSecurityClass = "mcgUserSecurity"
lEnableSessionState = .T.* -- works on development computer not on server ... always shows maintenenace in progress
FUNCTION OnProcessInit
LOCAL lcPageFile
lcSessionCookieName = THIS.cSessionKey
lcSessionId = THIS.oRequest.GetCookie(lcSessionCookieName)
lcSessionCookieName = THIS.cSessionKey
Process.InitSession("")
session = this.osession
lcPageFile = lower(JUSTFNAME(Request.GetPhysicalPath()))IF ! INLIST(UPPER(lcPageFile), 'LOGIN', 'LOGON', 'DEFAULT')
llvalidsession = session.IsValidSession(lcSessionId)
IF ! llValidSession
session.endSession(lcSessionid)
ENDIF
ENDIFIF !This.Authenticate() && If they're not, a login form should appear
RETURN
ENDIFENDFUNC
FUNCTION OnAuthenticateUser(tcUserName, tcPassword, tcErrorMsg)
This.oUserSecurity = CREATEOBJECT(This.cAuthenticationUserSecurityClass)
IF !This.oUserSecurity.Authenticate(m.tcUserName, m.tcPassword)
*** Set lcErrorMsg to pass back via REF parm
tcErrorMsg = this.oUserSecurity.cErrorMsg
IF "expired" $ tcErrorMsg
* TODO: Response.Redirect(Server.oConfig.cRedirectSignupTo)
ENDIFRETURN .F.
ELSE
session.setSessionVar('username', tcusername)
session.setSessionvar('subky',this.oUserSECURITY.Ouser.subky)
session.setSessionvar('admin',this.oUSERSECURITY.oUser.Admin)
session.setSessionvar('admin2',IIF( this.oUSERSECURITY.oUser.Admin = .t., 'yes', 'no'))
session.setSessionvar('email',this.oUSERSECURITY.oUser.email)
session.setSessionvar('logoncount',this.oUSERSECURITY.oUser.logoncount)
session.setSessionvar('sessionid',session.getsessionid())
This.cAuthenticatedUser = tcUserName
Response.Redirect('mcglogin.mcg')
ENDIF
RETURN .T.
ENDFUNC************************************************************************
* wwWebPage :: Authenticate
****************************************
*** Function: Handles authenticating of a specific user.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION Authenticate(lcValidUserName,lcErrorMessage,llNoForcedLogin)
LOCAL lcUsername, lcPassword, lcErrorMsg, loLogin, lcHtml, lcCurrentUrl, l_lMaintenance
IF EMPTY(lcValidUserName)
lcValidUserName = ""
ENDIF
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()DO CASE
*** Logout: Note only works with UserSecurity. Basic cannot be undone
CASE UPPER(lcValidUserName) = "LOGOUT"
THIS.lEnableSessionState = .T.
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,"")
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .t.
CASE THIS.cAuthenticationMode == "UserSecurity" OR ;
this.cAuthenticationMode == "Custom"*** Require Session State
this.lEnableSessionState = .T.*** Check and see if we already authenticated
lcUserName = Session.GetSessionVar(this.cAuthenticationUserSecurityKey)
IF !EMPTY(lcUserName) AND ! odataUtils.IsMaintenanceInProgress()
*** Yup - already authenticated return .T.
this.cAuthenticatedUser = lcUsername
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
ENDIFIF llNoForcedLogin
RETURN .F.
ENDIF
lcCurrentUrl = Request.GetCurrentUrl()
lcErrorMsg = ""
lcUsername = ""
lcPassword = ""
*** Only try to authenticate on Postback
IF Request.IsPostBack()
*** Retrieve the default form field values
lcUsername = Request.Form("WebLogin_txtUsername")
lcPassword = Request.Form("WebLogin_txtPassword")
*** Check and see whether this user is authenticated
IF this.OnAuthenticateUser(lcUserName,lcPassword,@lcErrorMsg)
*** Assign auth user and write into Session
IF EMPTY(this.cAuthenticatedUser)
this.cAuthenticatedUser = lcUsername
ENDIF
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,lcUsername)
*** Override Page setting so it doesn't
*** look like a Postback after validation
IF VARTYPE(__WebPage) = "O"
__WebPage.IsPostBack = .F.
ENDIF
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
*RETURN .T.
ENDIF
*** If we failed to auth redisplay login
*** with an error message
ENDIF
*** Display a generic login form on a GET operation
IF EMPTY(Response.cStyleSheet)
Response.cStyleSheet = this.ResolveUrl("~/westwind.css")
ENDIF
*** You display a custom page by creating a template and showing that
*** instead. Just make sure the page has WebLogin_txtUsername and WebLogin_txtPassword
*** Form variables
*** Respone.ExpandTemplate( this.oConfig.cHtmlPagePath + "CustomLogin.wc")
*** Generate Page output
LOCAL loLogin as wwWebLogin
loLogin = CREATEOBJECT([WWC_WEBLOGINCONTROL])
loLogin.Id = "WebLogin"
loLogin.width = "350px"
loLogin.Username = lcUsername
* -- RMM
loLogin.UserMessage = IIF( l_lMaintenance, 'Sorry for the inconvenience. Try back later.' , this.cAuthenticationUserMessage)*loLogin.UserMessage = this.cAuthenticationUserMessage
loLogin.Center = .t.
loLogin.UserSecurityClass = this.cAuthenticationUserSecurityClass
loLogin.ErrorMessage = IIF( EMPTY(lcErrorMsg), IIF( l_lMaintenance, 'Maintenance in Progress',space(0)), lcErrorMsg)
lcHtml = loLogin.Render()
lcHtml = "<br /><br />" + CRLF +;
"<form method='POST' action=''>" + CRLF + ;
lcHtml + CRLF +;
"</form>"THIS.StandardPage("Case Works Account Login",lcHtml)
CASE THIS.cAuthenticationMode = "Basic"
IF EMPTY(lcValidUsername)
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIFIF UPPER(lcValidUserName) = "WCINI"
lcValidUsername = Request.GetWcIniValue("AdminAccount")
IF EMPTY(lcValidUsername)
* - RMM
IF ! l_lmaintenance
RETURN .T.
ENDIF
* Return .t.
ENDIF
ENDIF*** Retrieve the actual username
lcUsername = JUSTSTEM( Request.GetAuthenticatedUser() )
this.cAuthenticatedUser = lcUsernameIF !EMPTY(lcUserName)
IF UPPER(lcValidUserName) == "ANY" AND !EMPTY(lcUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF UPPER(lcUsername) == UPPER(lcValidUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF AT(",",lcValidUserName) > 0
lcValidUserName = "," + lcValidUsername + ","
IF AT(","+lcUserName+",",lcValidUserName) > 0
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIF
ENDIF
ENDIF
IF llNoForcedLogin
RETURN .f.
ENDIFthis.cAuthenticatedUser = lcUsername
*** Prompt for Authentication from the user
* -- RMM
IF ! l_lMaitenance
Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
endif
*Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
ENDCASE
RETURN .F.
ENDFUNC
* wwProcess :: Authenticate
ENDDEFINE
I also sub classed wwUserSercurity
DEFINE CLASS mcgUserSecurity AS wwUserSecuritycerrormsg = "You Owe Us Money."
*-- Alias of the user file.
calias = "subscriber"*-- Filename for the user file.
cfilename = "subscriber"************************************************************************
* wwUserSecurity :: GetUser
****************************************
*** Function: Retrieves a user data record object without affecting the
*** currently active user. Pass in a PK or Username and Password.
*** GetUser always returns a record unless the record cannot be found. Use Logon to check for Active and Expired status.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION GetUser(lcPK, lcPassword)*** lcPK could also be the username
THIS.lError = .F.IF .NOT. THIS.OPEN()
THIS.lError = .T.
THIS.SetError("Couldn't open "+THIS.cfilename)
RETURN .F.
ENDIFTHIS.lNewUser = .F.
*** Allow retrieving a blank user record
IF lcPK="BLANK"
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .T.
ENDIF* -- RMM 2/21/13
SELECT (THIS.cfilename)
*** If 2 parameters we have username and password
*** Otherwise it's a PK
IF PCOUNT()>1
IF THIS.lCaseSensitive
LOCATE FOR username=PADR(lcPK, LEN(username)) .AND. ;
PASSWORD = PADR(lcPassword, LEN(password))
ELSE
LOCATE FOR LOWER(username)=PADR(LOWER(lcPK), LEN(username)) .AND. ;
LOWER(PASSWORD) = PADR(LOWER(lcPassword), LEN(password))
ENDIF
* -- RMM
l_lFound = FOUND()
* -- is there maintenance going on?
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()
DO case
CASE l_lFound AND ! l_lMaintenance
SCATTER MEMO NAME THIS.ouser
RETURN .T.
CASE l_lFound AND l_lMaintenance
THIS.SetError("Maintenance In Progress")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.
OTHERWISE
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.ENDCASE
* -- RMM 9/25/14
*!* *** Found: Load the record into THIS.oUser
*!* IF FOUND()
*!* SCATTER MEMO NAME THIS.ouser
*!*
*!* * -- testing how to store varaibles with sessions
*!* RETURN .T.
*!* ENDIF
*!*
*!* *** No match - return blank user object and error message
*!* THIS.SetError("Invalid Password or Username")
*!* SCATTER BLANK MEMO NAME THIS.ouser
*!* RETURN .F.
ENDIF*** A PK was passed in
* LOCATE FOR pk = PADR(lcPK,LEN(pk))
* -- RMM 2/21/13
SELECT (THIS.cfilename)
LOCATE FOR subky = PADR(lcPK,LEN(subky))*** Found: set THIS.oUser record
IF FOUND()
SCATTER MEMO NAME THIS.ouser
RETURN .T.
ENDIF*** No match - blank user record and error message
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouserRETURN .F.
ENDFUNC
* wwUserSecurity :: GetUser
West Wind Technologies
Making waves on the Web
from Maui
Thanks
Hmmm... is this a busy site then under load?
RLOCK() will force a buffer refresh and ensure you get current data when READING. Without that it's possible to get stale data from VFP as VFP caches data in memory. RLOCK() will force the buffers to be flushed and any subsequent data access to read again from disk.
There are also other ways such as GO RECNO() on the current table to force buffer refreshes from disk. Lots more info here too: http://fox.wikis.com/wc.dll?Wiki~VFPTableConcurrency
Not exactly the most clear topic, but lots of issues and workarounds suggested there.
I ran into this problem years ago on a very busy site and there we had to instrument all sorts of workarounds to make this work reliably, but this should only happen with FoxPro data. SQL data should be no problem.
+++ Rick ---
Rick ... Got it fixed and believe it or not .... it was FoxPro ... the following code cured it
IF RLOCK("mcgInfo")
UNLOCK IN mcgInfo
ENDIF
Note I didn't lock the record ... but somehow FoxPro was not refreshing the table. Also, after trying different mechanisms, including many hours of nonsense .... this simple code fixed it! Can't take credit for getting this in play, Paul Mrozowski suggested this ... viola!
Roy,
Not sure - code should run identically whether you run from within VFP or an EXE assuming the rest of the environment is set up the same. Make sure you have your paths set up the same and are using the same data paths etc. If unsure run the EXE out of the same folders as the EXE...
+++ Rick ---
Rick,
I am attempting to do the following
1) Stop a user from logging into the website when a flag is set that indicates maintenance in progress
2) If a user has already logged in, and subsequently the maintenance flag set to true, then the very next time the user hits the website, they are denied access
When I run the code under VFP umbrella, i.e. go into foxpro the process works
When I run it as an EXE outside the umbrella, it fails. I'm a little perplexed as how to trouble shoot this. Any help is appreciated.
I sub classed wwprocess and wwusersercurity with the following
DEFINE CLASS mcgBaseProcess AS wwProcesscAuthenticationMode = "UserSecurity"
cAuthenticationUserSecurityClass = "mcgUserSecurity"
lEnableSessionState = .T.* -- works on development computer not on server ... always shows maintenenace in progress
FUNCTION OnProcessInit
LOCAL lcPageFile
lcSessionCookieName = THIS.cSessionKey
lcSessionId = THIS.oRequest.GetCookie(lcSessionCookieName)
lcSessionCookieName = THIS.cSessionKey
Process.InitSession("")
session = this.osession
lcPageFile = lower(JUSTFNAME(Request.GetPhysicalPath()))IF ! INLIST(UPPER(lcPageFile), 'LOGIN', 'LOGON', 'DEFAULT')
llvalidsession = session.IsValidSession(lcSessionId)
IF ! llValidSession
session.endSession(lcSessionid)
ENDIF
ENDIFIF !This.Authenticate() && If they're not, a login form should appear
RETURN
ENDIFENDFUNC
FUNCTION OnAuthenticateUser(tcUserName, tcPassword, tcErrorMsg)
This.oUserSecurity = CREATEOBJECT(This.cAuthenticationUserSecurityClass)
IF !This.oUserSecurity.Authenticate(m.tcUserName, m.tcPassword)
*** Set lcErrorMsg to pass back via REF parm
tcErrorMsg = this.oUserSecurity.cErrorMsg
IF "expired" $ tcErrorMsg
* TODO: Response.Redirect(Server.oConfig.cRedirectSignupTo)
ENDIFRETURN .F.
ELSE
session.setSessionVar('username', tcusername)
session.setSessionvar('subky',this.oUserSECURITY.Ouser.subky)
session.setSessionvar('admin',this.oUSERSECURITY.oUser.Admin)
session.setSessionvar('admin2',IIF( this.oUSERSECURITY.oUser.Admin = .t., 'yes', 'no'))
session.setSessionvar('email',this.oUSERSECURITY.oUser.email)
session.setSessionvar('logoncount',this.oUSERSECURITY.oUser.logoncount)
session.setSessionvar('sessionid',session.getsessionid())
This.cAuthenticatedUser = tcUserName
Response.Redirect('mcglogin.mcg')
ENDIF
RETURN .T.
ENDFUNC************************************************************************
* wwWebPage :: Authenticate
****************************************
*** Function: Handles authenticating of a specific user.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION Authenticate(lcValidUserName,lcErrorMessage,llNoForcedLogin)
LOCAL lcUsername, lcPassword, lcErrorMsg, loLogin, lcHtml, lcCurrentUrl, l_lMaintenance
IF EMPTY(lcValidUserName)
lcValidUserName = ""
ENDIF
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()DO CASE
*** Logout: Note only works with UserSecurity. Basic cannot be undone
CASE UPPER(lcValidUserName) = "LOGOUT"
THIS.lEnableSessionState = .T.
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,"")
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .t.
CASE THIS.cAuthenticationMode == "UserSecurity" OR ;
this.cAuthenticationMode == "Custom"*** Require Session State
this.lEnableSessionState = .T.*** Check and see if we already authenticated
lcUserName = Session.GetSessionVar(this.cAuthenticationUserSecurityKey)
IF !EMPTY(lcUserName) AND ! odataUtils.IsMaintenanceInProgress()
*** Yup - already authenticated return .T.
this.cAuthenticatedUser = lcUsername
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
ENDIFIF llNoForcedLogin
RETURN .F.
ENDIF
lcCurrentUrl = Request.GetCurrentUrl()
lcErrorMsg = ""
lcUsername = ""
lcPassword = ""
*** Only try to authenticate on Postback
IF Request.IsPostBack()
*** Retrieve the default form field values
lcUsername = Request.Form("WebLogin_txtUsername")
lcPassword = Request.Form("WebLogin_txtPassword")
*** Check and see whether this user is authenticated
IF this.OnAuthenticateUser(lcUserName,lcPassword,@lcErrorMsg)
*** Assign auth user and write into Session
IF EMPTY(this.cAuthenticatedUser)
this.cAuthenticatedUser = lcUsername
ENDIF
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,lcUsername)
*** Override Page setting so it doesn't
*** look like a Postback after validation
IF VARTYPE(__WebPage) = "O"
__WebPage.IsPostBack = .F.
ENDIF
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
*RETURN .T.
ENDIF
*** If we failed to auth redisplay login
*** with an error message
ENDIF
*** Display a generic login form on a GET operation
IF EMPTY(Response.cStyleSheet)
Response.cStyleSheet = this.ResolveUrl("~/westwind.css")
ENDIF
*** You display a custom page by creating a template and showing that
*** instead. Just make sure the page has WebLogin_txtUsername and WebLogin_txtPassword
*** Form variables
*** Respone.ExpandTemplate( this.oConfig.cHtmlPagePath + "CustomLogin.wc")
*** Generate Page output
LOCAL loLogin as wwWebLogin
loLogin = CREATEOBJECT([WWC_WEBLOGINCONTROL])
loLogin.Id = "WebLogin"
loLogin.width = "350px"
loLogin.Username = lcUsername
* -- RMM
loLogin.UserMessage = IIF( l_lMaintenance, 'Sorry for the inconvenience. Try back later.' , this.cAuthenticationUserMessage)*loLogin.UserMessage = this.cAuthenticationUserMessage
loLogin.Center = .t.
loLogin.UserSecurityClass = this.cAuthenticationUserSecurityClass
loLogin.ErrorMessage = IIF( EMPTY(lcErrorMsg), IIF( l_lMaintenance, 'Maintenance in Progress',space(0)), lcErrorMsg)
lcHtml = loLogin.Render()
lcHtml = "<br /><br />" + CRLF +;
"<form method='POST' action=''>" + CRLF + ;
lcHtml + CRLF +;
"</form>"THIS.StandardPage("Case Works Account Login",lcHtml)
CASE THIS.cAuthenticationMode = "Basic"
IF EMPTY(lcValidUsername)
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIFIF UPPER(lcValidUserName) = "WCINI"
lcValidUsername = Request.GetWcIniValue("AdminAccount")
IF EMPTY(lcValidUsername)
* - RMM
IF ! l_lmaintenance
RETURN .T.
ENDIF
* Return .t.
ENDIF
ENDIF*** Retrieve the actual username
lcUsername = JUSTSTEM( Request.GetAuthenticatedUser() )
this.cAuthenticatedUser = lcUsernameIF !EMPTY(lcUserName)
IF UPPER(lcValidUserName) == "ANY" AND !EMPTY(lcUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF UPPER(lcUsername) == UPPER(lcValidUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF AT(",",lcValidUserName) > 0
lcValidUserName = "," + lcValidUsername + ","
IF AT(","+lcUserName+",",lcValidUserName) > 0
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIF
ENDIF
ENDIF
IF llNoForcedLogin
RETURN .f.
ENDIFthis.cAuthenticatedUser = lcUsername
*** Prompt for Authentication from the user
* -- RMM
IF ! l_lMaitenance
Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
endif
*Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
ENDCASE
RETURN .F.
ENDFUNC
* wwProcess :: Authenticate
ENDDEFINE
I also sub classed wwUserSercurity
DEFINE CLASS mcgUserSecurity AS wwUserSecuritycerrormsg = "You Owe Us Money."
*-- Alias of the user file.
calias = "subscriber"*-- Filename for the user file.
cfilename = "subscriber"************************************************************************
* wwUserSecurity :: GetUser
****************************************
*** Function: Retrieves a user data record object without affecting the
*** currently active user. Pass in a PK or Username and Password.
*** GetUser always returns a record unless the record cannot be found. Use Logon to check for Active and Expired status.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION GetUser(lcPK, lcPassword)*** lcPK could also be the username
THIS.lError = .F.IF .NOT. THIS.OPEN()
THIS.lError = .T.
THIS.SetError("Couldn't open "+THIS.cfilename)
RETURN .F.
ENDIFTHIS.lNewUser = .F.
*** Allow retrieving a blank user record
IF lcPK="BLANK"
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .T.
ENDIF* -- RMM 2/21/13
SELECT (THIS.cfilename)
*** If 2 parameters we have username and password
*** Otherwise it's a PK
IF PCOUNT()>1
IF THIS.lCaseSensitive
LOCATE FOR username=PADR(lcPK, LEN(username)) .AND. ;
PASSWORD = PADR(lcPassword, LEN(password))
ELSE
LOCATE FOR LOWER(username)=PADR(LOWER(lcPK), LEN(username)) .AND. ;
LOWER(PASSWORD) = PADR(LOWER(lcPassword), LEN(password))
ENDIF
* -- RMM
l_lFound = FOUND()
* -- is there maintenance going on?
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()
DO case
CASE l_lFound AND ! l_lMaintenance
SCATTER MEMO NAME THIS.ouser
RETURN .T.
CASE l_lFound AND l_lMaintenance
THIS.SetError("Maintenance In Progress")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.
OTHERWISE
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.ENDCASE
* -- RMM 9/25/14
*!* *** Found: Load the record into THIS.oUser
*!* IF FOUND()
*!* SCATTER MEMO NAME THIS.ouser
*!*
*!* * -- testing how to store varaibles with sessions
*!* RETURN .T.
*!* ENDIF
*!*
*!* *** No match - return blank user object and error message
*!* THIS.SetError("Invalid Password or Username")
*!* SCATTER BLANK MEMO NAME THIS.ouser
*!* RETURN .F.
ENDIF*** A PK was passed in
* LOCATE FOR pk = PADR(lcPK,LEN(pk))
* -- RMM 2/21/13
SELECT (THIS.cfilename)
LOCATE FOR subky = PADR(lcPK,LEN(subky))*** Found: set THIS.oUser record
IF FOUND()
SCATTER MEMO NAME THIS.ouser
RETURN .T.
ENDIF*** No match - blank user record and error message
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouserRETURN .F.
ENDFUNC
* wwUserSecurity :: GetUser
Another thing that's more reliable yet is what I do in the wwSession logic:
THIS.OpenTable()** Force table buffers to refresh
REPLACE SessionId with SessionIdlcLocateId = PADR(lcSessionId,FSIZE("SessionId"))
LOCATE FOR SessionId = lcLocateId AND ;
LastOn > DATETIME() - THIS.nSessionTimeout
Note the REPLACE SessionId with SessionId which is silly of course - but it actually forces a write to disk which also forces the buffers to refresh. This is what I used in said super high volume sites in the 90's and that was the only thing that worked for every scenario.
The GOTO Recno() also works in most cases... the nice thing about that is that you can make it generic - the REPLACE trick not so much. For the formaer I just created a helper function in wwUtils:
************************************************************************
* ForceTableRefresh
****************************************
*** Function: Forces a table to refresh its read buffers
*** Assume: Works on the current active cursor
*** Pass: lcAlias - Optional alias name. Else Alias() is used
*** Return: nothing
************************************************************************
FUNCTION ForceTableRefresh(lcAlias)
LOCAL lcOldAlias, llAliasSpecifiedllAliasSpecified = !EMPTY(lcAlias)
IF llAliasSpecified
lcOldAlias = ALIAS()
SELECT (lcAlias)
ENDIFIF EMPTY(ALIAS())
RETURN
ENDIFIF !EOF()
*** THIS FORCES A VFP BUFFER REFRESH ON TABLE
GO RECNO()
ELSE
GO TOP
IF !EOF()
*** THIS FORCES A VFP BUFFER REFRESH ON TABLE
GO RECNO()
ENDIF
ENDIFIF llAliasSpecified AND !EMPTY(lcOldAlias)
SELECT (lcOldAlias)
ENDIFENDFUNC
* ForceTableRefresh
+++ Rick ---
No the site is about to active. I checked out the go recno() and it works just as well. Thanks, this will be more effective than the other.
Thanks
Hmmm... is this a busy site then under load?
RLOCK() will force a buffer refresh and ensure you get current data when READING. Without that it's possible to get stale data from VFP as VFP caches data in memory. RLOCK() will force the buffers to be flushed and any subsequent data access to read again from disk.
There are also other ways such as GO RECNO() on the current table to force buffer refreshes from disk. Lots more info here too: http://fox.wikis.com/wc.dll?Wiki~VFPTableConcurrency
Not exactly the most clear topic, but lots of issues and workarounds suggested there.
I ran into this problem years ago on a very busy site and there we had to instrument all sorts of workarounds to make this work reliably, but this should only happen with FoxPro data. SQL data should be no problem.
+++ Rick ---
Rick ... Got it fixed and believe it or not .... it was FoxPro ... the following code cured it
IF RLOCK("mcgInfo")
UNLOCK IN mcgInfo
ENDIF
Note I didn't lock the record ... but somehow FoxPro was not refreshing the table. Also, after trying different mechanisms, including many hours of nonsense .... this simple code fixed it! Can't take credit for getting this in play, Paul Mrozowski suggested this ... viola!
Roy,
Not sure - code should run identically whether you run from within VFP or an EXE assuming the rest of the environment is set up the same. Make sure you have your paths set up the same and are using the same data paths etc. If unsure run the EXE out of the same folders as the EXE...
+++ Rick ---
Rick,
I am attempting to do the following
1) Stop a user from logging into the website when a flag is set that indicates maintenance in progress
2) If a user has already logged in, and subsequently the maintenance flag set to true, then the very next time the user hits the website, they are denied access
When I run the code under VFP umbrella, i.e. go into foxpro the process works
When I run it as an EXE outside the umbrella, it fails. I'm a little perplexed as how to trouble shoot this. Any help is appreciated.
I sub classed wwprocess and wwusersercurity with the following
DEFINE CLASS mcgBaseProcess AS wwProcesscAuthenticationMode = "UserSecurity"
cAuthenticationUserSecurityClass = "mcgUserSecurity"
lEnableSessionState = .T.* -- works on development computer not on server ... always shows maintenenace in progress
FUNCTION OnProcessInit
LOCAL lcPageFile
lcSessionCookieName = THIS.cSessionKey
lcSessionId = THIS.oRequest.GetCookie(lcSessionCookieName)
lcSessionCookieName = THIS.cSessionKey
Process.InitSession("")
session = this.osession
lcPageFile = lower(JUSTFNAME(Request.GetPhysicalPath()))IF ! INLIST(UPPER(lcPageFile), 'LOGIN', 'LOGON', 'DEFAULT')
llvalidsession = session.IsValidSession(lcSessionId)
IF ! llValidSession
session.endSession(lcSessionid)
ENDIF
ENDIFIF !This.Authenticate() && If they're not, a login form should appear
RETURN
ENDIFENDFUNC
FUNCTION OnAuthenticateUser(tcUserName, tcPassword, tcErrorMsg)
This.oUserSecurity = CREATEOBJECT(This.cAuthenticationUserSecurityClass)
IF !This.oUserSecurity.Authenticate(m.tcUserName, m.tcPassword)
*** Set lcErrorMsg to pass back via REF parm
tcErrorMsg = this.oUserSecurity.cErrorMsg
IF "expired" $ tcErrorMsg
* TODO: Response.Redirect(Server.oConfig.cRedirectSignupTo)
ENDIFRETURN .F.
ELSE
session.setSessionVar('username', tcusername)
session.setSessionvar('subky',this.oUserSECURITY.Ouser.subky)
session.setSessionvar('admin',this.oUSERSECURITY.oUser.Admin)
session.setSessionvar('admin2',IIF( this.oUSERSECURITY.oUser.Admin = .t., 'yes', 'no'))
session.setSessionvar('email',this.oUSERSECURITY.oUser.email)
session.setSessionvar('logoncount',this.oUSERSECURITY.oUser.logoncount)
session.setSessionvar('sessionid',session.getsessionid())
This.cAuthenticatedUser = tcUserName
Response.Redirect('mcglogin.mcg')
ENDIF
RETURN .T.
ENDFUNC************************************************************************
* wwWebPage :: Authenticate
****************************************
*** Function: Handles authenticating of a specific user.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION Authenticate(lcValidUserName,lcErrorMessage,llNoForcedLogin)
LOCAL lcUsername, lcPassword, lcErrorMsg, loLogin, lcHtml, lcCurrentUrl, l_lMaintenance
IF EMPTY(lcValidUserName)
lcValidUserName = ""
ENDIF
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()DO CASE
*** Logout: Note only works with UserSecurity. Basic cannot be undone
CASE UPPER(lcValidUserName) = "LOGOUT"
THIS.lEnableSessionState = .T.
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,"")
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .t.
CASE THIS.cAuthenticationMode == "UserSecurity" OR ;
this.cAuthenticationMode == "Custom"*** Require Session State
this.lEnableSessionState = .T.*** Check and see if we already authenticated
lcUserName = Session.GetSessionVar(this.cAuthenticationUserSecurityKey)
IF !EMPTY(lcUserName) AND ! odataUtils.IsMaintenanceInProgress()
*** Yup - already authenticated return .T.
this.cAuthenticatedUser = lcUsername
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
ENDIFIF llNoForcedLogin
RETURN .F.
ENDIF
lcCurrentUrl = Request.GetCurrentUrl()
lcErrorMsg = ""
lcUsername = ""
lcPassword = ""
*** Only try to authenticate on Postback
IF Request.IsPostBack()
*** Retrieve the default form field values
lcUsername = Request.Form("WebLogin_txtUsername")
lcPassword = Request.Form("WebLogin_txtPassword")
*** Check and see whether this user is authenticated
IF this.OnAuthenticateUser(lcUserName,lcPassword,@lcErrorMsg)
*** Assign auth user and write into Session
IF EMPTY(this.cAuthenticatedUser)
this.cAuthenticatedUser = lcUsername
ENDIF
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,lcUsername)
*** Override Page setting so it doesn't
*** look like a Postback after validation
IF VARTYPE(__WebPage) = "O"
__WebPage.IsPostBack = .F.
ENDIF
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
*RETURN .T.
ENDIF
*** If we failed to auth redisplay login
*** with an error message
ENDIF
*** Display a generic login form on a GET operation
IF EMPTY(Response.cStyleSheet)
Response.cStyleSheet = this.ResolveUrl("~/westwind.css")
ENDIF
*** You display a custom page by creating a template and showing that
*** instead. Just make sure the page has WebLogin_txtUsername and WebLogin_txtPassword
*** Form variables
*** Respone.ExpandTemplate( this.oConfig.cHtmlPagePath + "CustomLogin.wc")
*** Generate Page output
LOCAL loLogin as wwWebLogin
loLogin = CREATEOBJECT([WWC_WEBLOGINCONTROL])
loLogin.Id = "WebLogin"
loLogin.width = "350px"
loLogin.Username = lcUsername
* -- RMM
loLogin.UserMessage = IIF( l_lMaintenance, 'Sorry for the inconvenience. Try back later.' , this.cAuthenticationUserMessage)*loLogin.UserMessage = this.cAuthenticationUserMessage
loLogin.Center = .t.
loLogin.UserSecurityClass = this.cAuthenticationUserSecurityClass
loLogin.ErrorMessage = IIF( EMPTY(lcErrorMsg), IIF( l_lMaintenance, 'Maintenance in Progress',space(0)), lcErrorMsg)
lcHtml = loLogin.Render()
lcHtml = "<br /><br />" + CRLF +;
"<form method='POST' action=''>" + CRLF + ;
lcHtml + CRLF +;
"</form>"THIS.StandardPage("Case Works Account Login",lcHtml)
CASE THIS.cAuthenticationMode = "Basic"
IF EMPTY(lcValidUsername)
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIFIF UPPER(lcValidUserName) = "WCINI"
lcValidUsername = Request.GetWcIniValue("AdminAccount")
IF EMPTY(lcValidUsername)
* - RMM
IF ! l_lmaintenance
RETURN .T.
ENDIF
* Return .t.
ENDIF
ENDIF*** Retrieve the actual username
lcUsername = JUSTSTEM( Request.GetAuthenticatedUser() )
this.cAuthenticatedUser = lcUsernameIF !EMPTY(lcUserName)
IF UPPER(lcValidUserName) == "ANY" AND !EMPTY(lcUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF UPPER(lcUsername) == UPPER(lcValidUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF AT(",",lcValidUserName) > 0
lcValidUserName = "," + lcValidUsername + ","
IF AT(","+lcUserName+",",lcValidUserName) > 0
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIF
ENDIF
ENDIF
IF llNoForcedLogin
RETURN .f.
ENDIFthis.cAuthenticatedUser = lcUsername
*** Prompt for Authentication from the user
* -- RMM
IF ! l_lMaitenance
Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
endif
*Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
ENDCASE
RETURN .F.
ENDFUNC
* wwProcess :: Authenticate
ENDDEFINE
I also sub classed wwUserSercurity
DEFINE CLASS mcgUserSecurity AS wwUserSecuritycerrormsg = "You Owe Us Money."
*-- Alias of the user file.
calias = "subscriber"*-- Filename for the user file.
cfilename = "subscriber"************************************************************************
* wwUserSecurity :: GetUser
****************************************
*** Function: Retrieves a user data record object without affecting the
*** currently active user. Pass in a PK or Username and Password.
*** GetUser always returns a record unless the record cannot be found. Use Logon to check for Active and Expired status.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION GetUser(lcPK, lcPassword)*** lcPK could also be the username
THIS.lError = .F.IF .NOT. THIS.OPEN()
THIS.lError = .T.
THIS.SetError("Couldn't open "+THIS.cfilename)
RETURN .F.
ENDIFTHIS.lNewUser = .F.
*** Allow retrieving a blank user record
IF lcPK="BLANK"
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .T.
ENDIF* -- RMM 2/21/13
SELECT (THIS.cfilename)
*** If 2 parameters we have username and password
*** Otherwise it's a PK
IF PCOUNT()>1
IF THIS.lCaseSensitive
LOCATE FOR username=PADR(lcPK, LEN(username)) .AND. ;
PASSWORD = PADR(lcPassword, LEN(password))
ELSE
LOCATE FOR LOWER(username)=PADR(LOWER(lcPK), LEN(username)) .AND. ;
LOWER(PASSWORD) = PADR(LOWER(lcPassword), LEN(password))
ENDIF
* -- RMM
l_lFound = FOUND()
* -- is there maintenance going on?
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()
DO case
CASE l_lFound AND ! l_lMaintenance
SCATTER MEMO NAME THIS.ouser
RETURN .T.
CASE l_lFound AND l_lMaintenance
THIS.SetError("Maintenance In Progress")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.
OTHERWISE
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.ENDCASE
* -- RMM 9/25/14
*!* *** Found: Load the record into THIS.oUser
*!* IF FOUND()
*!* SCATTER MEMO NAME THIS.ouser
*!*
*!* * -- testing how to store varaibles with sessions
*!* RETURN .T.
*!* ENDIF
*!*
*!* *** No match - return blank user object and error message
*!* THIS.SetError("Invalid Password or Username")
*!* SCATTER BLANK MEMO NAME THIS.ouser
*!* RETURN .F.
ENDIF*** A PK was passed in
* LOCATE FOR pk = PADR(lcPK,LEN(pk))
* -- RMM 2/21/13
SELECT (THIS.cfilename)
LOCATE FOR subky = PADR(lcPK,LEN(subky))*** Found: set THIS.oUser record
IF FOUND()
SCATTER MEMO NAME THIS.ouser
RETURN .T.
ENDIF*** No match - blank user record and error message
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouserRETURN .F.
ENDFUNC
* wwUserSecurity :: GetUser
West Wind Technologies
Making waves on the Web
from Maui
Another thing that's more reliable yet is what I do in the wwSession logic:
THIS.OpenTable()** Force table buffers to refresh
REPLACE SessionId with SessionIdlcLocateId = PADR(lcSessionId,FSIZE("SessionId"))
LOCATE FOR SessionId = lcLocateId AND ;
LastOn > DATETIME() - THIS.nSessionTimeout
Note the REPLACE SessionId with SessionId which is silly of course - but it actually forces a write to disk which also forces the buffers to refresh. This is what I used in said super high volume sites in the 90's and that was the only thing that worked for every scenario.
The GOTO Recno() also works in most cases... the nice thing about that is that you can make it generic - the REPLACE trick not so much. For the formaer I just created a helper function in wwUtils:
************************************************************************
* ForceTableRefresh
****************************************
*** Function: Forces a table to refresh its read buffers
*** Assume: Works on the current active cursor
*** Pass: lcAlias - Optional alias name. Else Alias() is used
*** Return: nothing
************************************************************************
FUNCTION ForceTableRefresh(lcAlias)
LOCAL lcOldAlias, llAliasSpecifiedllAliasSpecified = !EMPTY(lcAlias)
IF llAliasSpecified
lcOldAlias = ALIAS()
SELECT (lcAlias)
ENDIFIF EMPTY(ALIAS())
RETURN
ENDIFIF !EOF()
*** THIS FORCES A VFP BUFFER REFRESH ON TABLE
GO RECNO()
ELSE
GO TOP
IF !EOF()
*** THIS FORCES A VFP BUFFER REFRESH ON TABLE
GO RECNO()
ENDIF
ENDIFIF llAliasSpecified AND !EMPTY(lcOldAlias)
SELECT (lcOldAlias)
ENDIFENDFUNC
* ForceTableRefresh
+++ Rick ---
No the site is about to active. I checked out the go recno() and it works just as well. Thanks, this will be more effective than the other.
Thanks
Hmmm... is this a busy site then under load?
RLOCK() will force a buffer refresh and ensure you get current data when READING. Without that it's possible to get stale data from VFP as VFP caches data in memory. RLOCK() will force the buffers to be flushed and any subsequent data access to read again from disk.
There are also other ways such as GO RECNO() on the current table to force buffer refreshes from disk. Lots more info here too: http://fox.wikis.com/wc.dll?Wiki~VFPTableConcurrency
Not exactly the most clear topic, but lots of issues and workarounds suggested there.
I ran into this problem years ago on a very busy site and there we had to instrument all sorts of workarounds to make this work reliably, but this should only happen with FoxPro data. SQL data should be no problem.
+++ Rick ---
Rick ... Got it fixed and believe it or not .... it was FoxPro ... the following code cured it
IF RLOCK("mcgInfo")
UNLOCK IN mcgInfo
ENDIF
Note I didn't lock the record ... but somehow FoxPro was not refreshing the table. Also, after trying different mechanisms, including many hours of nonsense .... this simple code fixed it! Can't take credit for getting this in play, Paul Mrozowski suggested this ... viola!
Roy,
Not sure - code should run identically whether you run from within VFP or an EXE assuming the rest of the environment is set up the same. Make sure you have your paths set up the same and are using the same data paths etc. If unsure run the EXE out of the same folders as the EXE...
+++ Rick ---
Rick,
I am attempting to do the following
1) Stop a user from logging into the website when a flag is set that indicates maintenance in progress
2) If a user has already logged in, and subsequently the maintenance flag set to true, then the very next time the user hits the website, they are denied access
When I run the code under VFP umbrella, i.e. go into foxpro the process works
When I run it as an EXE outside the umbrella, it fails. I'm a little perplexed as how to trouble shoot this. Any help is appreciated.
I sub classed wwprocess and wwusersercurity with the following
DEFINE CLASS mcgBaseProcess AS wwProcesscAuthenticationMode = "UserSecurity"
cAuthenticationUserSecurityClass = "mcgUserSecurity"
lEnableSessionState = .T.* -- works on development computer not on server ... always shows maintenenace in progress
FUNCTION OnProcessInit
LOCAL lcPageFile
lcSessionCookieName = THIS.cSessionKey
lcSessionId = THIS.oRequest.GetCookie(lcSessionCookieName)
lcSessionCookieName = THIS.cSessionKey
Process.InitSession("")
session = this.osession
lcPageFile = lower(JUSTFNAME(Request.GetPhysicalPath()))IF ! INLIST(UPPER(lcPageFile), 'LOGIN', 'LOGON', 'DEFAULT')
llvalidsession = session.IsValidSession(lcSessionId)
IF ! llValidSession
session.endSession(lcSessionid)
ENDIF
ENDIFIF !This.Authenticate() && If they're not, a login form should appear
RETURN
ENDIFENDFUNC
FUNCTION OnAuthenticateUser(tcUserName, tcPassword, tcErrorMsg)
This.oUserSecurity = CREATEOBJECT(This.cAuthenticationUserSecurityClass)
IF !This.oUserSecurity.Authenticate(m.tcUserName, m.tcPassword)
*** Set lcErrorMsg to pass back via REF parm
tcErrorMsg = this.oUserSecurity.cErrorMsg
IF "expired" $ tcErrorMsg
* TODO: Response.Redirect(Server.oConfig.cRedirectSignupTo)
ENDIFRETURN .F.
ELSE
session.setSessionVar('username', tcusername)
session.setSessionvar('subky',this.oUserSECURITY.Ouser.subky)
session.setSessionvar('admin',this.oUSERSECURITY.oUser.Admin)
session.setSessionvar('admin2',IIF( this.oUSERSECURITY.oUser.Admin = .t., 'yes', 'no'))
session.setSessionvar('email',this.oUSERSECURITY.oUser.email)
session.setSessionvar('logoncount',this.oUSERSECURITY.oUser.logoncount)
session.setSessionvar('sessionid',session.getsessionid())
This.cAuthenticatedUser = tcUserName
Response.Redirect('mcglogin.mcg')
ENDIF
RETURN .T.
ENDFUNC************************************************************************
* wwWebPage :: Authenticate
****************************************
*** Function: Handles authenticating of a specific user.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION Authenticate(lcValidUserName,lcErrorMessage,llNoForcedLogin)
LOCAL lcUsername, lcPassword, lcErrorMsg, loLogin, lcHtml, lcCurrentUrl, l_lMaintenance
IF EMPTY(lcValidUserName)
lcValidUserName = ""
ENDIF
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()DO CASE
*** Logout: Note only works with UserSecurity. Basic cannot be undone
CASE UPPER(lcValidUserName) = "LOGOUT"
THIS.lEnableSessionState = .T.
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,"")
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .t.
CASE THIS.cAuthenticationMode == "UserSecurity" OR ;
this.cAuthenticationMode == "Custom"*** Require Session State
this.lEnableSessionState = .T.*** Check and see if we already authenticated
lcUserName = Session.GetSessionVar(this.cAuthenticationUserSecurityKey)
IF !EMPTY(lcUserName) AND ! odataUtils.IsMaintenanceInProgress()
*** Yup - already authenticated return .T.
this.cAuthenticatedUser = lcUsername
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
ENDIFIF llNoForcedLogin
RETURN .F.
ENDIF
lcCurrentUrl = Request.GetCurrentUrl()
lcErrorMsg = ""
lcUsername = ""
lcPassword = ""
*** Only try to authenticate on Postback
IF Request.IsPostBack()
*** Retrieve the default form field values
lcUsername = Request.Form("WebLogin_txtUsername")
lcPassword = Request.Form("WebLogin_txtPassword")
*** Check and see whether this user is authenticated
IF this.OnAuthenticateUser(lcUserName,lcPassword,@lcErrorMsg)
*** Assign auth user and write into Session
IF EMPTY(this.cAuthenticatedUser)
this.cAuthenticatedUser = lcUsername
ENDIF
Session.SetSessionVar(this.cAuthenticationUserSecurityKey,lcUsername)
*** Override Page setting so it doesn't
*** look like a Postback after validation
IF VARTYPE(__WebPage) = "O"
__WebPage.IsPostBack = .F.
ENDIF
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
*RETURN .T.
ENDIF
*** If we failed to auth redisplay login
*** with an error message
ENDIF
*** Display a generic login form on a GET operation
IF EMPTY(Response.cStyleSheet)
Response.cStyleSheet = this.ResolveUrl("~/westwind.css")
ENDIF
*** You display a custom page by creating a template and showing that
*** instead. Just make sure the page has WebLogin_txtUsername and WebLogin_txtPassword
*** Form variables
*** Respone.ExpandTemplate( this.oConfig.cHtmlPagePath + "CustomLogin.wc")
*** Generate Page output
LOCAL loLogin as wwWebLogin
loLogin = CREATEOBJECT([WWC_WEBLOGINCONTROL])
loLogin.Id = "WebLogin"
loLogin.width = "350px"
loLogin.Username = lcUsername
* -- RMM
loLogin.UserMessage = IIF( l_lMaintenance, 'Sorry for the inconvenience. Try back later.' , this.cAuthenticationUserMessage)*loLogin.UserMessage = this.cAuthenticationUserMessage
loLogin.Center = .t.
loLogin.UserSecurityClass = this.cAuthenticationUserSecurityClass
loLogin.ErrorMessage = IIF( EMPTY(lcErrorMsg), IIF( l_lMaintenance, 'Maintenance in Progress',space(0)), lcErrorMsg)
lcHtml = loLogin.Render()
lcHtml = "<br /><br />" + CRLF +;
"<form method='POST' action=''>" + CRLF + ;
lcHtml + CRLF +;
"</form>"THIS.StandardPage("Case Works Account Login",lcHtml)
CASE THIS.cAuthenticationMode = "Basic"
IF EMPTY(lcValidUsername)
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIFIF UPPER(lcValidUserName) = "WCINI"
lcValidUsername = Request.GetWcIniValue("AdminAccount")
IF EMPTY(lcValidUsername)
* - RMM
IF ! l_lmaintenance
RETURN .T.
ENDIF
* Return .t.
ENDIF
ENDIF*** Retrieve the actual username
lcUsername = JUSTSTEM( Request.GetAuthenticatedUser() )
this.cAuthenticatedUser = lcUsernameIF !EMPTY(lcUserName)
IF UPPER(lcValidUserName) == "ANY" AND !EMPTY(lcUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF UPPER(lcUsername) == UPPER(lcValidUserName)
* -- RMM
IF ! l_lMaintenance
RETURN .t.
endif
*RETURN .T.
ENDIFIF AT(",",lcValidUserName) > 0
lcValidUserName = "," + lcValidUsername + ","
IF AT(","+lcUserName+",",lcValidUserName) > 0
* -- RMM
IF ! l_lMaintenance
RETURN .T.
ENDIF
* RETURN .T.
ENDIF
ENDIF
ENDIF
IF llNoForcedLogin
RETURN .f.
ENDIFthis.cAuthenticatedUser = lcUsername
*** Prompt for Authentication from the user
* -- RMM
IF ! l_lMaitenance
Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
endif
*Response.BasicAuthentication(Request.GetServerName(),lcErrorMessage)
ENDCASE
RETURN .F.
ENDFUNC
* wwProcess :: Authenticate
ENDDEFINE
I also sub classed wwUserSercurity
DEFINE CLASS mcgUserSecurity AS wwUserSecuritycerrormsg = "You Owe Us Money."
*-- Alias of the user file.
calias = "subscriber"*-- Filename for the user file.
cfilename = "subscriber"************************************************************************
* wwUserSecurity :: GetUser
****************************************
*** Function: Retrieves a user data record object without affecting the
*** currently active user. Pass in a PK or Username and Password.
*** GetUser always returns a record unless the record cannot be found. Use Logon to check for Active and Expired status.
*** Assume:
*** Pass:
*** Return:
************************************************************************
FUNCTION GetUser(lcPK, lcPassword)*** lcPK could also be the username
THIS.lError = .F.IF .NOT. THIS.OPEN()
THIS.lError = .T.
THIS.SetError("Couldn't open "+THIS.cfilename)
RETURN .F.
ENDIFTHIS.lNewUser = .F.
*** Allow retrieving a blank user record
IF lcPK="BLANK"
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .T.
ENDIF* -- RMM 2/21/13
SELECT (THIS.cfilename)
*** If 2 parameters we have username and password
*** Otherwise it's a PK
IF PCOUNT()>1
IF THIS.lCaseSensitive
LOCATE FOR username=PADR(lcPK, LEN(username)) .AND. ;
PASSWORD = PADR(lcPassword, LEN(password))
ELSE
LOCATE FOR LOWER(username)=PADR(LOWER(lcPK), LEN(username)) .AND. ;
LOWER(PASSWORD) = PADR(LOWER(lcPassword), LEN(password))
ENDIF
* -- RMM
l_lFound = FOUND()
* -- is there maintenance going on?
l_lMaintenance = oDataUtils.IsMaintenanceInProgress()
DO case
CASE l_lFound AND ! l_lMaintenance
SCATTER MEMO NAME THIS.ouser
RETURN .T.
CASE l_lFound AND l_lMaintenance
THIS.SetError("Maintenance In Progress")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.
OTHERWISE
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouser
RETURN .f.ENDCASE
* -- RMM 9/25/14
*!* *** Found: Load the record into THIS.oUser
*!* IF FOUND()
*!* SCATTER MEMO NAME THIS.ouser
*!*
*!* * -- testing how to store varaibles with sessions
*!* RETURN .T.
*!* ENDIF
*!*
*!* *** No match - return blank user object and error message
*!* THIS.SetError("Invalid Password or Username")
*!* SCATTER BLANK MEMO NAME THIS.ouser
*!* RETURN .F.
ENDIF*** A PK was passed in
* LOCATE FOR pk = PADR(lcPK,LEN(pk))
* -- RMM 2/21/13
SELECT (THIS.cfilename)
LOCATE FOR subky = PADR(lcPK,LEN(subky))*** Found: set THIS.oUser record
IF FOUND()
SCATTER MEMO NAME THIS.ouser
RETURN .T.
ENDIF*** No match - blank user record and error message
THIS.SetError("Invalid Password or Username")
SCATTER BLANK MEMO NAME THIS.ouserRETURN .F.
ENDFUNC
* wwUserSecurity :: GetUser