Login Program in Delphi XE7 which gives an error: Parameter object is improperly defined. Inconsistent or incomplete information was provided - delphi

I have the following problem with a simple and basic program that I am writing in Delphi. It is a Login program where the user enters a username and password. The program will then get the password from an access database where the username equals to the username that the user entered. Then the program will compare the password that it got from the access database with the password that the user entered.
Here is a copy of my code:
(I have the following variables: Password, Username, sPassword)
Username := edtUsername.Text;
Password := edtPassword.Text;
UserQuery.SQL.Add('Select Password as Password1 from Users where Username = :Username');
UserQuery.Parameters.ParamByName('Username').Value := Username;
UserQuery.Open;
sPassword := UserQuery['Password1'];
if sPassword = Password then
begin
showmessage('Correct');
end
else
begin
showmessage('Incorrect');
end;
It saves the query value to a variable. If the username is correct and the password that the user entered is correct the program works fine.
My problem is that the second time or if anything like the username or password was typed in wrong by the user it gives me an error : Parameter object is improperly defined. Inconsistent or incomplete information was provided. I think it has to check if the query exists but I do not know how to do it. How can I solve this problem?
(I am still a learner)

The problem is, that every time your method is executed, you're adding a SQL statement into your query. If you inspect while debugging for its value, or just ShowMessage(UserQuery.SQL.Text) you'll clearly see it
It will look like this:
Select Password as Password1 from Users where Username = :Username
Select Password as Password1 from Users where Username = :Username
Select Password as Password1 from Users where Username = :Username
...
The query then fails because from the second to the last :Username parameter, no value is provided
There are a few ways to fix it. One of the may involve Clearing the Query then assign the SQL statement again:
UserQuery.Clear; // clear the query before adding the SQL statement
UserQuery.SQL.Add('Select Password as Password1 from Users where Username = :Username');
UserQuery.Parameters.ParamByName('Username').Value := Username;
UserQuery.Open;
Or you can just assign directly to the Text property which will replace the whole string with the new supplied value:
UserQuery.SQL.Text := 'Select Password as Password1 from Users where Username = :Username';
UserQuery.Parameters.ParamByName('Username').Value := Username;
UserQuery.Open;
Since you are using the same query over and over, the most ideal setup would be initialize it (on a constructor for example) and leave it on a Prepared state. A prepared SQL statement is preparsed and sent to the DB engine, leaving it ready to execute. It should be used when you have a query or command that you need to execute over and over and the only thing you change are the parameters values (just your case)
procedure TForm1.Create(Sender: TObject);
begin
UserQuery.SQL.Text := 'Select Password as Password1 from Users where Username = :Username';
// it's a good practice to set the parameter type
UserQuery.Parameters.ParamByName('Username').DataType := ftString;
// prepares the query: preparses sql, sends it to the DB engine, etc
UserQuery.Prepared := True;
end;
// usage
procedure TForm1.YourLoginMethod;
begin
UserQuery.Parameters.ParamByName('Username').Value := Username;
UserQuery.Open;
try
sPassword := UserQuery.FieldByName('Password1').AsString;
// perform login logic
finally
UserQuery.Close;
end;
end;
Just a couple things more to note. I recommend that, similar to set a parameters DataType, you use type-safe TField properties, aka .AsString, .AsInteger, etc
The default property of TDataSet will return a Variant for the given field value, and the perform an implicit conversion. I suggest being explicit since you know your data types better than the RTL
Also a try-finally block is needed here. When using prepared querys, every time you execute them, you need to have a closed query, set the parameters and then call open. The try-finally will grant that every time you open the query, it will be closed regardless of execptions that may ocurr

Related

Accept user input and set assign to variable PL/SQL

