Subject Re: PasswordMaskEF.cc bug?
From Peter Hegelbach <peter@hegelbach.com>
Date Wed, 21 Feb 2024 08:23:55 +0100
Newsgroups dbase.getting-started
Attachment(s) Pic.pngTestform.wfmPassword.ccPasswordHidden.icoPasswordVisible.ico

I can confirm that.

I have therefore slightly modified Password.cc in this respect. I also
added the possibility to hash the password and a reveal function for the
input.

Where I got stuck: Support for Ctrl-C, pasting the password from the
clipboard.

Peter


** END HEADER -- Diese Zeile nicht entfernen
//
// Erstellt am 20.02.24
//
parameter bModal
local f
f = new TestformForm()
if (bModal)
   f.mdi = false // Nicht-MDI festlegen
   f.ReadModal()
else
   f.Open()
endif

class TestformForm of FORM
   set procedure to :hitTime2:Password.cc
        with (this)
      metric = 6        // Pixel
      height = 423.0
      left = 800.0
      top = 0.0
      width = 336.0
      text = ""
   endwith

        this.E_Passwort = new ph_PasswordEntry(this)
   with (this.E_Passwort)
      left = 94
      top = 133
      width = 180
      value = ""
      maxLength = 20
                fontBold = true
   endwith
        
endclass



// 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