In a textfile, How to express if ( Line 1's answer = Line 2's answer ) then - delphi

I just started Delphi.
I'm making a simple login form, and the text file (UserInfo.txt) is set up like this
I just want Delphi to run through the TextFile to look for the string, then if that string equals edtUseranme.Text then it must check if that string is equal to the string on the second line.
If it equals then it can continue to the next Form
Here is my code:
for I := 0 to Eof(tFile) do
begin
Readln(tFile, sLine);
Inc(I);
if ReadLn(tFile) = edtUsername.Text then
begin
if edtUsername.Text = then
begin
frmMain.Show;
frmLogin.Hide;
end
else
begin
ShowMessage('INPUT INVALID (Try again)');
end;
end;
end;
At that second if statement, I don't know what to put after =.

Like Ken White suggested, you could redesign your text file and then use a TStringList to parse it. That would certainly make it easier to work with the data. However, if you want to stick with the code you already have, then you could do something more like this instead:
var
tFile: TextFile;
//Declare variables for temporally storing read username and password
sUser, sPass, sDivider: string;
Valid: Boolean;
...
Valid := False;
//use while loop to read every line till the end of file
while not Eof(tFile) do
begin
//Read username into sUser variable
Readln(tFile, sUser);
//Read password into sPass variable
Readln(tFile, sPass);
//Read divider into sDivider variable
Readln(tFile, sDivider);
//Use if clause to perform multiconditional logical comparison to see if
//read username and password equals to those specified in suitable edit
//boxes
//NOTE: Each separate logical condition must be in its own brackets
if (sUser = edtUsername.Text) and (sPass = edtPassword.Text) then
begin
//If conditions are met we set local variable Valid to true
Valid := True;
//and then break the loop
Break;
end;
end;
//If local variable valid was set to True show the main form
if Valid then
begin
frmMain.Show;
frmLogin.Hide;
end else
//else we show message about invalid input entered
ShowMessage('INPUT INVALID (Try again)');

Your file format is poorly designed for what you're wanting to do, I'm afraid.
You'd be better off using a more standard format (realizing, of course, that it's totally insecure to store names and passwords as plain text). Delphi makes it easy to work with name/value pairs using TStringList with text like this:
Name=Value
This would mean your text file would contain the username=password type lines:
Bob=Password
Sue=Bambi
You then use TStringList to load the file and read the value associated with the name, like this:
var
SL: TStringList;
UserName, Password: string;
begin
UserName := Edit1.Text; // Your username control
Password := Edit2.Text;
// You should check to make sure that both the username and password
// were entered here before proceeding. I'll leave that to you to do.
SL := TStringList.Create;
try
SL.LoadFromFile('C:\Temp\UserInfo.txt'); // Replace with your filename
// The next line looks for a name in the stringlist equal to the
// entered username, and if it's found compares its value to the
// password that was provided.
//
// You'll need to handle making sure that you do a case-sensitive
// (or non-sensitive, depending on your needs) comparison is done
// if needed.
if SL.Values[UserName] = Password then
// Login succeeded
else
// Login failed.
finally
SL.Free;
end;
end;

Related

Check if user authentication in Active DIrectory

