tl;dr: how do i implement a permissions model like (e.g.) github's
Updated to try to address some of #philipxy's comments:
I am planning to implement a permissions model similar to github's:
users
users can be in groups
users can be in organizations
groups can be in organizations
a user will be permitted any of C, R, U, and D operations on an asset, group, or organization as:
an individual user who has been permitted those (any of C, R, U, D) operations
a member of a group which has been granted those permissions
a member of an organization that has been granted those permissions
or as a member of a group where that group belongs to an org that has permissions
a user is granted Read because the asset/group/org is viewable (readable) to anonymous users ("public")
a user should also have a set of permissions to say whether they can do any of C, R, U, or D on the permissions (a user can create a permission[C,R,U,D] for another user, a group, or an org)
a user can set the permissions for any asset, group, or org they create, or any asset, group, or org for which they have been given permission to set permissions.
These permissions will control who can perform Create, Read, Update, and Delete (CRUD) actions on assets, groups, and organizations in the site.
Approximately how do I model this?
Obviously i have these models:
Asset
User
Group
Organization
What next?
Permission?
PermissionType (to capture C/R/U/D)?
I am using mysql from node (via sequelize), but I can figure out specific syntax an all that myself, I just haven't yet figured out how to do this conceptually.
More to #philipxy's point:
The very thing you are proposing I do more of is indeed the thing I think I'm asking for help with. That is, those info design methods (NIAM, FCO-IM, ORM2, IDEF1X) are what i'm looking for. I know a decent amount about relational db implementation (days of learning normalizing and normal forms and whatnot), but indeed the process of specifying business requirements and converting them into actionable specs is the challenge.
ORM2 is difficult to find because of name collisions with the nodejs module. : I have downloaded the book linked from the NIAM wikipedia page
NIAM seems to be less common in usage nowadays?
FCO-IM: I have downloaded the book from their website
IDEF1X: also looks interesting
I guess I'm going to pick up a database text book.
More work toward predicates:
U identifies a User
A identifies an Asset
G identifies a Group
a User U can be in 0 or more Groups G
O identifies an Organization
a User U can be in 0 or more Organizations O
a Group G can be in 0 or more Organizations O
an asset A can be created by a User U
CRUD on Assets:
an Entity E can be permitted (through Permission P ?) to perform actions Ac on Assets
those Actions are:
Create
Read
Update
Delete
the Entity may be of types:
User
Group
Organization
Anonymous User/"the public"
details (shown only for Read, but also relevant for Create, Update, and Delete):
a User U0 can permit another User U1 to Read an Asset A
a User U0 can permit Users U who are members of Group G to Read an Asset A
a User U0 can permit Users U who are members of Organization O to Read an Asset A
Users U in Group G1, where G1 is a Group that is in an Organization O that has been permitted to Read Asset A, are therefore permitted Read Asset A
a Permission P that references an Asset A may only be created by certain users:
By default, the User U who is the creator of an Entity can create Permissions for that Entity,
but they may only reference Assets to which they have Permission (in the base case: those Assets create by U)
a User who as been Grant(?)ed the privilege can also reference Entity E in a Permission
Gr identifies a Grant
a Grant gives an Entity the privilege to create, read, update, or delete Permissions that reference another Entity
like Permissions, Grants have a transitive nature in that:
if Organization O has been Granted the privilege to (e.g.) modify Permissions for Entity E, then
not only may Users who are members of O modify Permissions referencing E,
but also Users who are members of any Group G where G is in O have the privilege to modify Permissions referencing E
Predicates and tables
A proposition is a statement that is true or false of a business situation. A predicate is a column-parameterized statement that given a row gives a proposition. A table (base or query result) holds the rows that make a true proposition from its predicate.
user (with id) U has name N
R is a grantor (may grant permissions)
user U has permission to update asset A
grantor R gave permission to grantor E to use an operator of type 'CRUD'
grantor E is of type 'user' AND grantor E has permission to update assets
Business rules
A business rule is an always-true statement that defines a term or describes a policy or process.
A user is uniquely identified by an id assigned when their cheque clears.
A crudable is an asset, group or organization.
A grantor is a user, group, organization.
"Grantee" refers to a grantor receiving or holding a permission.
Users can be in organizations.
You can make true statements that are parameterless predicates. These can use parameter names that are bound by FOR ALL & FOR SOME (THERE EXISTS). Business rules phrased in terms of such propositional predicates and/or table names are database constraints. Given User(U,N) & Grantor(R) as shorthands for the first two predicates above as predicates for tables User & Grantor, the following lines all say the same thing:
A user is a grantor.
FOR ALL U, if U is a user then U is a grantor.
FOR ALL U, (FOR SOME N, User(U, N)) IMPLIES Grantor(U).
(SELECT U FROM User) ⊆ (SELECT R AS U FROM Grantor).
FOR ALL U & N, User(U, N) IMPLIES Grantor(U).
FOR ALL U & N, (U, N) IN User IMPLIES (U) IN Grantor.
FOREIGN KEY User (U) REFERENCES Grantor (R); states what the above do (note its similarity to the middle two) plus that R is UNIQUE NOT NULL in Grantor.
Don't confuse rules with predicates. They have different uses & usually different forms. (A parameterless sentence template can be used as either.) A rule is a true statement; a predicate is a parameterized statement. Look at how my answer separates them. Base tables and query result tables have predicates. But a rule may suggest that you need a base predicate/table to record something. We have base predicates/tables when we see from a rule that we have to record some statements about the current situation. Note some rules inspire no base predicates.
You probably want to reify types and permissions.
A user is a grantor of type 'user'.
Permission named 'C' is permission for a grantee to create a crudable.
Grantor E is of type 'user'.
Permission P is of type 'CRUD'.
Grantor R gave permission P of type 'CRUD' on crudable C to grantee E.
Design is finding necessary & sufficient rules & base predicates
Here are relevant predicates to record situations that your exposition suggests arise.
users
U identifies a user
users can be in groups
G identifies a group
user U is in group G
users can be in organizations
O identifies an organization
user U is in organization O
groups can be in organizations
group G is in organization O
a user will be permitted CRUD operations on an asset, group, or organization
A identifies a crudable of type 'asset'
user U is permitted CRUD operations on crudable C
5.1 as an individual user, or as a member of a group, or as a member of an organization (or as a member of a group where that group belongs to an org that has permissions),
P identifies a permission
organization O is permitted CRUD operations on crudable C
or because the asset/group/org is viewable (readable) to anonymous users ("public")
crudable C is public
a user should also have a set of permissions to say whether they can set the above permissions
grantor R has permission to set CRUD permission for users on crudable C --?
What are "the above permissions"? Maybe you mean user CRUD permission and organization CRUD permission? Maybe you mean there are individual permissions for operations Create, Read, etc? You need to be clearer.
What are the permissions in "a set of permissions"? By "permission" here do you instead really mean "particular permission to a particular grantee"? You need to be more clearer.
The way to be clearer is to give rules & predicates that are as simple as possible but also not so simple that they don't mention relevant entities/values. You may afterwards want to generalize multiple rules & predicates into single ones. Eg instead of dealing with users, groups, organizations and assets, have grantors and crudables: Grantors may grant permissions. & grantor R gives permission P to grantee E. If some such permissions are also associated with specific grantees you might also need predicates like grantor R gives permission P to grantee E re permission Q and grantee F.
6.1. a user can set the permissions for any asset, group, or org they create,
user U created crudable C
or any asset, group, or org for which they have been given permission to set permissions.
user U has permission to set permission P for crudable C --?
You will want to record things like that user U has name N and ....
Learn about database design
Search re database/SQL subtyping/inheritance/polymorphism idioms. Eg user, group and organization are types of permission possessors & holders; I made them subtypes of a type grantor. Maybe you want some kind of permission target type that is the union of crudable & grantor. Maybe you want types of permissions. Maybe some permission permissions have associated grantees. Maybe 'C', 'R', 'U' & 'D' are permissions, and 'CRUD' is a type of permission. You probably want to record what grantor gave what permission to a grantee.
Later we can replace tables by their join if the join is on a shared PK/UNIQUE with the same set of values in both. When we can join on a PK/UNIQUE & FK we can replace tables by one like their join but with the FK nullable. There are yet other times we can replace multiple tables by one without problems. But first identify basic predicates.
Learn about relational database design. Follow some information design method. Best are members of the the NIAM/FCO-IM/ORM2 family. Peek at IDEF1X. Don't rely on products.
Learn about constraints. They follow from predicates and business rules. They are truths about possible business situations in terms of the predicates. Equivalently, they are truths about possible database states in terms of the tables. Also learn about constraints in SQL, both declarative (PK, UNIQUE, FK) & triggered.
It seems to me that you need to create the concept of an entity which can receive permissions on a repository or an organization.
In this model the problem becomes relatively simple, as the permissions would revolve around an RepositoryPermissions table of the form:
(EntityId, RepositoryId, canCreate, canRead, canUpdate, canDelete)
and a OrganizationPermissions table of the form:
(EntityId, OrganizationId, canCreate, canRead, canUpdate, canDelete)
There are two types of Entities Groups and Users, and the permissions of any user will need to be checked in four ways:
Direct User permissions on the Repository level
User membership in a Group with permissions on the Repository level
Direct User permissions on the Organization level
User membership in a Group with permissions on the Organization level.
the following query should retrieve all permission entries pertaining to the user someUserId on repository someRepoId
SELECT
rp.canCreate,
rp.canRead,
rp.canWrite,
rp.canDelete
FROM RepositoryPermissions AS rp
Left JOIN Users AS u ON u.EntityId = rp.EntityId
Left JOIN Groups AS g ON g.EntityId = rp.EntityId
Left JOIN GroupUsers AS gu ON gu.GroupId = g.GroupId
WHERE rp.repositoryId IS "someRepoId" AND (
u.UserId IS "someUserId" OR
gu.UserId IS "someUserId"
)
UNION
SELECT
op.canCreate,
op.canRead,
op.canWrite,
op.canDelete
FROM Repositories AS r
JOIN OrganizationPermissions AS op ON r.repositoryId = op.repositoryId
Left JOIN Users AS u ON u.EntityId = op.EntityId
Left JOIN Groups AS g ON g.EntityId = op.EntityId
Left JOIN GroupUsers AS gu ON gu.GroupId = g.GroupId
WHERE r.repositoryId IS "someRepoId" AND (
u.UserId IS "someUserId" OR
gu.UserID IS "someUserID"
)
Well it is problematic when you try to make a database behave the same way as something like Github. Databases have a completely different paradigm. It is why is is just as bad to design databases based on how C# works. Not the same functionality or rules. However it is an interesting question because too few people pay attention to the permission model as part of their database design and just grant everybody rights to everything because it is easier.
In the first place, users should not be able to grant rights to others, only admins should do that. Users should NEVER be creating objects if you have a good database design. Database design has no business being handled by amateurs.
Users can be individuals or you can use application users where all database requests coming from a specific application have the same rights. Applications can even have multiple generic users such as XYZAdmin, XYZUser, XYZReadonly (often used for senior managers who need to be able to see the data but aren't going to adjust it.). From my experience the biggest problems with the generic users is that it becomes difficult to audit who changed what in the database (very important in a regulated environment) and some users may have more permissions than they really need.
In databases you have several basic types of permissions. You have permissions that extend to the whole database and object level permissions. You also have some specific server permissions such as the permissions to use Bulk insert or execute jobs. Groups should also be given permissions concerning which databases they can see on the server as most database servers have multiple databases. So you can grant a user permissions to write to any table or grant them no rights to tables but only to specific stored procedures or views. In general administrative personnel get the overall rights to everything (or server level rights such a Bulk insert rights). This includes DBAs who have full rights and other specialists such as data analysts/senior developers/build team members who may only have rights concerning jobs and data entry or creating new objects but not rights to perform tasks such as setting permissions. All other users should be locked in to object level permissions.
Now no one should be given permissions as an individual (or at least it should be a rare thing). Each person should be in various groups which have rights. Individuals can be in more that one group and how those right interact between groups can be explained in the documentation associated with the specific database product you are using (yes this is specific by product, there is no one size fits all database permissions model.) The beauty of this is that when someone is no longer a user, you only have to remove them from the group(s) and the permissions are gone rather than searching out a multitude of individual permissions.
Now when you are handling permissions by groups, you need to define the groups. You may also need to define some additional views or stored procedures if you want a group to only have permissions on a subset of the records or columns instead of the whole table. If you have multiple clients in one database and need to make sure the permissions are only for one specific client, then you need to use stored procedures/views and grant permissions only at the stored procedure/view level not at the table level. Using views for permissions can get tricky because each database product has specific rules about what makes a view updateable. You need to understand this at a deep level to determine how you are going to manage permissions.
All database objects created will need to script the specific groups being granted permission to that object as part of the creation script. (You would never create database objects through any kind of GUI, always through scripts kept in source control.)
You can also do some permissions work by client by setting up some database tables with meta data about specific pages the user can access or specific clients that he is allowed to access. Then when the application is loaded, the users data from these tables is loaded and the application would decide what he or she could do. This is frankly easier but can be risky.
It means the users have to have table level permission and users really should not have those. Because the database level permissions are broader, it is easier for a malicious user to log in outside of the application and do things that he or she should not be able to do. This is something to be especially wary of if you have internal users who can easily log in through something other than the user's application such as SSMS for SQL Server.
If your database application is in a legally regulated field such as finance or health care, you need to be very strict about permissions and using ORMs instead of stored procedures is contraindicated because at no time should you be setting permissions at the table level.
Related
I need to check the roles and grants given to users, but from the metadata tables.
Basically, I need the metadata table, where I can query this, using multiple roles, eg. XXX, YYY,ZZZ. I need this to get the hierarchy of the roles that might have been granted.
I can do show grants OF role XXX - This'll give me all the users/ roles to which this role is granted, but I have to do for one role at a time.
If I do
SELECT *
FROM SNOWFLAKE.ACCOUNT_USAGE.GRANTS_TO_USERS
WHERE ROLE='XXX'
AND DELETED_ON IS null;
It gives me only the users who have been granted this role, not the roles itself.
If I check on GRANTS_TO_ROLES table - it gives me the actual privileges given for that role, but not the other roles to which the particular role is granted to.
What you can do is use the SHOW syntax rather than select:
show roles in account;
will give you the full list of roles (still coming from metadata). Not sure what exactly you want to do with them further, but in case you'd actually want to proceed the results as a query you can follow it by
select * from table(result_scan(last_query_id()));
and use to join with other tables or just to copy into some sort of temporary table and join further from there
You mentioned the hierarchy - I suspect you want to see which roles are granted to other roles..
Try this:
show grants of role Your_Role
In the table returned you will see that some roles are assigned to other roles and to users..i.e. column granted_to
I am trying to get all the roles a user is part of. In my case, the user is part of an admin role which inherits another role ingestor, this inherits another role analyst. If I query from snowflake like as follows:
show grants to user <userid>
This lists only the admin role but not other two roles (ingestor, analyst). If the same user logs into snowflake, he could see all three roles available for him in the role dropdown.
Need help to get all explicit roles irrespective of role inheritance.
As a start, the views "SNOWFLAKE"."ACCOUNT_USAGE"."GRANTS_TO_USERS" and "SNOWFLAKE"."ACCOUNT_USAGE"."GRANTS_TO_ROLES" in combination have the information you need,
but are only accessible to ACCOUNTADMIN
You also have:
SELECT * FROM "MY_DATABASE"."INFORMATION_SCHEMA"."ENABLED_ROLES";
SELECT * FROM "MY_DATABASE"."INFORMATION_SCHEMA"."APPLICABLE_ROLES";
The latter looks like a good place to start.
Edit primo 2023:
If you want to make your own near-instant expanded GRANTS_TO_ROLES, you can follow these lines:
Get roles with SHOW ROLES; RESULT_SCAN()
Iterate over roles above with SHOW GRANTS TO ROLE <role>; RESULT_SCAN()
Iterate over ALL_USER_NAMES() with SHOW GRANTS TO USER <user>; RESULT_SCAN()
Finally create a SELECT statement with a recursive Common Table Expression expanding the nested roles
i found the best way to find all roles with inherited roles.
just run below SQL.
SELECT CURRENT_AVAILABLE_ROLES()
My current Cube has two roles:
AdminSec: are for a few users only and refers to a Group of administrators in my Active Directory.
GlobalSec: refers to "Everyone" and gives everyone access to read all data.
We are now planning to incorporate a new department (MinimalDepartment) in our company. This department should not have access to very much of the information in our company, so my plan is to create a third role (DepartmentSec) and assigning a new Active Directory Group (MinimalDepartment) to this role. Also I will limit the access to my dimension data by deselecting all departments and only grant access to the relevant department in the Department dimension.
My question is: If an employee is a member of the Active Directory Group MinimalDepartment will they then only be able to see the data in the cube which the role DepartmentSec allows (which is what I want)? Or will they be able to see all data as they are also part of "everyone" and therefore also the role GlobalSec?
If it is the latter is it then possible somehow to create the role so "everyone" has access except those in AD-group MinimalDepartment?
It seems that I have to create a new group in my AD which contains all the departments which I want to include and then use this instead of "everyone". Fortunately my IT-supports could tell me that we already have this group so for me its not a problem.
I've been scratching my head lately over the relationship between database normalization and foreign keys with respect to junction tables and lookup tables.
I’ve currently have the following tables: Users, UserTypes, Roles, UsersInRoles, and Permissions. UserTypes is simply a lookup table providing the name of the type, with description, via a foreign key in the Users table. Roles are the various roles with associated Permissions linked to each User via the UsersInRoles table.
I need to come up with a structure that allows me to provide multiple Roles for each User, in addition to special permissions for each respective User that may not be covered in the fixed Roles of which they are a member.
I had a foreign key to my UsersInRoles table from the Users table, but decided that it just didn’t make sense. Conversely, it seems to make perfect sense to use a foreign key from the Users table to the UserTypes table. Is this the rule of thumb? That junction tables have foreign keys linking to the primary keys of the tables it joins, while master tables have foreign keys linking to the primary key of associated lookup tables?
Parameters:
Each User can have one or multiple Roles
Each Role has a fixed set of Permissions
Each User can have additional Permissions not provided by their Roles
I suspect I may also need a PermissionsInRoles junction table as well as one for PermissionsInUsers? But this is just ridiculous isn't it? There just must be a better way. I'm thoroughly convinced that I'm losing my mind here, lol. Any help would be greatly appreciated. This has got my head spinning :P
UPDATE
Is this basically how it would be setup? I might get rid of the UsersInRoles table so each user can only be in one role, and additional permissions can be added via the SpecialPermissions junction table. From a UI standpoint, I was thinking it might be good when assigning permissions to a user, selecting a "Role" would simply check the appropriate boxes associated with that role, then you customize that and submit. That way I think I would only need a junction table between the Users and Permissions tables perhaps? Ugh. This is quite daunting for a first time database designer haha. Remember when you were just starting out? Or maybe you guys are more of a genius than I am, lol.
Schema Image link (can't post images yet)
Here's a neat scholarly article (albeit 10 years old) on query-driven database design titled: "Robust Database Design for Diverse Query Patterns". The Conclusion section has an interesting approach.
I suspect I may also need a PermissionsInRoles junction table as well
as one for PermissionsInUsers?
Well, you already said one of the requirements was "Each Role has a fixed set of Permissions". So to fulfill that requirement, you need to store permissions that apply to each role.
Table: role_permissions
PK: (Role, Permission)
Role Permission
--
User Create
User Update
Admin Create
Admin Update
Admin Delete
You don't need two different tables to implement that requirement.
By the same token, you've already said "Each User can have additional Permissions not provided by their Roles". To fulfill that requirement, you have to store user-specific permissions.
Table: user_permissions
PK: (username, permission)
username permission
--
user1 Rename
user1 Leak to News of the World
user2 Randomly corrupt data
So, again, you don't need two different tables to implement that requirement. Both those tables are in 5NF.
But this is just ridiculous isn't it?
What's ridiculous?
That you have very elaborate requirements for permissions?
That you store business data (like permissions) in tables?
That it takes more than one table to model your permission requirements?
Something else?
If you want specific advice about your actual tables, edit your question and insert DDL for your tables.
Later
I looked at your diagram. Not every table needs an id number; id numbers have nothing to do with normalization.
If I were designing your system, I probably wouldn't use id numbers in the tables Roles, Permissions, and UserTypes until I saw a performance problem that id numbers could fix. (In most systems over the last 30 years, that means, well, almost never.) Before I used an id number, I'd also consider and test using a human-readable code instead. Human-readable codes often don't require joins; id numbers always require joins.
In most SQL dbms, you can combine a data type and check constraint in a CREATE DOMAIN statement. In PostgreSQL, you might use something like this to reduce the number of tables.
CREATE DOMAIN role AS VARCHAR(7) NOT NULL
CHECK (VALUE in ('Admin', 'User', 'Guest'));
Followed by
CREATE TABLE user_roles (
user_id integer not null references users (id),
role_name role
);
Replacing a table with a CREATE DOMAIN statement is most useful when the number of rows is stable and relatively small. Personally, I'd rather have the tables.
If you stick with id numbers, you also need UNIQUE constraints on Roles.RoleName, Permissions.Description, and UserTypes.UserType.
Otherwise, you seem to be doing fine.
If your securables are just a products table (or set of tables), and you're using SSMS (SQL-Server Management Studio) then you should not be inventing your own security schema from scratch.
What I would recommend is that you setup your users and roles in SSMS -- expand the Database, then --> Security --> Users, etc. Right-click a user, look for securables, and then you can assign the user to roles, or also, just objects (tables, etc.) directly. Or right-click roles and you have similar options. Whatever you do, stay away from creating your own security schema, if you can help it.
If you need your web app to have access to the database, then look into "utility accounts" (these are like users, created at the server level instead of the database level, but then you can bring them into your database from there.); or look into impersonation if you're able to pass users' creds from your internal network when they login to the database. Utility accounts or users can be assigned to roles, or granted direct access to database objects without roles -- whatever you need.
One thing I've done before, in a similar-sounding scenario:
Keep Roles and users in the same table.
Have an objects table (the tables/queries/forms/etc. you will be granting permissions to)
Have a permission table -- this is where you will link roles/users to objects (i.e., John can SELECT on table 1)
Finally, have an Inheritance table -- this is where you will link roles/users to each other (i.e., John has permission to do whatever Role1 can do)
For example -- a structure like this:
For example:
Users Table:
UserID -- User -- UserTypeID
1 ------- John Smith --- 1
2 ------- Sally Fort --- 1
3 ------- Public Role -- 2
4 ------- Special Role - 2
UserType Table:
UserTypeID -- Description
1 ----------- Human Being
2 ----------- Role
Objects Table:
1 -- Data-Entry Form 1
2 -- Data-Entry Form 2
3 -- Query 1
4 -- Table 1
Permissions Table
UserID -- ObjectID -- Permission
1 -- 1 -- Update (This says John can Update Data-Entry Form 1 (via direct permission))
3 -- 1 -- Update (This says that the Public Role can Update Data-Entry Form 1)
3 -- 2 -- Update (...as well as Data-Entry Form 2)
4 -- 3 -- Select (This says that the special role can Select from Query1)
4 -- 4 -- Insert (...the special role can Insert into Table1)
Permission Inheritance Table
UserID -- UserID_ToInheritFrom
1 -- 3 (this says John can do whatever the Public Role can do)
1 -- 4 (this says John can do whatever the Special Role can do)
2 -- 3 (this says Sally can do whatever the Public Role can do)
So then if you wanted to query, "What can John do?", you'd do something like this:
SELECT
ObjectID,
Permission
FROM
PermissionsTable
WHERE
UserID = 1 -- John has direct permission
OR UserID IN (SELECT UserID_ToInheritFrom FROM PermissionInheritanceTable WHERE UserID = 1)
-- John has indirect permission via the Permission Inheritance Table (or you can call it the "role
-- membership table" if that's easier for you to think of that way.)
This implementation works well. If you want to see a similar implementation, look at SQL-Server's implementation (or better yet, USE it, instead of re-creating the wheel from scratch.)
I have several entities which respresent different types of users who need to be able to log in to a particular system. Additionally, they have different types of information associated with them.
For example: a "general user", which has an e-mail address and "admin user", which has a workstation number (note that this a hypothetical case). Both entities also share common properties like first name, surname, address and telephone number. Finally, they naturally need to have a (unique) user name and a password to log in.
In the application, the user just has to fill in his user name and password, and the functionality of the application changes slightly according to the type of the user. You can imagine that the username needs to be unique for this work.
How should I model this effectively?
I can't just create two tables, because then I can't force a unique constaint on the user name.
I also can't put them all in just one table, because they have different types of specific information associated to them.
I think I might need 3 seperate tables, one for "users" (with user name and password), one for the "general users" and another one for the "admin users", but how would the relations between these work? Or is there another solution?
(By the way, the target DBMS is MySQL, so I don't think generalization is supported in the database system itself).
Your 3 tables approach seems Ok.
In users table have only ID, username, password,usertype.
In general users table have ID, UserID (from users table), other fields.
Same thing for admin users.
Usertype field will tell you from what table to search for additional info
if(usertype==admin)
select * from admins where userid=:id;
else
select * from general where userid=:id;
Two tables. USERS with user names, first, last, etc. ROLES with roles, and a link back to the user name (or user id or whatever). Put a unique constraint on the user name. Put workstation nbr, email, phone, whatever else you need, in the user table. Put 2 columns in the ROLES table -- USERID and ROLE.
You should decide how much specific information is being stored (or likely to be stored in the future) and make the decision based on that. If there are only a handful of fields for each user type then using a single table is alright.
USERS table (name, type, email, password, genfield1, genfield2, adminfield1, adminfield2)
Make sure to include the type (don't assume because some of the fields particular to that user are filled in that the user is of that type) field. Any queries will just need to include the "AND usertype = " clause.
If there are many fields or rules associated with each type then your idea of three tables is the best.
USERS table (ID, type, name, password)
GENUSERS (ID, genfield1, genfield2)
ADMINUSERS(ID, adminfield1, adminfield2)
The constraints between IDs on the table are all you need (and the main USERS table keeps the IDs unique). Works very well in most situations but reports that include both types of users with their specific fields have to be done in two parts (unioned SQL or subqueries or multiple left joins).
You can solve it with one 'general' users table containing the information thats available for all users and 1 table for every specific user type. In your example you will then need 3 tables.
Users: This table holds only information shared between all usertypes, ie. UserId, Name, Address, etc.
GeneralUsers: This table 'extends' the Users table by providing a foreing key UserId that references the Users table. In addition, information specific to general users are held here, fx. EmailAddress, etc.
AdminUsers: As with GeneralUsers, this table also 'extends' the Users table by providing a foreign key UserId referencing the Users table. In addition information specific to admin users are held here, fx. WorkstationId, etc.
With this approach you can add additional 'specializations' if the need arises by simply adding new tables that 'extends' the Users table using a foreign key reference. You can also create several levels of specialization. If for example admin users are general users as well as admin users then AdminUsers could 'extend' GeneralUsers instead of Users simply by using a foreing key to GeneralUsers instead of Users.
When you need to retreive data from this model you need to which type of user to query. If for example you need to query a GeneralUser you will need something similar to:
SELECT * FROM GeneralUsers
LEFT JOIN Users ON GeneralUsers.UserId = Users.UserId
Or if querying an admin user
SELECT * FROM AdminUsers
LEFT JOIN Users ON AdminUsers.UserId = Users.UserId
If you have additional levels of specialization, for example by having admin users also being general users you just join your way back.
SELECT * FROM AdminUsers
LEFT JOIN GeneralUsers ON AdminUsers.UserId = GeneralUsers.UserId
LEFT JOIN Users ON GeneralUsers.UsersId = Users.UserId
I most definitely would not do a model where you have separate tables as in GeneralUser, AdminUser and ReadOnlyUser.
In database design, a good rule of thumb is "Down beats across". Instead of multiple tables (one for each type), I would create a SystemUsers table, and a Roles table and define a join table to put SystemUsers in Roles. Also, I would define individual roles.
This way, a user can be added to and removed from multiple roles.
A role can have multiple permissions, which can be modified at any time.
Joins to other places do not need a GeneralUserId, AdminUserId and ReadOnlyUserId column - just a SystemUserId column.
This is very similar to the ASP.Net role based security model.
alt text http://img52.imageshack.us/img52/2861/rolebasedsecurity.jpg