I have a database in Access 2003 that I only want certain people to be able to access.
In my database I have a table which lists the people that should be able to access the database. (Tbl_BIRT_Users). The table contains their name, ntlogin and email address. It also has an 'adminstrator' field.
My question has two parts:
1 - On opening the database how can I get it to lookup the ntlogin (environ username) of the person and ensure that that person is authorised to use the database?
2 - I need the database to look at the 'administrator' Yes/No field and grant read only access to non admins and full access to admins.
Thanks!
Use an API call to get the login name - API: Get Login name You can change environment variables while at the command prompt and then, if start Access executing from the command prompt, Access will use the spoofed environment variable.
Also there are ways of easily breaking table driven security such as the user taking the backend database home to a retail copy of Access resides, changing the values in the tables and bringing the database back to the office.
Even if you trust the users not to fiddle with their environment variables, please adopt Tony's suggestion anyway. After you add the module he linked, retrieving the user's account name is a simple call to the fOSUserName() function. It's really no more difficult than getting it from the user's environment.
But I want to add to Tony's point about "easily breaking table driven security". Your plan is to check whether the user is one of your authorized users. My suggestion is to place your back end database file in a location where only your authorized users can get at it. Use Windows file system permissions to keep everyone else out. That way you may decide you don't even need to check your table to determine whether the user is authorized. You could still use the table data to determine whether the user is an Admin or regular user. Or you might decide to keep the authorization check if it gives your managers peace of mind ... even though it doesn't really offer much security.
Could you not just do something like this
Dim rst as Recordset
Dim sql as string
sql = "SELECT * FROM Tbl_BIRT_Users WHERE ntlogin = '" & Environ("UserName") & "'"
set rst = CurrentDb.OpenRecordset(sql)
if (rst.bof and rst.eof) then
/*not a valid user*/
DoCmd.Quit
else
if not rst!Administrator then
/*make read only*/
end if
end if
rst.close
This is the Access security window dressing I use.
Public Function SecurityCode()
'* Purpose: Limits access to program
Dim sUserID As String
Dim sUserName As String
'* Determines user from Windows Login
sUserID = Environ("USERNAME")
'* Lookup on BE table of Allowed Users to verify on the list.
sUserName = DLookup("[UserName]", "tbl_AllowedUsers", "ID = '" & sUserID & "'")
If Len(sUserName) > 0 Then
'Allowed User, opens Main Switchboard
'Set global variable for Admin rights
g_Admin = DLookup("[AdminRights]", "tbl_AllowedUsers", "ID = '" & sUserID & "'")
DoCmd.OpenForm "Switchboard"
DoCmd.SelectObject acForm, "Switchboard", True
DoCmd.RunCommand acCmdWindowHide
Else
'Not on the Allowed Users list, opens to a Password Page
DoCmd.OpenForm "frm_LockPage"
DoCmd.SelectObject acForm, "frm_LockPage", True
DoCmd.RunCommand acCmdWindowHide
End If
End Function
Try something like the below:
Function RealName()
payroll = Environ("Username")
firstname = DLookup("[first name]", "[Payroll Numbers]", "[persno] = " & payroll)
lastname = DLookup("[Last name]", "[Payroll Numbers]", "[persno] = " & payroll)
If IsNull(firstname) = True Then
RealName = payroll
Else
RealName = firstname & " " & lastname
End If
End Function
You can then enter code in the form_load event to make sure it's a verified user.
Related
I'm using Access 2013.
I want to disable buttons on some forms based on the user that is logged into the computer.
I'm using code from Dev Ashish to determine the name of the logged in user to the computer.
The username is stored in global variable LoggedInUser
I have a table called "Users" in which I am storing the login emails for the admins who need expanded functions. This table along with other fields has the following fields:
"Email" Short Text
"Active" yes/no
I want to check if the logged in user exists in the users table and if they are marked as Active.
I then want a global boolean variable called AuthUser to hold a true/false for if the user exists in the table and is active or not.
I think this might need to be done with a dlookup or dcount, but I just can't seem to make it work - any ideas on a solution?
You haven't shown your query that doesn't work?
I can guess - and you can tell me I guessed wrong
Here's something you can play with
dim rs as DAO.Recordset
strSQL = "SELECT Username from Users WHERE [Username] = """ & LoggedInUser & """ AND [Active]"
set rs = currentdb.openrecordset(strSQL)
AuthUser = Not rs.EOF
rs.close
Okay, I posted this question previously but only got half of the answer I require. To put simply I have an application which checks the log-in credentials with those stored in the My SQL Database. The awkward problem I have hear is that I've created it in the way that best suits me, and in a way that's simple for me to understand -- I'm not experienced with programming and self taught. The method I used - though prone to 'injection attacks' and other such dangers is suitable for my application and there's obviously security in my application (CD-Key checks & passwords.)
So, the actual details:
The connection is established successfully and I'm able to manipulate the data from the application.
Connection Code:
Public Sub conecDB()
'This is called upon program start.
Dim connectionString As String = SQLLoginIn.serveriptxt.Text 'This is the server IP/Server name. If server is intalled on your local machine, your IP should be 127.0.0.1 or you may use localhost
Dim strDbase As String = SQLLoginIn.databasenametxt.Text 'Database name
Dim strUser As String = SQLLoginIn.databaseusertxt.Text 'Database user
Dim strPass As String = "CENSORED" 'Database password
If connDB.State <> ConnectionState.Open Then connDB.ConnectionString = "Data Source=" & connectionString.Trim & ";Initial Catalog=" & strDbase.Trim & ";MultipleActiveResultSets=False;User ID=" & strUser.Trim & ";Password=" & strPass
If connDB.State <> ConnectionState.Open Then connDB.Open()
End Sub
That works great, for what I need it to do.
I add the records (of a separate table) to a listview in my program called lvRec
Code:
Public Sub dispRec(ByVal PstrSQL As String)
'This is called once the connection is established.
frmMain.lvRec.Items.Clear()
With comDB
.CommandText = PstrSQL
rdDB = .ExecuteReader
End With
Do While rdDB.Read
Item = frmMain.lvRec.Items.Add(rdDB!Members_ID.ToString)
Item.SubItems.Add(rdDB!Gamer_Tag.ToString.Trim)
'And a bunch of more
Item.SubItems.Add(rdDB!Games_Owned.ToString.Trim)
My.Application.DoEvents()
Loop
rdDB.Close()
End Sub
So now that you've seen how I'm working with SQL you're probably screaming "That's not how you do it!", but as I sayed, it works for me :) The problem occurs when I want to varifiy the user with their own Username & Password stored on a different Table called Logins.
I know I need to/can use the below code to query the users input with the log-in credentials in the Logins table, but I don't know how to return anything use-able like a Boolean result or something similar. I simply want to know if the credentials teh user entered are correct, so a TRUE for it existing would be great.
The SQL Query:
SQL = "Select * from logins " & _
"where Name like '%" & Me.Usernametxt.Text.Trim & _
"%' AND Passkey LIKE '" & Me.Passwordtxt.Text.Trim & "'"
I know this is a lot of detail for such a simple question but I needed people to understand the way I've went around this. Any help would be much appreciated.
Not an answer but to help you get started with authentication in a more robust fashion...
[Please bear in mind that you should never take an SO answer as gospel as all of us make mistakes and security is a very difficult topic to do right even for the professionals...]
The process you want to follow is something like the following (in pseudocode)
UserRecords = ExecuteSql("SELECT UserID, PasswordHash, Salt, IsEnabled FROM Users WHERE Username = '<Blah>'")
If UserRecords.Count <> 1
'Login Failed
End If
Dim UserRecord = UserRecords.First
If Hash(PasswordEnteredByUser & UserRecord.Salt) <> UserRecord.PasswordHash
'Login Failed
End If
If UserRecord.IsEnabled <> True
'Login Failed
End If
' Login was successful, continue as planned
A hashing function works by taking an input eg MyPassword! and converting it to something else eg AB0653AAAF330. Think of it as a sausage maker - you put a pig in one end and get a sausage out the other end. It's impossible to turn a sausage back into a pig. In theory, however, putting the same pig in twice will produce the same sausage. By storing the sausage, not the pig a database breach won't reveal users passwords
The "Salt" comes in to play when your database is breached. Imagine two users picked the same password. You know bob's password is MyPassword! and you can see it's stored as AB0653AAAF330. Anyone else with a password hash of AB0653AAAF330 has the same password - meaning multiple accounts could be breached at once.
If, on the other hand, there is a random string which is user-specific and appended to their password pre-hashing, Bob's password is treated as MyPassword!AAAAAAAAA which (say) hashes to 099DEF3333445 and Jane's (identical) password is treated as MyPassword!BBBBBBBBBwhich (say) hashes to AAAAE1234400.
In short, knowing Bob's password won't tell you that Jane has the same one.
This is still a hideously incomplete example. There are many other things to consider (eg the hashing algorithm. MD5 hashing is considered weak, bcrypt is intentionally computationally expensive to make brute-forcing more difficult, etc) but hopefully it will give you a rough idea to get you started.
You also want to make authentication binary - success or failure. If you tell the user "That username is invalid" (as opposed to a simple "Login Failed") as a help to them, they can use the feature to determine valid usernames by repetition.
I wholeheartedly recommend you read this article on how a company was compromised via SQL injection - once you know how it works in detail, you'll be able to see the pitfalls...
Another feature to consider is a failed login "window" - a sliding period of time in which a certain number of failed logins is allowed before that account becomes locked (possibly temporarily). This means brute-forcing passwords becomes impractical. This is usually handled by recording a FailedLoginAttempts and a FailedLoginTimeout against the user account. If a login fails, set the Timeout to now + 30 mins. If attempts > some threshold and the timeout hasn't expired, lock the account. If the login fails, extend the timeout. You get the idea.
Finally, if you're really security-conscious, when a user logs in successfully, tell them when they last logged in. If someone's been on holiday for a week and sees "Your last login was yesterday" they know instantly that something's wrong.
All the best.
On the general command on how to return a singular results from a SQL command:
Dim sqlConnection1 As New SqlConnection("Your Connection String")
Dim cmd As New SqlCommand
Dim success As Object
cmd.CommandText = "SELECT COUNT(*) FROM logins " & _
"WHERE Name = #Name " & _
"AND Passkey = #Passkey"
cmd.Parameters.AddWithValue("#Name",Me.Usernametxt.Text.Trim)
cmd.Parameters.AddWithValue("#Passkey",Me.Passwordtxt.Text.Trim)
cmd.CommandType = CommandType.Text
cmd.Connection = sqlConnection1
sqlConnection1.Open()
success = cmd.ExecuteScalar()
sqlConnection1.Close()
I am building a system using Microsoft Access 2013 that uses MySQL as a backend. I am trying to figure out a way to do some kind of basic user management, ideally in such a way that users would have to "log in" when they launch the database, and then their username would be easily accessible by the system while they are using it. I've tried searching for solutions, but most of them just tell me to use Office 365 or sharepoint, which are not options at the moment. Does anyone have an idea of how to accomplish this? Thanks in advance!
I recommend building your own user storage and login system. You'll basically need to create your own users table (in MySQL in your case), make forms to manage users, make a Login form, and write code to control the login process.
Logging in usually consists of checking some kind of credentials they type in against existing data in your users table. You can usually do this in Access with DLookup or DCount statements but I usually end up using a DAO or ADO recordset instead since I like to pull out more than one value from the User's table and I also like to write things back to it right away, like the LastLogin datetime, LastLogin computername, etc.
I actually wrote an example database which you can download here. It needs a rewrite. I've changed quite a few of my practices since Jan, 2011. But give me another year and it would need another rewrite.
I usually program the login form so that the user enters their initials and then a password. If you go this route you need to have a unique index setup on the Initials field to prevent duplicates. If you're going to have a lot of users you need to use a Username instead, which could still theoretically be the users initials.
Here's what my code would look like to authenticate a user. Be aware that this is far from truly secure. This assumes that passwords are stored in plain text. Users could theoretically try to do SQL Inject from here because I'm not use a parametrized query or stripping out special characters from their input such as # or ;.
Private Function AuthenticateUser() As Boolean
Dim sInitials As String
Dim sPassword As String
sInitials = Trim(Nz(Me.txtInitials, ""))
sPassword = Trim(Nz(Me.txtPassword, ""))
If sInitials = "" Or sPassword = "" Then
'Logging in with blank passwords is not allowed
AuthenticateUser = False
Exit Function
End If
If DCount("EmployeeID", "tblEmployees", "[Initials] = '" & Replace(sInitials, "'", "''") & "' AND Password = '" & Replace(sPassword, "'", "''") & "'", True) = 0 Then
MsgBox "Invalid Credentials."
AuthenticateUser = False
Exit Function
Else
Dim rs As New DAO.Recordset
Dim sSQL As String
sSQL = "SELECT * FROM tblEmployees WHERE initials = '" & Replace(sInitials, "'", "''") & "'"
Set rs = CurrentDb.OpenRecordset(sSQL)
If Not (rs.EOF And rs.BOF) Then
'Config is an instance of a User Defined Type. It could also be a class object.
Config.UserInitials = rs("Initials")
Config.UserFullName = rs("EmployeeName")
Config.UserID = rs("EmployeeID")
rs.Edit
rs("LastLoginDateTime") = Now()
rs("LastLoginComputer") = "Function Required to Get Computer Name"
rs("ProgVersion") = "Your Program Version Number"
rs("CurrentDbPath") = Left(CurrentProject.path & "\" & CurrentProject.Name, 254)
rs.Update
End If
rs.Close
Set rs = Nothing
AuthenticateUser = True
End If
End Function
In my applications I use a global object, in this case an instance of a User Defined Type, which I call Config. I store any kind of application runtime related settings in there for the duration of the runtime of the application. Of course this object gets destroyed when the user closes out of the application or when a code reset happens (which cannot happen in Access runtime, but does happen frequently during development). You could use a class object instead of a User Defined Type. Or you could use individual global variables for everything, which I don't recommend (that's what I used to do). A User Defined Type simply allows you to group global variables together and gives you an easy way to refer to them in your code during design time by typing in Config., which then brings up every option using Intellisense (assuming you have it enabled).
If you want your settings to survive a code reset, you need to use TempVars. TempVars became available with Access 2007. I do not use them now (contrary to my example database) because they are not strongly typed. There's no Intellisense to help you get the correct TempVar and you can technically refer to a TempVar that doesn't even exist and Access won't throw an error. I think TempVars is really just a Dictionary object with all of it's shortcomings, and the single benefit of surviving a code reset. I can imagine storing a Connection String in there, but I wonder if it's worth using TempVars for anything at all. If a code reset happens, my entire application needs to be reloaded anyway since I setup a lot of global objects and variables when the application first opens and the user first logs in.
FYI, in previous versions of Access there was user security built in. I think Microsoft discontinued that starting in 2007. I never really used it so I didn't miss it when it got discontinued.
I have an old Access db with a plethora of lookup tables. Supposedly it was a warehouse of sorts for a bunch of other dept-made access apps to link to. We want to kill it. But, id there a way to find out if any app is currently linked to it?
You need the full path and filename of all the Access apps; this may not be possible.
For those you can, loop through all the files:
connect to each database to test for link.
Loop through all the tables in TestForLinkDatabase.TableDefs
Check to see if there is a .SourceTableName and the .Connect = YourLookupTableWarehouse for each table. I think the SourceTableName is an empty string for local tables.
Keep track of #3. You can optionally stop checking the rest of the tables if you find a single instance in the other file.
Again, it is not foolproof, but would be a good exercise to get a grip on all the Access apps floating around your company.
*Code does not exclude system tables.
Private Sub CheckToSeeIfLinked()
Dim Dbs As DAO.Database
Dim Tdf As DAO.TableDef
Dim Tdfs As TableDefs
Dim wrk As DAO.Workspace
Set wrk = DBEngine.Workspaces(0)
Dim TestDatabaseForLinks As String
TestDatabaseForLinks = "C:\FileNameToCheck.mdb"
Set Dbs = wrk.OpenDatabase(TestDatabaseForLinks)
Set Tdfs = Dbs.TableDefs
For Each Tdf In Tdfs
If Tdf.Connect <> "" Then
Debug.Print "Table: " & Tdf.Name & " - Is Linked To: " & Tdf.Connect
Else
Debug.Print "Table: " & Tdf.Name & " is not linked"
End If
Next
If Not (Dbs Is Nothing) Then
Dbs.Close
Set Dbs = Nothing
Set Tdfs = Nothing
End If
End Sub
Move it to another directory. Linked tables have hard-coded paths.
Not while the database isn't in use. When it is in use you should see an LDB/LACCDB file. You can open it with Notepad to see the workstation name.
If you are using Access security you will also see the Access userid. Otherwise you will see "Admin"
Opening the .ldb/.laccdb file using notepad will show you both who's currently in the database and some of the workstations which were in the database. When a person exits Access their workstation name and Access login id, Admin unless you are using Access security, are left in a "slot" or record in the ldb file. This slot or record may get overwritten the next time someone enters the MDB depending on what slot or record is available previous to it in the ldb file.
Determining the workstation which caused the Microsoft Access MDB corruption
I manage an SQL Server 2005 Database and I would like to give read-only access to the necessary tables to a group of 20-30 networked users who are able to use the GUI in MS Access 2007 to write or modify their own queries to the database, with some help.
I would like to distribute an Access database with a single form that would create links to the necessary tables. All of these users are included in a group with read-only permissions to the SQL Server database. I can distribute a dsn file for the connection, but I haven't found a way to programatically create the links to the 50 or so tables they might need, with their network credentials from an otherwise empty Access database.
I found a line of VB code from answer to a similar question onstackoverflow (below), but I was wondering if there was any simpler way than running the modified command once for each of the 50 or so tables.
DoCmd.TransferDatabase acLink, "ODBC Database", "ODBC;DRIVER=Microsoft ODBC for Oracle;SERVER=myserver;UID=myuser;PWD=mypassword", acTable, "SCHEMA.TABLE", "TABLE", False, True
I just wrote an article last week detailing a way to quickly link all tables in an SQL Database to Access. Here are some Access methods that will help. Read the article for more instructions on using it.
You will need to reference the Microsoft ActiveX Data Objects library.
Sub LinkAllTables(Server As String, database As String, OverwriteIfExists As Boolean)
'Usage Example (link all tables in database "SQLDB" on SQL Server Instance SQO01, overwriting any existing linked tables.
'linkalltables "SQL01","SQLDB", true
'This will also update the link if the underlying table definition has been modified.
Dim rsTableList As New ADODB.Recordset
Dim sqlTableList As String
sqlTableList = "SELECT [name] as tablename FROM sysObjects WHERE (type = 'U')"
rsTableList.Open sqlTableList, BuildSQLConnectionString(Server, database)
While Not rsTableList.EOF
If LinkTable(rsTableList("tableName"), Server, database, rsTableList("tableName"), OverwriteIfExists) Then
Debug.Print "Linked: " & rsTableList("tableName")
End If
rsTableList.MoveNext
Wend
rsTableList.Close
Debug.Print "Done."
End Sub
Function LinkTable(LinkedTableAlias As String, Server As String, database As String, SourceTableName As String, OverwriteIfExists As Boolean)
'This method will also update the link if the underlying table definition has been modified.
'The overwrite parameter will cause it to re-map/refresh the link for LinktedTable Alias, but only if it was already a linked table.
' it will not overwrite an existing query or local table with the name specified in LinkedTableAlias.
'Links to a SQL Server table without the need to set up a DSN in the ODBC Console.
Dim dbsCurrent As database
Dim tdfLinked As TableDef
' Open a database to which a linked table can be appended.
Set dbsCurrent = CurrentDb()
'Check for and deal with the scenario ofthe table alias already existing
If TableNameInUse(LinkedTableAlias) Then
If (Not OverwriteIfExists) Then
Debug.Print "Can't use name '" + LinkedTableAlias + "' because it would overwrite existing table."
Exit Function
End If
'delete existing table, but only if it is a linked table
If IsLinkedTable(LinkedTableAlias) Then
dbsCurrent.TableDefs.Delete LinkedTableAlias
dbsCurrent.TableDefs.Refresh
Else
Debug.Print "Can't use name '" + LinkedTableAlias + "' because it would overwrite an existing query or local table."
Exit Function
End If
End If
'Create a linked table
Set tdfLinked = dbsCurrent.CreateTableDef(LinkedTableAlias)
tdfLinked.SourceTableName = SourceTableName
tdfLinked.Connect = "ODBC;DRIVER={SQL Server};SERVER=" & Server & ";DATABASE=" & database & ";TRUSTED_CONNECTION=yes;"
On Error Resume Next
dbsCurrent.TableDefs.Append tdfLinked
If (Err.Number = 3626) Then 'too many indexes on source table for Access
Err.Clear
On Error GoTo 0
If LinkTable(LinkedTableAlias, Server, database, "vw" & SourceTableName, OverwriteIfExists) Then
Debug.Print "Can't link directly to table '" + SourceTableName + "' because it contains too many indexes for Access to handle. Linked to view '" & "vw" & SourceTableName & "' instead."
LinkTable = True
Else
Debug.Print "Can't link table '" + SourceTableName + "' because it contains too many indexes for Access to handle. Create a view named '" & "vw" & SourceTableName & "' that selects all rows/columns from '" & SourceTableName & "' and try again to circumvent this."
LinkTable = False
End If
Exit Function
End If
On Error GoTo 0
tdfLinked.RefreshLink
LinkTable = True
End Function
Function BuildSQLConnectionString(Server As String, DBName As String) As String
BuildSQLConnectionString = "Driver={SQL Server};Server=" & Server & ";Database=" & DBName & ";TRUSTED_CONNECTION=yes;"
End Function
Function TableNameInUse(TableName As String) As Boolean
'check for local tables, linked tables and queries (they all share the same namespace)
TableNameInUse = DCount("*", "MSYSObjects", "(Type = 4 or type=1 or type=5) AND [Name]='" & TableName & "'") > 0
End Function
Function IsLinkedTable(TableName As String) As Boolean
IsLinkedTable = DCount("*", "MSYSObjects", "(Type = 4) AND [Name]='" & TableName & "'") > 0
End Function
In addition to what David proposed, you could have a local (client side) table listing the list of tables available through the SQL connection. You could then write a piece of VBA code that will browse this table to establish all corresponding connections:
Dim rsTable as DAO.recordset
set rsTable = currentDb.openRecordset("Tbl_Tables")
if rsTable.EOF and rsTable.BOF then
else
rsTable.moveFirst
Do while not rsTable.EOF
DoCmd.openDatabase .... 'enumerate here all needed paarmeters with rsTable.fields("tableName") in the string'
rsTable.moveNext
Loop
Endif
rsTable.close
set rsTable = Nothing
This piece of code was written on the fly, so I cannot garantee it will work 'as is'. This code could for example be launched at startup (through the autoexec macro) so that your users will have their links ready when they open their app.
The 'view-only' thing can be easily managed by listing corresponding users (or, if you have a Domain, the corresponding group of users) as 'data readers' on your SQL server.
Is there a special reason why you want to re-create the links every time?
It would be much simpler to create once the mdb with the linked tables, and distribute that mdb to your users.
You might also want to link SQL Server Views (instead of tables) to Access tables, in order to make sure it's read only, maybe pre-join some tables, and eliminate some fields they do not require.
Why not use an Active Data Project in Access?
Linked tables are really only useful if you also need local (unlinked) tables. If you can keep all the tables and views on SQL Server and leave the forms in Access, an ADP will work fine and won't require "linking" any tables manually or via scripting.
In response to Patrick below, if you don't want them mucking around creating queries in your real SQL Server store, create a second SQL Server database that they have rights to create and update queries in, and create VIEWs like the following:
CREATE VIEW mytable AS SELECT * FROM [real database].dbo.mytable
Thus, when you change your master data tables, you only have to make a change to the VIEW in their shared SQL Server database, not a change to each Access database.
Side advantage #1: the users can see each other's queries, thus giving a social aspect of easily sharing good queries.
Side advantage #2: since they are all in one place, you can use SQL Server to detect which user queries will break if you make a change to one of the read-only tables (by searching the view definitions created by Access).
If your SQL Server uses Windows security instead of SQL Server security, then you don't have to supply a username/password in your connect string.
Here's the standard way to do this:
on your development machine, create a DSN for your SQL Server database.
use FILE | GET EXTERNAL DATA | LINK TABLES to link to the tables via ODBC.
get Doug Steele's code to convert to DSN-less connect strings.
then just distribute the front end as is to your users.
The key to this scenario is using Windows security rather than SQL Server security -- Access silently passes the credentials when it requests the connection via ODBC. This is one reason I'd never use SQL Server security -- too much trouble!