i would like to know whether a user inputs the correct combination of Domain, User and Password for his Active Directory user.
I tried to make a very simple program that is not able to connect but by reading the error message i can know if the user/password is correct.
This is trick based (the logic is on reading the Exception message), anyway i testd this prototype on 2 servers and i noticed that the excpetion messages change from server to server so this is not reliable.
uses adshlp, ActiveDs_TLB;
// 3 TEdit and a TButton
procedure TForm4.Button1Click(Sender: TObject);
Var
aUser : IAdsUser;
pDomain, pUser, pPassword : string;
myResult : HRESULT;
Counter: integer;
begin
pDomain := edtDomain.Text;
pUser:= edtUser.Text;
pPassword := edtPwd.Text;
Counter := GetTickCount;
Try
myResult := ADsOpenObject(Format('LDAP://%s',[pDomain]),Format('%s\%s',[pDomain,pUser]),pPassword,
ADS_READONLY_SERVER,
IAdsUser,aUser);
except
On E : EOleException do
begin
if (GetTickCount - Counter > 3000) then ShowMessage ('Problem with connection') else
if Pos('password',E.Message) > 0 then ShowMessage ('wrong username or password') else
if Pos('server',E.Message) > 0 then ShowMessage ('Connected') else
ShowMessage('Unhandled case');
memLog.Lines.Add(E.Message);
end;
end
end;
The reason why i set "Connected" if the message contain "server" is that on my
local machine (on my company ldap server in fact) in case all is fine (domain, user and password) the server replies "The server requires a safer authentication", so the "server" word is in there, while in other cases it says "wrong user or password". SInce this must work on itlian and english servers i set "server" and "pasword" as reliable words. Anyway i tested on another server that gives differente errors.
I started from a reply to this question to do the above.
How can i check if the user set the correct password or not in a more reliable way using a similar technique?
UPDATE (found solution)
Thanks to the replies i managed to write this function that does what i need. It seems quite reliable up to now, I write here to share, hoping it can help others:
// This function returns True if the provided parameters are correct
// login credentials for a user in the specified Domain
// From empirical tests it seems reliable
function UserCanLogin(aDomain, aUser, aPassword: string): Boolean;
var
hToken: THandle;
begin
Result := False;
if (LogonUser(pChar(aUser), pChar(aDomain), pChar(aPassword), LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, hToken)) then
begin
CloseHandle(hToken);
Result := True;
end;
end;
You need to check the error code that ADsOpenObject returns. do not base your error checking on the returned exception messages.
if the function succeeded it will return S_OK, otherwise you need to refer to ADSI Error Codes, specifically, the LDAP error codes for ADSI
When an LDAP server generates an error and passes the error to the
client, the error is then translated into a string by the LDAP client.
This method is similar to Win32 error codes for ADSI. In this example,
the client error code is the WIN32 error 0x80072020.
To Determine the LDAP error codes for ADSI
Drop the 8007 from the WIN32 error code. In the example, the remaining hex value is 2020.
Convert the remaining hex value to a decimal value. In the example, the remaining hex value 2020 converts to the decimal value
8224.
Search in the WinError.h file for the definition of the decimal value. In the example, 8224L corresponds to the error
ERROR_DS_OPERATIONS_ERROR.
Replace the prefix ERROR_DS with LDAP_. In the example, the new definition is LDAP_OPERATIONS_ERROR.
Search in the Winldap.h file for the value of the LDAP error definition. In the example, the value of LDAP_OPERATIONS_ERROR in
the Winldap.h file is 0x01.
For a 0x8007052e result (0x052e = 1326) for example you will get ERROR_LOGON_FAILURE
From your comment:
Since the function always raises an exception i am not able to read
the code
You are getting an EOleException because your ADsOpenObject function is defined with safecall calling convention. while other implementations might be using stdcall. when using safecall Delphi will raise an EOleException and the HResult will be reflected in the EOleException.ErrorCode, otherwise (stdcall) will not raise an exception and the HResult will be returned by the ADsOpenObject function.
i would like to know whether a user inputs the correct combination of
Domain, User and Password for his Active Directory user.
You can use LogonUser function to validate user login e.g. :
if (LogonUser(pChar(_Username), pChar(_ADServer), pChar(_Password), LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, hToken)) then
begin
CloseHandle(hToken);
//...
//DoSomething
end
else raise Exception.Create(SysErrorMessage(GetLastError));
Note: You must use LogonUser within a domain machine to be able to use the domain login or the function will always return The user name or password is incorrect
The alternative is using TLDAPSend e.g. :
function _IsAuthenticated(const lpszUsername, lpszDomain, lpszPassword: string): Boolean;
var
LDAP : TLDAPSend;
begin
Result := False;
if ( (Length(lpszUsername) = 0) or (Length(lpszPassword) = 0) )then Exit;
LDAP := TLDAPSend.Create;
try
LDAP.TargetHost := lpszDomain;
LDAP.TargetPort := '389';
....
LDAP.UserName := lpszUsername + #64 + lpszDomain;;
LDAP.Password := lpszPassword;
Result := LDAP.Login;
finally
LDAP.Free;
end;
end;
How can i check if the user set the correct password or not in a more
reliable way using a similar technique?
Try to use FormatMessage function
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,
nil,
myResult,
LANG_ENGLISH or SUBLANG_ENGLISH_US,
lpMsg,
0,
nil);
MessageBox(0, lpMsg, 'Msg', 0);
I get (HRESULT) 0x8007052e (2147943726) "unknown user name or bad password" when I use a wrong password. And there is no EOleException, try:
hr := ADsOpenObject('LDAP://'+ ADomain + '/OU=Domain Controllers,' + APath,
AUser, APwd,
ADS_SECURE_AUTHENTICATION or ADS_READONLY_SERVER,
IID_IADs, pObject);
if (hr=HRESULT(2147943726)) then ShowMessage ('wrong username or password')

