Using Delphi XE2.
I have a database with a table in it called users. It has a user_id field, username field, a password field and an Active field in it. (the user_id is a unique number that identifies each user... 1,2,3 etc).
I am writing a database software package which requires username and passwords to login to it. (I already have a login form created).
How do I get it to match/check the usernames and passwords in the database then allow user to continue into the software? Also I would like the Active Field to store in the database 'Y' if user is logged in or 'N' if user is not logged in. Is this doable?
I am connected to the users table via TADQuery and TDataSource.
Example below of function which I thought would get me started(calling it at point of clicking the login button on the login form).
function TfrmLogin.CheckUser: Boolean;
begin
while not dmData.qryUser.Eof do
begin
if(editName.Text <> qryUser.FieldByName('uname').AsString) or (editPassword.Text <> qryUser.FieldByName('pword').AsString)
then ShowMessage('Username and/or Password not recognised');
Exit;
End;
Looping through all rows in the database will not suffice, especially when you get more than a handful of users. You need to SELECT from the database for the specific user, and see if you get results back. You can do the SELECT based just on the username:
qryUser.SQL.Text := 'SELECT uname, pword FROM users WHERE uName = :uname';
qryUser.ParamByName('uname').AsString := editName.Text;
try
qryUser.Open;
if qryUser.IsEmpty then // No record found for user
// Handle error
else
begin
if qryUser.FieldByName('pword').AsString <> editPassword.Text then
// Handle password mismatch;
end;
finally
qryUser.Close;
end;
It's not clear from your question which database components you're using (TADQuery might be a typo for TADOQuery, or it might be something else). If in fact it is TADOQuery, you'll need to make a couple of small changes to the code. (Actually, only three minor changes; two in the assignment of the parameter and one that reads the password value.)
qryUser.SQL.Text := 'SELECT uname, pword FROM users WHERE uName = :uname';
qryUser.Params.ParamByName('uname').Value := editName.Text;
try
qryUser.Open;
if qryUser.IsEmpty then // No record found for user
// Handle error
else
begin
if qryUser.FieldByName('pword').Value <> editPassword.Text then
// Handle password mismatch;
end;
finally
qryUser.Close;
end;
I'm with Sam, if possible do not store passwords in the database. If the database supports Active Directory Authentication (MS/SQL, Oracle, DB2, MySQL, SyBase) use the user name and password to validate against Active Directory before trying a connection to the database. Then only store the user name and active flag in the Users table.
This function ask Active Directory if the user and password are valid, before you ever try to make a connection to the database. Then you can build your connection parameters to the database (I assume you are using FireDAC and a TADConnection see here for instructions). Try to open the connection, if it fails the user does not have access to the database at all. If it passes, then query the Users table like Ken suggest but test the active field instead of the password. This way no one can see a users password and you do not have to manage passwords in your application. By using this method someone still needs to know a users Active Directory password to access your application even though your using Active Directory Authentication on the database.
function TfrmPassword.ActiveDirectoryValidate: Boolean;
var
LHandle: THandle;
lDomainName: String;
begin
Screen.Cursor := crHourglass;
try
// Get the Domain Name
lDomainName := GetEnvironmentVariable('USERDOMAIN');
// Test the user Logon
Result := LogonUser(PWideChar(edtUserID.Text),
PWideChar(lDomainName),
PWideChar(edtPassword.Text),
LOGON32_LOGON_NETWORK,
LOGON32_PROVIDER_DEFAULT,
LHandle);
// If True, we got a Handle, so close it
if Result then
CloseHandle(LHandle);
finally
Screen.Cursor := crDefault;
end;
end;
Related
Without going into why I would like to do this, is it possible (I'll be using a login trigger) to log out a user that has no write permissions to a certain database?
I am able to find the currently logged in users permission, I just need to know if it's possible to log them out?
DECLARE #HasPermission bit
SELECT #HasPermission = HAS_PERMS_BY_NAME('RTEST2.dbo.TestTableSize', 'OBJECT', 'INSERT');
IF #HasPermission = 0
SELECT 'Now this is where id want to log out the user'
One can prevent a user from logging in by executing a ROLLBACK from within a login trigger. As #DavidBrowneMicrosoft mentioned in his comment, it's also a good practice to use a PRINT or RAISERROR statement so that reason for the login failure is logged. This message will not be returned to the client but may be useful for troubleshooting.
IF #HasPermission = 0
BEGIN
PRINT 'User does not have permissions to login';
ROLLBACK;
END;
dxStatusbar1.Panels1.Text :=
DataModule2.UniConnectDialog1.Connection.Username;
...gives me the username that has connected to sql server.
However the connected user has a different name in the actual database.
Example:
His login name for the sql server is 'John' and is user mapped to 'Northwind' database.
However in 'Northwind' database he is called 'John Smith'.
And this is the name (John Smith) I am trying to have displayed in dxStatusbar1.Panels1.Text
after he connects.
How can I get that ?
edit :
Tried Victoria suggestion :
UserName := DataModule2.UniConnection1.ExecSQL('SELECT :Result = CURRENT_USER', ['Result']);
dxStatusbar1.Panels[1].Text := UserName;
but get :
I couldn't find any UniDAC API way to get currently connected user name (not even for SDAC), so I would just issue a SQL command querying CURRENT_USER and grab the name from the result:
SELECT CURRENT_USER;
Or in the Unified SQL way with the USER function:
SELECT {fn USER};
Since you've mentioned stored procedure in your comment, it sounds to me like you probably want to get this information directly from a connection object without using query object. If that is so, you don't even need to have a stored procedure but execute directly command like this:
var
UserName: string;
begin
UserName := UniConnection1.ExecSQL('SELECT :Result = CURRENT_USER', ['Result']);
...
end;
Or in unified way:
var
UserName: string;
begin
UserName := UniConnection1.ExecSQL('SELECT :Result = {fn USER}', ['Result']);
...
end;
One of these might do the job for you. Haven't tested.
SELECT ORIGINAL_LOGIN()
SELECT SYSTEM_USER
SELECT SUSER_SNAME()
Hope it helps.
ORIGINAL_LOGIN: Returns the name of the login that connected to the instance of SQL Server. You can use this function to return the identity of the original login in sessions in which there are many explicit or implicit context switches.
SYSTEM_USER: Allows a system-supplied value for the current login to be inserted into a table when no default value is specified.
SUSER_SNAME: Returns the login name associated with a security identification number (SID).
I am trying to get a better understanding of the distinction between users & logins. I know how to create them, and I understand that they are required, but I don’t yet understand how SQL Server uses this distinction.
As far as I understand, a login has access to the Server, while a user has access to a database and its objects.
If I execute the following:
CREATE LOGIN fred WITH PASSWORD='…';
USE stuff;
CREATE USER wilma FOR LOGIN fred;
USE nonsense;
CREATE USER pebbles FOR LOGIN fred;
Then I have a Login of fred with two user names associated with two databases. My guess is that these user names can be regarded as aliases for fred.
I undersand that it is common to use the same username as the login name, but clearly not necessary.
The next step is to login as fred. I cannot login as one of the users.
At this point, I don’t see what happens next. How do I become one of the users, and what would do for me?
You never authenticate as a user.You authenticate as a login, which then maps to a single user in zero or more databases.
Create the login
CREATE LOGIN fred WITH PASSWORD='fredsecret', CHECK_POLICY = OFF;
GO
Create the users
USE stuff;
CREATE USER wilma FOR LOGIN fred;
GO
USE nonsense;
CREATE USER pebbles FOR LOGIN fred;
GO
Change context so it looks like I'm fred
SELECT SUSER_SNAME() --gbn
EXECUTE AS LOGIN = 'fred'
SELECT SUSER_SNAME() --fred
Note the differences
USE stuff
SELECT SUSER_SNAME(), USER_NAME()
USE nonsense;
SELECT SUSER_SNAME(), USER_NAME()
GO
and go back to me
REVERT
The name of the login (fred in this case) is only used for authentication to SQL Server. After authentication you mostly use the sid value: which links login (server principal) and user (database principal)
SELECT name, sid FROM sys.server_principals
USE stuff
SELECT name, sid FROM sys.database_principals
USE nonsense
SELECT name, sid FROM sys.database_principals
In my case, it is 0xC7C14DE4BFDF2445A7DABE158CC399F0
Note, sid is unique in a database. This will fail
USE nonsense;
CREATE USER barney FOR LOGIN fred;
GO
Msg 15063, Level 16, State 1, Line 10
The login already has an account under a different user name.
You connect to the server under the login, on base stuff will be CURRENT_USER wilma and on nonsense CURRENT_USER = pebbles
I created a new superuser just so that this user can run COPY command.
Note that a non-superuser cannot run a copy command.
I need this user due to a backup application, and that application requires to run COPY command
But all the restrictions that I specified does not take effect (see below).
What is the difference between user postgres and a superuser?
And is there a better way to achieve what I want? I looked into a function with security definer as postgres ... that seems a lot of work for multiple tables.
DROP ROLE IF EXISTS mynewuser;
CREATE ROLE mynewuser PASSWORD 'somepassword' SUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT LOGIN;
-- ISSUE: the user can still CREATEDB, CREATEROLE
REVOKE UPDATE,DELETE,TRUNCATE ON ALL TABLES IN SCHEMA public, schema1, schema2, schema3 FROM mynewuser;
-- ISSUE: the user can still UPDATE, DELETE, TRUNCATE
REVOKE CREATE ON DATABASE ip2_sync_master FROM mynewuser;
-- ISSUE: the user can still create table;
You are describing a situation where a user can write files to the server where the database runs but is not a superuser. While not impossible, it's definitely abnormal. I would be very selective about who I allow to access my DB server.
That said, if this is the situation, I'd create a function to load the table (using copy), owned by the postgres user and grant the user rights to execute the function. You can pass the filename as a parameter.
If you want to get fancy, you can create a table of users and tables to define what users can upload to what tables and have the table name as a parameter also.
It's pretty outside of the norm, but it's an idea.
Here's a basic example:
CREATE OR REPLACE FUNCTION load_table(TABLENAME text, FILENAME text)
RETURNS character varying AS
$BODY$
DECLARE
can_upload integer;
BEGIN
select count (*)
into can_upload
from upload_permissions p
where p.user_name = current_user and p.table_name = TABLENAME;
if can_upload = 0 then
return 'Permission denied';
end if;
execute 'copy ' || TABLENAME ||
' from ''' || FILENAME || '''' ||
' csv';
return '';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
COPY with option other than writing to STDOUT and reading from STDIN is only allowed for database superusers role since it allows reading or writing any file that the server has privileges to access.
\copy is a psql client command which serves the same functionality as COPY but is not server-sided, so only local files can be processed - meaning it invokes COPY but ... FROM STDIN / ... TO STDOUT, so that files on a server are not "touched".
You can not revoke specific rights from a superuser. I'm quoting docs on this one:
Docs: Access DB
Being a superuser means that you are not subject to access controls.
Docs: CREATE ROLE
"superuser", who can override all access restrictions within the database. Superuser status is dangerous and should be used only when really needed.
this is my sample access database:
ID--UserName--Password--AccountType
1---- A123 --1234 --User
2-----B123 --1345 --Admin
I am using VS2012. In my VB.net Project I have username textbox, a password textbox,
and login button.
I add my database using a wizard. I can add, modify, delete, and query, but how to check if the entered username in username text box exists in UserName column?
I filled up my dataset using:
Me.UsersTableAdapter.Fill(Me.WSDataSet.users)
and if I want to get the user type I am using:
Me.WSDataSet.users.FindByUserName(IDtxt.Text).AcountType
but the main problem if user not exists I get the error below:
An unhandled exception of type 'System.NullReferenceException' occurred in user login.exe Additional information: Object reference not set to an instance of an object.
How can I check if the username exists or not?
Try doing this.
Dim user = Me.WSDataSet.users.FindByUserName(IDtxt.Text)
If not user is nothing Then
'Do what you want with the user object
Else
'Message User does not exist.
End If
you just check if the user exists then do what you want with it.