I am trying to accept user input inside of a stored procedure and assign it to a VARCHAR variable. For some reason I get error
PLS-00201: identifier 'userinput' must be declared.
Any ideas? I have to use this later down the line to see how many times the input appears in a table.
CREATE OR REPLACE PROCEDURE nums
AS
x_num VARCHAR(20);
BEGIN
x_num := &input;
dbms_output.put_line('You entered: ' || x_num);
END;
/
Procedures cannot receive user input in response to a prompt, PLSQL is NOT interactive. When you have that you are not actually communicating with the database. What is actually happening is symbol substitution were SQLPLUS or other interface (Toad , SQL Developer, ...) is actually intercepting symbol, requesting the input, and physically changing the script before submitting it to the database. If you want a stored procedure you will need to use a parameter as #HereGoes suggested and then provide the user a script as follows:
Begin
nums(pInput => &Input);
end ;
Or provide an application interface to receive the input value and call the procedure or allow user access through SQLPLUS or other interface and let them enter the script - not recommended.
I suggest making the input a parameter.
CREATE OR REPLACE PROCEDURE nums (pInput IN VARCHAR2)
AS
x_num VARCHAR(20);
BEGIN
x_num := pInput ;
dbms_output.put_line('You entered: ' || x_num);
END;
/

How do I use an ID throughout a Delphi app

I have a MySql database which has a users table and all the other tables are connected to the users table.
If we log in to an user that has id = 1. We want to make all the requests to mysql using the user_id = 1, so it'll be displayed only the data from this specific user.
I have a login screen in my Delphi app where the user can type his username and password. After a successfully login I want to take thie user_id to use and throughout this application to make the calls. How should I do it?
After the login in successfully, should I sotre this ID in Delphi somehow and access it whenever we want to make some call? If so, how do I save this id in Delphi so I can use it in several forms?
A way to do this is to add a DataModule to your project and make the UserID a
field of this.
So, using Delphi's default naming, you would have
type
TDataModule1 = class(TDataModule)
[...]
public
property UserID : Integer read FUserID;
end;
[...]
var
DataModule1 : TDataModule1;
Then, add to TDataModule1 the db-access components your project uses, e.g. a Query
component to access the Users table where user details are stored and a method to
try to login in a given user, and also a db-connection component of the type your db-access components use. So you could add a method like this:
function TDataModule1.LogInUser(const UserName : String) : Boolean;
begin
try
if qUsers.Active then
qUsers.Close;
FUserID := -1; // deliberately set an invalid UserID
Assert(Trim(UserName) <> '')); // raise exception if UserName is blank
// qUsers Sql.Text property would be something like 'select * from users where username = :username'
qUsers.ParamByName(UserName).Value := UserName;
qUsers.Open;
if qUsers.Locate('UserName', UserName, []) then
FUserID := qUsers.FieldByName('UserID').AsInteger;
finally
Result := FUserID >= 0; // assuming zero is a valid UserID
end;
end;
and save the module under the name, say, of DataModule1u. Notice that this method omits any reference to the user's password: This is because if you leave your db connection component's LoginPrompt property at the default True value, it will pop up a prompt for the user's password when the LogInUser method executes.
Once you've got that far, go to your Login form's unit and edit it to USE DataModule1u.
Then you can add code to it to use the LogInUser method like so:
if DataModule1.LogInUser(UserName) then begin
Caption := 'User: ' + IntToStr(DataModule1.UserID);
// do something else with DataModule1.UserID's value
end;
Obviously you could (and usually should) add all your db-access components to the datamodule
and USE it in your forms which do db-access.
Hope this is all clear, if not then ask.

I have a syntax error in my insert into statement