Prevent inserting an empty memo

I am checking the contents of the memo before an insert query.
if memo1.lines.Text = '' then begin
showmessage('Warning:Missing data!');
abort;
end else ....
It works ok as long as there is nothing in the memo1.
However, when user hits enter inside the empty memo and the cursor moves
to the second line,running the query, fires the insert without a warning message,
though theres nothing in the memo.
Is there a way to prevent this ?
After the user hits enter your memo's text contains whitespace (the line-return), and so it doesn't equal ''.
You need to Trim the Text first. http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.Trim
var
memoText : String;
...
memoText := Trim(memo1.lines.Text);
if memoText = '' then
begin
showmessage('Warning:Missing data!');
abort;
end else ....
If the user pressing RETURN is resulting in an additional line in the memo, then you must have the WantReturns property set to TRUE, and an OnKeyPress or OnKeyDown event handling the #13 key or VK_RETURN virtual key code.
If you set WantReturns to FALSE then the user must use CTRL+ENTER to insert a new line, and a simple striking of the RETURN key will not insert an empty line.
But if you are happy with the way your user interface currently behaves and simply want to check whether or not the memo contains only whitespace then you will have to validate this separately using Trim(Memo.Text) or some other mechanism for testing the content of the memo to meet your applications definition of "not empty".
As others have noted, using Trim() on Memo.Text simply in order to test for the presence of non-whitespace characters is potentially very inefficient, though how much of a concern this is in your case will depended a great deal on the expected content of the memo in your application.
A more efficient way to test for a non-whitespace string would be:
function ContainsOnlyWhitespace(const aString: String): Boolean;
var
i: Integer;
begin
result := FALSE;
for i := 1 to Length(aString) do
if (aString[i] > ' ') then
EXIT;
result := TRUE;
end;
Which would be used thus:
if ContainsOnlyWhitespace(Memo.Text) then
begin
// show warning message etc...
end;
This will be significantly more efficient than Trim() since it does not involve producing any new strings or modifying the string being tested.

Database Username and Password in Delphi?

I would like to create a type of login window in Delphi. Unfortunately I can't get it to match the username and password.
I have a basic .mdb database, with an Users table. In this table, there is a Username and Password. I want Delphi to check the username and password in the database and if it matches those in the edit boxes, it goes to the next form or shows a message, otherwise it does nothing. When I enter the database's first row, username and password values, I get success, but with the second, nothing. I feel like I need a way to get it to move on to the second rows values and check those and so on. There is also currently no relationships in the database.
This is my layout of Data Access: ADOConnection -> ADOTable -> DataSource
Thanks in advance!
As per your guess, one solution can be to move record by record to check each one. Example:
function MatchPass(Table: TADOTable; const Name, Pass: string): Boolean;
begin
Result := False;
Table.First;
while not Table.Eof do begin
if Table.FieldByName('Username').AsString = Name then begin
Result := Table.FieldByName('Password').AsString = Pass;
Exit;
end;
Table.Next;
end;
end;
Can be called like:
if MatchPass(ADOTable1, Edit1.Text, Edit2.Text) then
..
Another solution can be to let the ADOTable search for a corresponding record:
function MatchPass(Table: TADOTable; const Name, Pass: string): Boolean;
begin
Result := Table.Locate('Username;Password', VarArrayOf([Name, Pass]), []);
end;

