There are two core objects involved in the Jet security system: the system database and the Workspace object. The system database, which can only be created by the Access Workgroup Administrator, stores user and group account information. The Workspace object is used to open databases in the context of a user account. Permissions are then assigned to user and group accounts for database objects.
Once a system database has been established, the primary tasks in administering Jet security are creating and maintaining user and group accounts and assigning permissions to database objects.
One common cause of confusion centers around the Admin user account. Admin, with the exception of being the default account used by Jet and being a member of Admins by default, has no special authority in a database. In fact, the Admin user would be better named as "Default", since that's really all it is - the default account. Unfortunately, the name was poorly chosen by the developers.
The term "admin" was probably used a little too liberally in Access, creating confusion for users. Admin user, Admins group, Administer permission, etc. It would have been less confusing if some variation of "admin" were used for some of these, but unfortunately its something you're going to have to learn to live with. The true security key in Access is the Administer permission - which grants authority to change permissions and ownership. The Administer permission is always available to the Admins group and by default also is available to the Admin user.
Remember that the authority of the Admins group applies only to members of the Admins group in the system database in use at the time the database was created. This is a key point which cannot be overlooked if you are securing a Jet database. If you are using the default Access system database, your application will not be secure. Anyone opening the database using the default Admin account will have Administer permission on all database objects.
I've used this capability to my advantage in Access for objects that I use as templates. When creating new reports, for example, I have a blank template with several properties, controls, and procedures already set up. Using the security menu, I then revoke my own permission to modify the design. This prevents me from accidentally changing the design of the template. If I ever do need to modify the template, I can simply grant myself the Modify permission, make and save my changes, then revoke Modify permission for myself - essentially making the object read-only again.
The database owner, database object owners and the Admins group have a interesting relationship. By default, Admins will always have Administer permission for database objects. Although the object owner or a member of Admins can revoke the Administer permission, a member of Admins can grant the permission to himself or the Admins group. There's really no effective means of preventing the Admins group of having access to an object. To demonstrate this, open a database as the database owner, go to the permissions dialog box, and attempt to set the permissions on the system tables (MSys...). You'll find that the database owner can change the permissions set by Jet on the system tables for the database, even though they are not owned by the database owner. (Don't do this with a production database!)
| Object Type | Permission | Description |
| Database | Open/Run | Open the database file. |
| Open Exclusive |
Open the database exclusively.
Note: Generally you should probably clear this permission for all users except those who actually need it. This will prevent users from inadvertently opening the database exclusively in Access by setting the "Exclusive" check box in the file open dialog. |
|
| Table | Read Data | Open the table and read the data in the records. |
| Update Data | Modify existing records. | |
| Insert Data | Create new records. | |
| Delete Data | Delete records from the table. | |
| Read Design |
Read the design specifications for the table.
Note: If any permission for the data in a table is granted, Read Design permission must also be granted. The database engine cannot present the data without being able to read the design. Revoking Read Design revokes all rights for the table. |
|
| Modify Design | Change the design of the table. | |
| Administer | Assign permissions and change ownership. | |
| Query | See Table | The permissions which can be assigned for queries are identical to those for tables. |
| Form | Open/Run | Open and use the form. |
| Read Design | Read but not modify the form design, including code. | |
| Modify Design | Change the form design. | |
| Administer | Set permissions or change ownership. | |
| Report | See Form | The permissions for reports are the same as those for forms. |
| Macro | Open/Run | Execute the macro. |
| Read Design | View but change the macro definition. | |
| Modify Design | View and change the macro definition. | |
| Administer | Set permissions and change ownership. | |
| Module | Read Design |
View but not change the code in the module.
Note: There is no administrative way to prevent someone from executing the code in a module. If you wish to prevent a user from running the code, you will have to build a switch in the code based on the current user account or group membership. |
| Modify Design | Change the code in the module. | |
| Administer | Set permissions and change ownership. |
There are a few things to consider regarding object permissions:
Here are the steps to follow to secure a new database:
Once the new owner account has been created and added to the Admins group, you can remove the Admin user from the Admins group. Remember, you must first add the new owner account to Admins before you can remove the Admin user since the Admins group must always have at least one member. At this point, you should also enable the login dialog box by assigning a password to the Admin user account.
Unless you have a need for a guest login, I also recommend that at this point you assign a difficult to guess password to the Guest user and remove the user from the Guests group. While the Guest account by default will have no permissions for any objects, disabling a free login gives you a small extra bit of insurance.
To protect the owner account, you should immediately assign it a password. (As always, make the password difficult to guess.)
Before creating any objects, you should setup the default permissions you will use for users and
groups. From the Security menu, choose Permissions.... In the permissions dialog,
you will see your user account, Admin, and Guest. If you select Groups, you'll see Admins, Guests,
and Users. Since at this point there are no objects in the database, each class of object will
have only the
Once you have completed these steps, you should quit and restart Access, logging in using the new
owner account and password. Before beginning to create database objects, you may wish to setup
whatever user and group accounts you intend to use and assign default permissions to the various
classes of database objects. This will make it easier to manage permissions later when you have a
large number of objects created.
You'll need to establish the value of the system database, user name, and password before creating
the workspace. Since the system database is typically a fairly stable property, you can probably
store the value in the registry using SaveSettings and GetSettings. (In a really stable environment,
you may even be able to get with coding the path to the system database as a constant.) The user
name and password, however, are typically more dynamic (they may change every time the program is
run). You will probably need to build a login dialog box to obtain these values.
Once a Workspace has been established, you can proceed to call the OpenDatabase method to open
your database file. Depending on the nature of the application and your preferred strategy for
developing the application, you may wish to declare the Workspace and Database object variables
so that they are persistent for the life of the application (created in the startup code as
global or module variables) or create them on demand. At a mimimum, you will wish to declare they
user name and password variables so their duration is the life of the program or the user will
need to provide them every time you open a database. The "conventional wisdom" is to create
data access objects on demand to lessen the possibility of a corrupted database if the
application or machine should crash while the application is open. If you are willing to risk
the possibility of corrupted files, you may choose to have the objects be persistent for the
life of the program. If you decide to create them on demand, keep in mind that you will
take a performance penalty for doing it.
These are the steps you need to take to manage user and group accounts:
Granting and revoking permissions for database objects is done by using the Permissions property
of (normally) the appropriate Document object. The Document objects are members of the Documents
collection of the associated Container object. These objects are a common point of confusion for
many Jet programmers, so let's take a closer look at them.
A Jet database has a large number of objects and collections. One of the most misunderstood is the
Containers collection and it's children. When you look at a database using the Access database
window, you'll see tabs for Tables, Queries, Forms, Reports, Macros, and Modules. Additionally,
when looking at the database programmatically, you can see a TableDefs collection (representing
saved tables), a QueryDefs collection (presenting saved queries), and from within Access, a Forms
and Reports collection. If you look at an object model chart of a database, you'll also see a
Containers collection having the following Container objects: Tables, Forms, Reports, Scripts, and
Modules. Here's what the various objects represent:
To assign permissions, take the following steps:
The following example revokes and then grants permission to delete data from a table for the Users
group:
In addition to managing object permissions, another common use for user accounts is to track user
activity. It can often be helpful to know who is creating and modifying data. However, in order
to do this, you will need to code the appropriate insert, update, and delete procedures for the
tables on which you wish to enable this type of tracking.
There are two general methods to enable this type of tracking. You can add fields to the table
to store the name of the user who created or last modified a record or you can use a log table.
Adding fields to the table is generally simpler, but using a log table can be more flexible and
may provide better performance.
Regardless of the method you choose, you may wish to restrict the number of tables which have
activity tracking in order to minimize the associated overhead and coding complexity. Using a log
table only for critical data and adding created by and/or modified by fields to other tables that
are less sensitive can also be an acceptable compromise.
Originally written by Joe Garrick
As an added security measure, I revoke all permissions from the Users group for all objects and
create my own equivalent of Users for the database. This ensures that only those users I have
expressly granted authority to will be allowed even to open the database. Additionally, since the
default Admin user will always be a member of Users, this also ensures that anyone who is not using
your system database will not be able to open the database.
Accessing a Secured Database
There are two keys to accessing a secured Jet database: the SystemDB property of the DBEngine
and the Workspace object. Before setting up a workspace, you must set the SystemDB property to
point to your system database so that you can use your user and group accounts, then use the
appropriate account to create a Workspace object. Let's take a look at a code sample:
Do NOT append your Workspace object to the Workspaces collection of the DBEngine. The Workspaces
collection is shared by all instances of the DBEngine, so if the Workspace is appended, another
application could loop through the collection and use your Workspace to bypass your login
dialog box.
Programming Jet Security
In most cases, you can use Microsoft Access to manage security for your application. Access has a
user interface for creating user and group accounts, assigning users to groups, and granting or
revoking permissions. While the interface is somewhat clumsy to use, it is sufficient for most
tasks. There are, however, some circumstances where you may need to manage security programmatically.
You may of course simply not like the Access interface and want to replace it. I don't like it
myself and would probably write a replacement if I had the time. Here are some of the problems
with the Jet security interface:
The database Container objects can be assigned permissions, but there is no interface in Access
for doing this. The most common reason to assign Container permissions would be to prevent users
from creating objects of a certain class.
The Access user interface - not the Jet engine - automatically puts user accounts in the Users
group. If you don't want users in the Users group, you can either programmatically remove them
or programmatically create them and simply not add them to the Users group.
If you want to change object permissions dynamically while the code is running, you'll need to
program the security settings. Keep in mind that an alternative to dynamically changing
permissions in the program is to simply perform the work in the context of a priviledged
Workspace object. Also remember that in either case, you'll need to provide a means of
creating a priviledged Workspace in code - meaning that somewhere you'll need to hard-code
the name and password of an account that's a member of the Admins group. Whether you code
the values as strings in the CreateWorkspace method, use string constants in the application,
or save the values in the registry or some other configuration file, you will be to some
extent exposing your security system to being cracked.
If you have end users that need to administer the accounts or permissions but don't have
Access available, you'll need to build your own user interface for managing security.
The lack of ability to see inherited permissions in particular is something I consider to be
a serious deficiency in the Access security interface. This can make it extremely difficult
to detect who has permissions for an object or what permissions any given user has for an
object.
You can find sample code in the Visual Basic help files.
Use the CreateUser method of the Workspace object. Keep in mind that this needs to be done in
the context of a user account that has the authority to create accounts.
Use the Append method of the Users collection of the Group object or use the Append method of the
Groups collection of the User object. This is often somewhat confusing since there are essentially
two means of seeing the same data. Each User object has a Groups collection that represents the
Groups of which they are a member. Additionally, each Group object has a Users collection that
represents the users which have membership in the group. You can use either method, but you should
probably make it a habit to consistently use one approach or the other rather than mixing and
matching both.
Use the Delete method of the Users or Groups collections of the Workspace object.
Object
Represents
Usage
TableDefs Collection
Saved Tables
Use the TableDefs collection to see or modify the structure of the table.
QueryDefs Collection
Saved Queries
Use the QueryDefs collection to see or modify the structure of queries.
Forms Collection
Open Forms
This represents currently open forms and is used only at run-time within Access.
You can see and modify form properties while the user is working with the form
or even design the form programmatically.
Reports Collection
Open Reports
This is the same as the Forms collection, except it refers to open reports. Again,
this collection is only used within Access and is not available in Visual Basic.
Tables Container
Saved Tables and Queries
Remember, tables and queries in Access share the same name space, thus the single
container for both types of objects. Use the Tables container to get to the individual
Document objects representing each saved table or query. It is the Document object that
has the Permissions property.
Forms Container
Saved Forms
Unlike the Forms collection, the Forms container provides access to all saved forms and
can be used to set form permissions.
Reports Container
Saved Reports
This is the same as the Forms container, but refers to reports. Use the Reports container
to set permissions for reports.
Scripts Container
Save Macros
Why the Access developers called this "Scripts" instead of "Macros" is beyond me, but there it
is. Scripts represents saved macros. Remember when assigning permissions for Macros that there
can be many individually callable macros within a single Script document object. You can only
set the permissions for the entire Document object, not the individual macros contained within
it.
Modules Container
Saved Modules
Use the Modules container to set design time permissions for code modules in Access. Remember,
there is no way to administratively prevent someone from running the code. You can only
grant or revoke permission to see it or modify it.
When modifying the Permissions property, the key to remember is that the value is a bit field
representing all available permissions. To grant a permission, use the bitwise Or operator
with the appropriate permission constant. To revoke a permission, use the bitwise And Not operator.
Be careful not to use the addition (+) or subtraction (-) operators.
Suggestions for Managing Accounts
As with any user level security system, you can save yourself a considerable amount of administrative
effort by using groups to assign permissions and using user accounts strictly to validate logins.
I generally divide users into several potential groups:
With one exception, I always assign permissions only to groups rather than users. That exception is
the database owner account. Rather than work with an individual user account, I create a special
account for administrative use. In this case, the database owner is a role rather than an
individual user. This account (often simply "dbo") is the account which actually owns the database
and often owns many of its objects. The account has complete authority over all objects and data.
Administrators manage the security system and control access to database objects. In a larger
organization, this may a separate group, but in most cases it will be one or two users who are
also database developers. This group will typically have full permissions on all objects.
This group is responsible for building and modifying database objects. In a smaller organization,
this group will also be responsible for administration. Developers will generally have modify
permission on all objects and normally will need to have full permissions on all data.
This group is responsible for managing vital data and business rules. The group typically will
have special access to sensitive or business critical data, values available for lookup tables,
and other special configuration items. These users will generally not have design permission for
objects but will have full permissions on most data. The only data which would normally be
unavailable to this group is data used strictly for configuration by the developers.
This are the typical front-line users of the database who will be doing data entry, ad-hoc
querying, and reporting. This group will normally have the ability to insert, update, and
delete data only for those tables that their job requires them to use.
These are users who either have not received training on the system or are in the process of
being trained on the system. Access to modifying and creating records is restricted until they
have learned the proper procedures and business rules that apply to the data.
In either case, you will need to modify your code to make sure that updates are properly logged.
If you are using Visual Basic to develop the application, this requires adding additional code
anywhere the data is changed. In an Access application, this can be more complex since you will
need to prevent users from opening the tables for modifications in the database window. One method
of doing this is to use the security system to make the appropriate tables read-only, then using a
priviledged Workspace in code to change data. Another method is to use queries designed with the
WITH OWNERACCESS OPTION clause in the query. This allows you to design a query that allows
modifications on a table for which the end user does not have permission. (In the Access query
design window, set the Run Permissions to Owners to enable this option.)
You can add one or two fields to track the user who created and if desired the user who last
modified a record. The problem with the approach is that you cannot see what modification was
done and you cannot track deletions. Additionally, you will be adding the overhead of retrieving
the fields if you use SELECT * FROM in a query. One benefit, however, is that you
do not need to join tables to retrieve the values when you need them.
By using a log table, you can track inserts, updates and deletes. A typical log table might contain
the name of the table being modified, the primary key for the row being inserted, updated, or
deleted, the user name performing the change, the date and time of the change, and the SQL
statement that changed the data. While this approach provides complete flexiblity and complete
accountability, it requires additional coding to managing inserting rows in the log table,
periodic maintenance of the log (removal of old records), and a more complex query to retrieve
the data. Additionally, this assumes that you can express the insert, update, or delete as a
SQL statement or can determine some other method of tracking the changes.
Comments
Post new comment