I'm using a MS Access database, with the following columns in the Admins table:
Column Type
====== ====
Name Text
Surname Text
Dateadded Date/time
Adminnumber Number(long integer)
Password Text
ID type Autonumber (Not sure if ID is relevant)
This is my code but it keeps giving me a syntax error.
ADOquery1.Active := false;
adoquery1.sql.Text := 'insert into Admins(Name, surname, Adminnumber, Dateadded,password)Values('''+edit11.Text+''', '''+edit12.text+''', '''+edit13.Text+''', '''+edit14.Text+''', '''+edit15.text+''')';
ADOquery1.ExecSQL;
Adoquery1.SQL.Text := 'select * from Admins';
ADOquery1.Active := true;
i have been trying for a day to figure it out but its the same error no matter what code i use. The error is
Project project1.exe raised exception class eoleException
with message 'Syntax error in INSERT INTO statement'.
i have also tried:
ADOquery1.SQL.Add('Insert into admins');
ADOquery1.SQL.Add('(Name , Surname, Dateadded, Adminnumber, Password)');
ADOquery1.SQL.Add('Values :Name, :Surname, :Dateadded, :adminnumber :Password)');
ADOquery1.Parameters.ParamByName('Name').Value := edit11.Text;
ADOquery1.Parameters.ParamByName('Surname').Value := edit12.Text;
ADOquery1.Parameters.ParamByName('Dateadded').Value := edit13.Text;
ADOquery1.Parameters.ParamByName('Password').Value := edit14.Text;
ADOquery1.Parameters.ParamByName('Adminnumber').Value := edit15.Text;
ADOquery1.ExecSQL;
ADOquery1.SQL.Text := 'Select * from admins';
ADOquery1.Open ;
But this code gives me a problem with the from clause
The problem is that Name (and possibly Password) is a reserved word in MS Access. It's a poor choice for a column name, but if you must use it you should escape it by enclosing it in square brackets ([]). You're also missing an opening parenthesis (() after your VALUES statement, and a comma after the :adminnumber parameter.
ADOquery1.SQL.Add('Insert into admins');
ADOquery1.SQL.Add('([Name] , [Surname], [Dateadded], [Adminnumber], [Password])');
ADOquery1.SQL.Add('Values (:Name, :Surname, :Dateadded, :adminnumber, :Password)');
ADOquery1.Parameters.ParamByName('Name').Value := edit11.Text;
ADOquery1.Parameters.ParamByName('Surname').Value := edit12.Text;
ADOquery1.Parameters.ParamByName('Dateadded').Value := edit13.Text;
ADOquery1.Parameters.ParamByName('Password').Value := edit14.Text;
ADOquery1.Parameters.ParamByName('Adminnumber').Value := edit15.Text;
ADOquery1.ExecSQL;
ADOquery1.SQL.Text := 'Select * from admins';
ADOquery1.Open;
(The error can't be moving around, as you say in the comments to your question. The only line that can possibly cause the problem is the ADOQuery1.ExecSQL; line, as it's the only one that executes the INSERT statement. It's impossible for any other line to raise the exception.)
You should make some changes here that are pretty important to the maintainability of your code.
First, break the habit immediately of using the default names for controls, especially those you need to access from your code later. You change the name by changing the Name property for the control in the Object Inspector.
It's much easier in the code to use NameEdit.Text than it is to use Edit1.Text, especially by the time you get to Edit14. It would be much clearer if Edit14 was named PasswordEdit instead, and you'll be happy you did six months from now when you have to change the code.
Second, you should avoid using the default variant conversion from string that happens when you use ParamByName().Value. It works fine when you're assigning to a text column, but isn't really good when the type isn't text (such as when using dates or numbers). In those cases, you should convert to the proper data type before doing the assignment, so that you're sure it's done correctly.
ADOQuery1.ParamByName('DateAdded').Value := StrToDate(DateEdit.Text);
ADOQuery1.ParamByName('AdminNumber').Value := StrToInt(AdminNum.Text);
Finally, you should never, ever use string concatenation such as 'SOME SQL ''' + Edit1.Text + ''','''. This can lead to a severe security issue called SQL injection that can allow a malicious user to delete your data, drop tables, or reset user ids and passwords and giving them free access to your data. A Google search will find tons of information about the vulnerabilities that it can create. You shouldn't even do it in code you think is safe, because things can change in the future or you can get a disgruntled employee who decides to cause problems on the way out.
As an example, if a user decides to put John';DROP TABLE Admins; into edit14 in your application, and you call ExecSQL with that SQL, you will no longer have an Admins table. What happens if they instead use John';UPDATE Admins SET PASSWORD = NULL; instead? You now have no password for any of your admin users.

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;

What protection scheme for my passwords?