Change Password Delphi v7

Delphi v7
This code is designed to allow the user to change his password. It appears to execute correctly, but the new password is not saved in the the password data field. I must have done something wrong, but I cannot see it.
procedure TForm4.btnPswordClick(Sender: TObject);
var
I: integer;
begin
tblLogin.First;;
for I := 0 to tblLogin.RecordCount do
Begin
If tblLogin.FieldByName('Username').Value = Edit1.Text then
if tblLogin.FieldByName('Password').Value = Edit2.Text then
sign2.Visible := True; //Test in this case tells the application to make Label1
visible if the //username and password are correct
tblLogin.Next;
end;
I:= I+1; //ends search loop of records so program can move on
If sign2.Visible = False then
begin
MessageDlg('Error Username, or Password not correct',
mtConfirmation, [mbCancel], 0);
end
else
if edit3.Text <> edit4.Text then
begin
MessageDlg('Error New Password does not match',
mtConfirmation, [mbCancel], 0);
end
else
begin
tblLogin.Edit;
tblLogin.FieldByName('Password').Value := Edit3.Text;
tblLogin.Post;
//form1.Show;
//form4.Close;
end;
My comment about formatting your code was just me being snide, but in fact I think it really would have helped you find the error yourself. Properly indented, your first loop is this:
tblLogin.First;;
for I := 0 to tblLogin.RecordCount do
Begin
If tblLogin.FieldByName('Username').Value = Edit1.Text then
if tblLogin.FieldByName('Password').Value = Edit2.Text then
sign2.Visible := True; //Test in this case tells the application to make Label1 visible if the
//username and password are correct
tblLogin.Next;
end;
The next line of code is this:
I:= I+1; //ends search loop of records so program can move on
The comment suggests that you expect that line to cause the loop to terminate. But that line isn't in the loop. If it were in the loop, your code never would have compiled because you're not allowed to modify the loop-control variable inside a loop. Even outside the loop, the compiler should have warned you that the current value of I is undefined. That's because the compiler makes no guarantees about the final value of a loop-control variable once the loop has terminated. Adding 1 to an undefined value is still an undefined value.
Furthermore, your loop iterates over more records than your database table contains. If the upper bound of a for loop hasn't had 1 subtracted from it and isn't the result of High or Pred, then you're probably doing something wrong.
Since your I+1 line doesn't actually terminate the loop, you end up traversing all the way to the end of the table, so when you call tblLogin.Edit, you're putting the final record into edit mode, not the record with the matching account details.
Fixing the indentation will also show that you didn't even include all the code for that function. There's a begin without a matching end somewhere.
You could avoid all this code by using a single UPDATE statement on your database:
UPDATE tblLogin SET Password = ? WHERE Username = ? AND Password = ?

reading 2 lines from IniFile

