// Metric: Should work with both Character and Pixels /* ------------------------------------------------------------------ Password.cc Author: Ken Mayer Date..: Spring, 1998 August 21, 2001 April 2, 2003 January 27, 2004 June 15, 2004 -- minor little fixes ... July 15, 2004 -- added captureEnter property, see below. A variation on the Password CC that shipped with Visual dBASE 5.x The original code is 1994 (c) Borland. August, 2001: Added caseSensitive and minLength properties, and code in isEnteredPasswordOK and LostFocus events to check for these values. April, 2003: Modified so that the IsEnteredPasswordOk event increments the numtries and then compares against numTriesAllowed, and if so, it sets the stopTrying property to true. This allows a form to close if using canClose event, and so on. See TestPass.wfm Check details in code, including LostFocus ... January, 2004: Added the CheckSelect API function prototype to detect if the user has selected any text and use this as a basis to determine the behavior of the Backspace and Delete keys. Modifications to this class done by Marc Hamelin. Original code from Marc VdB. April, 2005: Added captureEnter property and code in Procedure 'Key' to ignor or capture 'ENTER' key activity -- when captureEnter is True and PasswordEntry length is 0; an Alert message is generated the same as XML Command LOGOUT's password behavior. -- Wes Rue June, 2015: In IsValidChar method, added a new case to allow for some symbols currently used in many passwords, such as: !@#$%^&* ... Also updated the password Mask and font for this control to use Wingdings font, and better character than the asterisk. July, 2018: minor change to code to deal with activeControl issues (if a users presses enter, focus is not where it should be ...). Change to code at top of LostFocus event handler, suggestion by Mervyn Bick. Usage: set procedure to password.cc additive // in the form designer, place an instance // of the password entryfield onto the form // set the "correctPassword" to a value // in some fashion, you probably do not want // to hardcode it, but ... when you know the // userid, get the password from the same // place, and assign it: form.passwordEntry1.setCorrectPassword( form.rowset.fields["password"].value ) // set the number of retries allowed: form.passwordEntry1.numTriesAllowed = 3 // (default) ========================================================== NOTE: See detailed examples in TESTPASS.WFM and CHNGPASS.WFM in the dUFLP library ... ========================================================== -- Custom Properties: maskChar -- character that appears instead of what's typed in Defaults to "*", but with some creativity, you could change it to just about anything. You may want to consider using a dingbat font, for example, (setting the passwordEntryfield's fontName) and using a character from that ... numTriesAllowed -- defaults to 3, but can be changed programmatically -- if you set it to 0, there won't be a check -- the user can keep trying ... numTries -- do not try to change this -- it's the counter used to determine if the number of tries allowed has been reached. stopTrying -- logical value used to exit ... [defaults to false] validCheckRequired -- logical, defaults to true -- used for such situations as a change password form caseSensitive -- defaults to false -- if it's false, then all tests compare against the uppercase version of the values, if it is true, then we do not, and a value of "TeSt" will not be equal to "Test", which will not be equal to "test", or "TEST". minLength -- minimum length of password -- defaults to zero, which means no error checking for length will occur. captureEnter -- defaults to false -- if it is false, then 'ENTER' key is ignored; if it is true, pressing 'ENTER' key when PasswordEntry length is 0 generates Alert message the same as XML Command LOGOUT's password behavior. maskChar -- with the font set to WingDings, this uses the chr() function to put specific characters into the mask: chr( 108 ) Solid Dot chr( 116 ) diamond shape chr( 117 ) slightly fatter diamond shape chr( 118 ) diamond made of smaller diamonds ("star bullet" in Word) chr( 173 ) eight pointed star And of course you could try a wide variety of other symbols ... // See Example: TestPass.wfm (which uses ChngPass.wfm) ------------------------------------------------------------------ */ #define BACKSPACE_KEY 8 // -- ASCII value of Backspace key #define DELETE_KEY 127 // -- ASCII value of Delete key #define ENTER_KEY 13 // -- ASCII value of Enter key class ph_PasswordEntry( oParent ) of Entryfield( oParent ) custom with( this ) Border := true PageNo := 1 alignVertical := 1 // Mitte systemtheme := false borderStyle := 4 fontName := "Wingdings" fontBold := true fontSize := 10 Value := "" Top := 3 onOpen := class::onOpen key := class::key onLostFocus := CLASS::LostFocus endwith // protect correctPassword, NumTries // geschützte eigenschaften, nur in dieser und davon abgeleiteten Klassen lesbar // Add custom properties this.correctPassword = "" this.maskChar = chr(164) // mask character this.CamouflageOn = true // added PH 05.01.24 this.numTriesAllowed = 3 // number of tries allowed this.numTries = 0 // counter ... this.stopTrying = false this.validCheckRequired = true this.minLength = 0 this.captureEnter = false // Add ph für Hash-Code statt unverschlüsseltes Passwort this.correctHash = null *------------------------------------------------------------------------------- function formatButton privat cBtnName, c, cn cBtnName = "Btn_" + trim(this.Name) c = "this.parent." + cBtnName // parent is normally the form cn = c + " = new Image(this.parent)" &cn cn = c + ".owner = this" &cn with (&c) group = true top = this.top + 2 left = this.left + this.width + 4 height = 18 width = 23 onLeftMouseUp = class::Change_Camouflage pageNo = this.PageNo borderStyle = 3 datasource = "filename PasswordHidden.ico" tabStop = false transparent = true endwith return *------------------------------------------------------------------------------- function Change_Camouflage if this.owner.CamouflageOn = true this.owner.CamouflageOn = false this.datasource = "filename :hitTime2:PasswordVisible.ico" this.owner.fontName = CONTROLFONTNAME this.owner.value = this.owner.enteredPassword else this.owner.CamouflageOn = true this.datasource = "filename :hitTime2:PasswordHidden.ico" this.owner.fontName = "Wingdings" this.owner.value = repl(this.owner.maskChar, len(this.owner.enteredPassword)) endif return *------------------------------------------------------------------------------- function onDesignOpen( lFromPalette ) if lFromPalette and form.metric == 6 // Pixels this.height := 22 this.width := 105 endif return *------------------------------------------------------------------------------- function OnOpen(nChar, nPosition) // Declare the CheckSelect API function prototype if Type("CheckSelect") # "FP" extern CLONG CheckSelect(CHANDLE, CUINT, ; CPTR CLONG, CPTR CLONG) user32 from "SendMessageA" endif // Custom properties if type("this.enteredPassword") = "U" this.enteredPassword = "" this.correctPassword = "" // This property must be manually assigned // whereever this control is instantiated endif this.formatButton() this.PasswordValid = false return *------------------------------------------------------------------------------- function SetCorrectPassword(password) this.correctPassword = password this.enteredPassword = "" return *------------------------------------------------------------------------------- function SetCorrectHashCode(HashCode) this.correctHash = HashCode this.enteredPassword = "" return *------------------------------------------------------------------------------- function IsEnteredPasswordOk private cPass cPass = this.enteredPassword if this.correctPassword = null // alte Version, mit unverschlüsseltem Passwort this.PasswordValid = iif(cPass == cCorrect, true, false) else // Version mit HashCode local h h = new hash() this.PasswordValid = iif(h.cmpr(cPass, this.correctHash), true, false) endif this.numTries++ // increment counter if this.numTries => this.numTriesAllowed this.stopTrying := true endif return ( this.PasswordValid ) *------------------------------------------------------------------------------- procedure Key(nChar, nPosition) // Handles keys entered in the password entryfield private enteredChar, returnValue // Get the character positions of the selected text (if any) local SelectStartPos, SelectEndPos SelectStartPos = 0 SelectEndPos = 0 CheckSelect(this.hwnd,0xB0,SelectStartPos,SelectEndPos) enteredChar = chr(nChar) returnValue = true // By default output whatever key was typed do case // Check for keys that modify the value case nChar = BACKSPACE_KEY // if the text is not selected, delete the previous character, // if the text is selected, delete the characters corresponding // to the selection range if SelectStartPos == 0 AND SelectEndPos == 0 this.enteredPassword = ; stuff(this.enteredPassword, nPosition - 1, 1, "") else this.enteredPassword = ; stuff(this.enteredPassword, SelectStartPos + 1, ; SelectEndPos - SelectStartPos, "") endif case nChar = DELETE_KEY // if the text is not selected, delete the current character, // if the text is selected, delete the characters corresponding // to the selection range if SelectStartPos == 0 AND SelectEndPos == 0 this.enteredPassword = ; stuff(this.enteredPassword, nPosition, 1, "") else this.enteredPassword = ; stuff(this.enteredPassword, SelectStartPos + 1, ; SelectEndPos - SelectStartPos, "") endif case nChar = ENTER_KEY if this.captureEnter AND this.enteredPassword.length = 0 msgbox( "Kein Passwort eingegeben!","Warnung!", 48 ) this.setFocus() endif otherwise if class::IsValidChar(enteredChar) // Check if alphanumeric // if the text is not selected, insert the character entered, // if the text is selected, overwrite the characters // corresponding to the selection range with the character // entered if SelectStartPos == 0 AND SelectEndPos == 0 this.enteredPassword = ; stuff(this.enteredPassword, nPosition, 1, enteredChar) else this.enteredPassword = ; stuff(this.enteredPassword, SelectStartPos + 1, ; SelectEndPos - SelectStartPos, enteredChar) endif if this.camouflageOn returnValue = asc( this.maskChar ) // Output camouflage character else returnValue = nChar endif else returnValue = false ? chr(7) // Beep endif endcase return returnValue *------------------------------------------------------------------------------- procedure IsValidChar(char) // Make sure entered key is alphanumeric private returnValue do case case isalpha(char) // Letter a-z, A-Z returnValue = true case char >= "0" and char <= "9" // Digit? returnValue = true // if you need to allow others, add them here: case char $ "!#$%&()*+-./:<=>?@[]_" // Special characters returnValue = true otherwise // Invalid? returnValue = false endcase return returnValue endclass