Mega Code Archive

 
Categories / Delphi / Examples
 

The security of knowing who’s who

The Perpetual Newbie - Log Entry #5.2 This article first appeared on http://www.undu.com An earlier version of this article appeared early in Feb 00. Some corrections, a little judicious editing and better organization will hopefully result in something better for you, the reader. Recently, I found myself without an application login component. Forced by circumstances to create one for at least temporary utilization, I found the experience interesting enough that I thought I'd take a run at writing an omnibus article for my fellow newbies. Beware a temporary segue a little farther down! A login form isn't always the sign of a programming paranoid. Honestly, I use the login form to find out who's using the application so that I can tag entries and edits with an identifier. If you need strong security at the component or menu level, leave now and check out full-fledged systems like the UIL Security System. But if you run a single app with a few users you want to differentiate, or if you want to have a 'little security,' read on. Readers will remember that I tend to buy my solutions rather than re-invent them. For security and a quite nifty login form, I preferred Kalliopi's SecurityGuard system. While using Delphi 3, I found it to be quick to install, easy to run and quite powerful. Unfortunately, there's no Delphi 5 version of it. Although I would have been be first in line when it came out, my latest information is that it will never happen. Sooooo, I had to have a login system that took advantage of the major things I had come to expect from SecurityGuard. It had to have a login screen and a means of extracting information about the user's security levels and other miscellaneous info, such as preferences (I could do this through INI/Registry files, but it's just SO much easier to put it into the security table, since some users migrate from computer to computer). Given my temporary need for a login, I decided to limit my ambitions and work on just getting the name and security level info into the application. I started with the design of the security table, which I call PAZWORDZ. That table should include a field for user name (keyed), the password and any other information you deem appropriate. SEGUE: Naming Fields In creating your own version of a security table, but whatever name, consider one caveat. Do not abuse your right to name fields whatever you want. Many years ago, I was part of the first operation to combine Paradox for DOS, Interbase and peer-to-peer networking. Borland said it couldn't be done at the time, but we did ... after we fixed a whole raft of naming issues. I discovered then, that spaces, underscores and punctuation are EVIL, EVIL, EVIL in field names. Like everybody else at the time, I was thrilled to throw off the yoke of limited dBase names and loved Paradox's ability to use names that ran on and on and on and on. It was the equivalent of a new desktop publishing user finding out about fonts and their abundance. Or when those first-time W95 users discovered the end of 8.3 filename tyranny. They went bananas. For me, it was field names. My major crime was field names like Telephone# or Customer's Reasons. Interbase hated THOSE names. So, we redid the structure of the Paradox databases and then went though the program code (several tens of thousands of lines) finding and replacing those names. NEVER again. Well, never is a long time. When I was first building my security tables, I used simple names for fields. Without any thought, I made the user's name field NAME. Starting date became DATE. There were a few other misdemeanors, but you get the drift. These field names didn't bite me until I needed to use some SQL. Then, the names became the cause for extra effort. If the field name looks like a likely property name, or even MIGHT be like a property name, CHANGE IT!. Do it now, in the creative stage, and you will be much happier later. Make it UserName and StartDate. If you don't want case-sensitive fields, then make sure you auto-cap the name and password fields. Finally, don't forget to password protect the final product and to record the password. It's an oversight I've been guilty of. SEGUE OVER. In making myself a Login form. I started with the Delphi Password form. I copied the label and edit box for the password to create a label for the user name and edit box. Some jockeying of the labels, boxes and buttons gave me the traditional login form with the user name edit box over the password edit box, overtop the OK and CANCEL buttons. The edit boxes were both set to uppercase, as I was using all upper case for names and passwords in the security table. If you want to go the extra step of having case-sensitive identifiers, then don't do that. I put a datasource and table component connected to Pazwordz on the form. As a final touch, I decided to include an image, with my logo in it and the label "An Idea Mechanic App." You could include a generic image, a company specific image or nothing at all. KeyPreview was turned to TRUE, an important step. And I had to add the dialogs unit to the uses statement. Then, I added the following code: var DlgLogin2 : TDlgLogin2; AttemptsLeft : integer; implementation {The name and password didn't match, Continue or Quit?} procedure TDlgLogin2.NoGood; begin {One attempt down, how many to go?} AttemptsLeft := AttemptsLeft - 1; if AttemptsLeft >= 1 then begin {Wipe out bad attempt and go back to the beginning} eUserName.Text := ''; ePassword.text := ''; eUserName.setfocus; {I could be more explanatory, here, but why?} showmessage('Not correct, try again!'); {I could be more explanatory) end else DlgLogin2.ModalResult := mrAbort; {Too many attempts, BYE!} end; {Handle the enter keystroke in the user name edit box} procedure TDlgLogin2.eUserNameKeyPress(Sender: TObject; var Key: Char); begin if Key = #13 {Was it the enter key?} then begin {convert enter to a tab} selectNext(ActiveControl as tWinControl, true, true); key := #0; {Wipe out the enter keystroke} end; end; {Handle the enter key in the password edit box and click OK} procedure TDlgLogin2.ePasswordKeyPress(Sender: TObject; var Key: Char); begin if Key = #13 then begin selectNext(ActiveControl as tWinControl, true, true); key := #0; OKBtnClick(Sender); {Equivalent to clicking the OKBtn} end; end; {Cancel entering name/password. Program will not run} procedure TDlgLogin2.CancelBtnClick(Sender: TObject); begin DlgLogin2.ModalResult := mrCancel; end; {We have an attempt, was it good? YES! Ooops, NO!} procedure TDlgLogin2.OKBtnClick(Sender: TObject); begin OKBtn.SetFocus; {This ensures the edit box contents are read} if eUserName.text = '' then eUserName.setfocus {no name, no attempt} else if ePassword.text = '' then ePassword.setFocus {no password, no attempt} else if TblPW.findkey([eUserName.text]) {is the name there?} then if uppercase(TblPW.fieldByName('PASSWORD').asString) = ePassword.text {is the password the same?} then DlgLogin2.ModalResult := mrOK {Let the program run} else NoGood {Any attempts left?} else NoGood; {Any attempts left?} end; initialization AttemptsLeft := 3; Some of the code bears extra commentary. The keyPress procedures for the two edit boxes are the same, except for the subsequent spoofing of a push of the OK button after hitting enter in the password field. And there is a lack of error-proofing with this project. There are obviously several places for Try/Except blocks and error-handlers. It's up to you to determine how much further to drive the safe-coding practice on this job. The project file is shown below: uses Forms, DBtables, Dialogs, FInputInv in 'FInputInv.pas' {FrmMainInput}, uDlgLogin2 in 'E:\v5\uDlgLogin2.pas' {DlgLogin2}; {$R *.RES} begin Application.Initialize; session.addpassword('itsreallynotmyfault'); DlgLogin2 := TDlgLogin2.create(nil); with DlgLogin2 do begin TblPw.DatabaseName := 'Project1'; TblPW.TableName := 'Pazwordz'; TblPW.Active := true; showmodal; if ModalResult = 1 then begin Application.CreateForm(TFrmMainInput, FrmMainInput); FrmMainInput.UserName := DlgLogin2.eUserName.text; FrmMainInput.DEB := DlgLogin2.TblPw.FieldByName('MiniID').asString; if FrmMainInput.DEB = '' then FrmMainInput.DEB := copy(uppercase(DlgLogin2.eUserName.text),1,3); FrmMainInput.SecLevel := DlgLogin2.TblPw.FieldByName('SecLevel').asInteger; FrmMainInput.PgmDir := ExtractFilePath(application.ExeName); DlgLogin2.free; Application.Run; end else DlgLogin2.free; end; end. Once more, there is some finagling with the uses statement of the DPR file. I added DBTables and Dialogs to handle the login dialog and the database components on that form. I could have foregone using the section where I set the security table's alias, table name and activate it. But I wanted to make this form a universal form I could use with many applications. So, I saved the dialog form to a central location and put in the lines shown here to operate successfully. One gotcha did arise. Originally, the "if ModalResult = 1" line was "if ModalResult = mrOK." The compiler choked on it, claiming it couldn't figure out mrOK. I was pretty sure mrOK was defined in dialogs, but I gave up and instead used the enumerated value of 1 for mrOK. Obviously, it's important to cling to the login dialog as long as you can, in order to cross over the Security Level, User Name and other miscellanea that you have in the security table. I use the DEB field to do the mini-tagging for identity. It stands for Data Entered By and is usually the first three characters of the name. But, in the case of ANG, it could be Angelo or Angie, so the contents of the security table's MiniID field differentiates between the two as ALO and ANG. Hopefully you can use this idea to launch your own login form, whether it's for security reasons or for finding out who's using the application right now. Gary Mugford Idea Mechanic, Bramalea ON Canada mugford@aztec-net.com