Trying again. On advice, adding the piece of code that I do understand. I am fine with the fact that I have to save 4 bits of information in two lines like so:
IniFile.WriteString('TestSection','Name','Country');
IniFile.WriteString('TestSection','City','Street');
My question is more about loading this information back into the form. If in my IniFile I have saved for example the following code
[TestSection]
John=Uk
London=barlystreet
Mike=Spain
Madrid=eduardostrata
Emma=USA
New York=1st Avenue
Made up information in the IniFile. Added through the code above.
Now my question is: How could I load for example, when I type in an edit box Mike, the rest of the belonging information.(Spain, Madrid,eduardostrata).
That's not how an INI file works. You save name=value pairs, and have to have a way to associate them.
Maybe this can help you get started:
Ini := TIniFile.Create(YourIniFileName);
try
Ini.WriteString('Mike', 'Country', 'Spain');
Ini.WriteString('Mike', 'City', 'Madrid');
Ini.WriteString('Mike', 'Street', 'EduardoStrata');
finally
Ini.Free;
end;
Results in your INI file containing:
[Mike]
Country=Spain
City=Madrid
Street=EduardoStrata
To load back:
var
Country, City, Street: string;
Ini: TIniFile;
begin
Ini := TIniFile.Create(YourIniFilename);
try
Country := Ini.ReadString('Mike', 'Country', '<None>');
City := Ini.ReadString('Mike', 'City', '<None>');
Street := Ini.ReadString('Mike', 'Street', '<None>');
finally
Ini.Free;
end;
// Country, City, and Street now equal the values for 'Mike',
// or they contain '<None>' if the section 'Mike' doesn't
// exist or has no values for the variable.
end;
So you can probably figure out how this works. The section (the part in []) is the person's name, and the name/value pairs are the location and it's corresponding value (for instance, 'Country=Spain').
You have clearly not understood how INI files work. What if both John and Dave live in New York? You cannot have two keys with the same name in an INI file. (In addition, you shouldn't rely on the ordering of the lines within each section.)
You thus need to rethink how you save your data. A very simple solution is to use a plain text file in which each line is an item in your database, and the fields are seperated by, for instance, a vertical line (|):
John|Uk|London|barlystreet
Mike|Spain|Madrid|eduardostrata
Emma|USA|New York|1st Avenue.
How to read this file? Well, that is trivial, and you should know how to do that. If you have a very specific question, then feel free to ask.
What INI files are for
But what are INI files for, then? Well, a typical application of a INI file is to save program settings. For instance, when you quit a text editor, it might save the settings to settings.ini:
[Font]
Name=Consolas
Size=10
[Behaviour]
AutoIndent=1
AutoReplace=1
AutoBrackets=1
BracketHighlight=1
SyntaxHighlight=1
[Window]
Width=800
Height=600
Maximized=0
etc. This is done by
WriteString('Font', 'Name', Editor.Font.Name);
WriteInteger('Font', 'Size', Editor.Font.Size);
etc. And when you start the application the next time, it will read the file to restore the settings:
Editor.Font.Name := ReadString('Font', 'Name', 'Consolas');
Editor.Font.Size := ReadInteger('Font', 'Size', 10);
etc., where the last parameters are the default values (in case the field is missing in the INI file). Notice that in each section, the keys are unique (and need to be), and that we don't care about the relative order of the keys inside each section.
The easiest way to create the relationship you want is to have all of a user's details on 1 line of text. This is not a job for INI files. Your issue is how to parse strings.
First of all, why do you need to save the "repeat password"? that doesn't make sense to me. Usually a UI will ask the user to repeat the password as a form of validation, but that's all it's good for. There's no benefit in storing it for later retrieval is there.
I think you need to save the user's first_name, last_name, and password (3 strings). Have a look at the following piece of code.
procedure SaveUserDetails(sFileName: string);
var
sFirstName, sLastName, sPassword: string;
slUsers: TStringList;
begin
sFirstName := txtFirstName.Text; // these could be from TEdit controls for example
sLastName := txtLastName.Text;
sPassword := txtPassword.Text;
slUsers := TStringList.Create;
slUsers.Add(sFirstName + ',' + sLastName + ',' + sPassword);
slUsers.SaveToFile(sFileName); // that has saved your stringlist to a file
slUsers.Free;
end;
The file will look this
Shane,Warne,cricket
Now, how to load it...
procedure LoadUserDetails(sFileName: string);
var
sFirstName, sLastName, sPassword: string;
sTemp: string;
slUsers: TStringList;
iPos: integer; // string position marker we'll use to split the string in 3
begin
slUsers := TStringList.Create;
slUsers.LoadFromFile(sFileName); // this loads the file's contents into stringlist
sTemp := slUsers[0];
if (Length(sTemp) > 0) then // just to check that there is data there
begin
iPos := pos(',', sTemp); // get position of first comma (our "delimiter")
sFirstName := Copy(sTemp, 0, iPos-1); // firstName everything upto 1st comma
sTemp := Copy(sTemp, iPos + 1, Length(sTemp)); // chop off bit we just read
iPos := pos(',', sTemp); // get position of second comma
sLastName := Copy(sTemp, 0, iPos-1); // LastName everything upto 2nd comma
sTemp := Copy(sTemp, iPos + 1, Length(sTemp)); // chop off bit we just read
sPassword := sTemp; // that's it
end;
slUsers.Free;
end;
Now... this far from "good code" but now you know at least 1 way to do your thing. Hope that helps.

Resources