I'm developing a software (for personal use) with Delphi.
But I have a problem, this is it :
-> There's a main password to access a some other password file.
-> When storing these password, i use as key the main password. I think, it's ok.
-> But how protect the main password, and allow modification of it ???
If I use a constant key (so stored in the code, in binary), It can be disassembled !
So, I'm crazy or there's a way to make this possible : Protect main password and derived passwords.
(Main password (choosen by user) -> use it as key when encrypting user data (other password and usernames related).
Thank you for your helps.
Excuse my bad english.
i'd like to suggest turning the problem on its head. Your Windows account is already protected with a password. The Win32 API provides a mechanism where you can have Windows encrypt data with your Windows password.
This means that your data is as secure as your Windows password; and you don't need to memorize a second password.
The Windows function CredWrite and CredRead allow storing and saving of credentials; of which i just happen to have a handy wrapper function already for storing credentials:
function CredWriteGenericCredentials(const Target, Username, Password: WideString): Boolean;
var
PersistType: DWORD;
Credentials: CREDENTIALW;
hr: DWORD;
s: string;
begin
if not CredGetMaxPersistType(CRED_TYPE_GENERIC, {var}PersistType) then
begin
Result := False;
Exit;
end;
ZeroMemory(#Credentials, SizeOf(Credentials));
Credentials.TargetName := PWideChar(Target); //cannot be longer than CRED_MAX_GENERIC_TARGET_NAME_LENGTH (32767) characters. Recommended format "Company_Target"
Credentials.Type_ := CRED_TYPE_GENERIC;
Credentials.UserName := PWideChar(Username);
Credentials.Persist := PersistType; //CRED_PERSIST_ENTERPRISE; //local machine and roaming
Credentials.CredentialBlob := PByte(Password);
Credentials.CredentialBlobSize := 2*(Length(Password)); //By convention no trailing null. Cannot be longer than CRED_MAX_CREDENTIAL_BLOB_SIZE (512) bytes
Credentials.UserName := PWideChar(Username);
Result := CredWriteW(Credentials, 0);
if not Result then
begin
hr := GetLastError;
case hr of
CredUI.ERROR_NO_SUCH_LOGON_SESSION: s := 'The logon session does not exist or there is no credential set associated with this logon session. Network logon sessions do not have an associated credential set. (ERROR_NO_SUCH_LOGON_SESSION)';
CredUI.ERROR_INVALID_PARAMETER: s := 'Certain fields cannot be changed in an existing credential. This error is returned if a field does not match the value in a protected field of the existing credential. (ERROR_INVALID_PARAMETER)';
CredUI.ERROR_INVALID_FLAGS: s := 'A value that is not valid was specified for the Flags parameter. (ERROR_INVALID_FLAGS)';
ERROR_BAD_USERNAME: s := 'The UserName member of the passed in Credential structure is not valid. For a description of valid user name syntax, see the definition of that member. (ERROR_BAD_USERNAME)';
ERROR_NOT_FOUND: s := 'CRED_PRESERVE_CREDENTIAL_BLOB was specified and there is no existing credential by the same TargetName and Type. (ERROR_NOT_FOUND)';
// SCARD_E_NO_READERS_AVAILABLE: raise Exception.Create('The CRED_TYPE_CERTIFICATE credential being written requires the smart card reader to be available. (SCARD_E_NO_READERS_AVAILABLE)');
// SCARD_E_NO_SMARTCARD: raise Exception.Create('A CRED_TYPE_CERTIFICATE credential being written requires the smart card to be inserted. (SCARD_E_NO_SMARTCARD)');
// SCARD_W_REMOVED_CARD: raise Exception.Create('A CRED_TYPE_CERTIFICATE credential being written requires the smart card to be inserted. (SCARD_W_REMOVED_CARD)');
// SCARD_W_WRONG_CHV: raise Exception.Create('The wrong PIN was supplied for the CRED_TYPE_CERTIFICATE credential being written. (SCARD_W_WRONG_CHV)');
else
s := SysErrorMessage(hr)+' (0x'+IntToHex(hr, 8)+')';
end;
OutputDebugString(PChar(s));
end;
end;
And a wrapper function to read credentials:
function CredReadGenericCredentials(const Target: WideString; var Username, Password: WideString): Boolean;
var
Credential: PCREDENTIALW;
begin
Credential := nil;
if CredReadW(Target, CRED_TYPE_GENERIC, 0, Credential) then
begin
try
username := Credential.UserName;
password := WideCharToWideString(PWideChar(Credential.CredentialBlob), Credential.CredentialBlobSize); //By convention blobs that contain strings do not have a trailing NULL.
finally
CredFree(Credential);
end;
Result := True;
end
else
Result := False;
end;
It should be noted that CredRead and CredWrite are themselves functions that turn around and use CryptProtectData and CryptUnprotectData.
These functions let you take some arbitrary blob, and encrypt it with the user account's password1, and then hand you back the encrypted blob. You can then store that blob wherever you like (e.g. registry or file).
Later you can have the blob decrypted, and can only be decrypted by the user who originally encrypted it.
This lets you have your dream of forcing you to deal with another password, but uses Windows to protect it.
"MyPassword04" --> CryptProtectData() --> "TXlQYXNzd29yZDA0"
You can store your encrypted password anywhere you like. Then later:
"TXlQYXNzd29yZDA0" --> CryptUnprotectData() --> "MyPassword04"
The suggestion i'm making is the ability to abandon passwords; leveraging the security of your own account.
Just a suggestion; you're free to consider, and reject, it.
Update
Additional helper functions.
Convert a PWideChar to a WideString (if there's a built-in (Delphi 5) function for it, i've never found it):
function WideCharToWideString(Source: PWideChar; SourceLen: Integer): WideString;
begin
if (SourceLen <= 0) then
begin
Result := '';
Exit;
end;
SetLength(Result, SourceLen div 2);
Move(Source^, Result[1], SourceLen);
end;
There are different "scopes" that you'r allowed to store credentails in:
CRED_PERSIST_NONE: No credential can be stored. This value will be returned if the credential type is not supported or has been disabled by policy.
CRED_PERSIST_SESSION: Only a session-specific credential can be stored.
CRED_PERSIST_LOCAL_MACHINE: Session-specific and computer-specific credentials can be stored.
Windows XP: This credential cannot be stored for sessions in which the profile is not loaded.
CRED_PERSIST_ENTERPRISE: Any credential can be stored.
Windows XP: This credential cannot be stored for sessions in which the profile is not loaded.
This function returns the highest supported persistence type for a given credential type (e.g. "generic" credentails). It's needed when you call CredWrite that you don't try to persist it in a location that isn't supported (i.e. in the domain when there is no domain):
type
TCredGetSessionTypes = function(MaximumPersistCount: DWORD; MaximumPersist: LPDWORD): BOOL; stdcall;
function CredGetMaxPersistType(CredType: DWORD; var MaxCredPersistType: DWORD): Boolean;
const
CRED_TYPE_MAXIMUM = 5;
var
_CredGetSessionTypes: TCredGetSessionTypes;
MaximumPersist: array[0..CRED_TYPE_MAXIMUM-1] of DWORD;
begin
_CredGetSessionTypes := GetProcedureAddress(advapi32, 'CredGetSessionTypes');
if Assigned(_CredGetSessionTypes) then
begin
Result := _CredGetSessionTypes(CRED_TYPE_MAXIMUM, PDWORD(#MaximumPersist[0]));
if Result then
MaxCredPersistType := MaximumPersist[CredType]
else
MaxCredPersistType := 0;
end
else
begin
SetLastError(ERROR_INVALID_FUNCTION);
Result := False;
MaxCredPersistType := 0;
end;
end;
Note: Any code is released into the public domain. No attribution required.
Encode your password file with the main password. Don't store that password anywhere; simply query it before decrypting the password file. If someone enters a wrong password, the password file will be scrambled.
What you could probably do is use a one-way hash for all passwords, without the need for a master password at all.
The nice thing with a hash is that it may be readable by everyone, they aren't any the smarter, since the only way to break a hashed password is a brute-force attack. Which is all the more time consuming that the hash is "large".
Of course, this won't hold if passwords stored are easily discoverable by a dictionary attack, but then is your master password secure?
Check out how password safe http://passwordsafe.sourceforge.net/ solves the problem.
Assemble the list-of-passwords-to-be-encrypted.
Generate two random 128 bit numbers using a secure random generator. Use the first as HMAC key for later authentication and integrity checks. Use the second as AES-CBC key for encrypting the list-of-passwords-to-be-encrypted. Append the HMAC output to the end of the encrypted list.
Generate a third random number. Use this as salt together with the password for deriving a key encryption key using a PBKDF. Use the key encryption key for encrypting the two random keys in step 2.
Optionally, generate a password verifier by hashing your password a sufficiently large number of times.
The final file should have the following layout, formatting omitted
[salt][password verifier][encrypted encryption key][encrypted hmac key][encrypted password list][hmac value]
You may be interested in our new SmartUtils Password SDK: http://sutils.com/index.php/smartutils-password-sdk It allows to store passwords with related info like URLs, usernames etc. in AES-256 encrypted database file. A master password may be encrypted using DPAPI in one line of code.

Resources