Elements of Access Control
by Vladi Belperchinov-ShabanskiFebruary 13, 2008
Why Gates?
In a perfect world we wouldn't do things we should not. However the world is not like this; people do forbidden things sometimes. This also applies to computer systems used by more than one person. Almost everyone has tried to read someone else's email, view accounting department salary reports, or something else, or access otherwise hidden data.
I know you have never done this, but many people have.
In Construction
The simplest way to allow or forbid a user account to do something is to check if the account is in a list of permitted accounts somewhere. If you assume that everything is forbidden unless explicitly allowed, the access function can be as simple as:
# access_check() return 1 or undef
sub access_check
{
my $user_id = shift;
my @allow_users = @_;
my %quick_allow = map { $_ => 1 } @allow_users;
return $quick_allow{ $user_id };
}
my @allowed = ( 11, 12, 23, 45 );
print "User 23 allowed\n" if access_check( 23, @allowed );
print "User 13 allowed\n" if access_check( 13, @allowed );
print "User 99 allowed\n" if access_check( 99, @allowed );
# only "User 23 allowed" will be printed
Usually access control can be almost as simple as this function. Using user IDs for access control is simple, but tends to be hard to maintain. The problem appears with systems with many users or with public systems where new users may be created at any point. Access lists may become very large for each operation, which needs access controls.
One solution to this problem is access groups. Each user may be a member of several groups. The access check will pass if the user is a member of a group with permission for the required operation. This middle level in the access check isolates users from the access check directly. It also helps the system's design--you can associate preset access groups with all controlled operations at their point of creation. Subsequently created users only need to be attached to one or more of those groups:
# mimic real system environment:
# %ALL_USER_GROUPS represents "storage" that contains all
# groups that each user is attached to
my %ALL_USER_GROUPS = (
23 => [ qw( g1 g4 ) ],
13 => [ qw( g3 g5 ) ],
);
# user 23 is in groups g1 and g4
# user 13 -- in g3 and g5
# return list of user's groups. read data from storage or
# from %ALL_USER_GROUPS in this example
sub get_user_groups
{
my $user_id = shift;
return @{ $ALL_USER_GROUPS{ $user_id } || [] };
}
# access_check_grp() return 1 or 0
sub access_check_grp
{
my $user_id = shift;
my @allow_users = @_;
my %quick_allow = map { $_ => 1 } @allow_users;
my @user_groups = get_user_groups( $user_id );
for my $group ( @user_groups )
{
# user groups is listed, allow
return 1 if $quick_allow{ $group };
}
# user group not found, deny
return 0;
}
# this groups list is static and will not be altered
# when users are added or removed from the system
my @allowed = qw( g1 g2 g7 g9 );
print "User 23 allowed\n" if access_check_grp( 23, @allowed );
print "User 13 allowed\n" if access_check_grp( 13, @allowed );
print "User 99 allowed\n" if access_check_grp( 99, @allowed );
# only "User 23 allowed" will be printed
Storage
Probably the most popular storage for system data nowadays is the SQL database. Here is a simple example of how to store users, groups, and mapping between them. Three tables are required:
SQL CREATE statements:
create table user ( id integer primary key, name char(64), pass char(64) );
create table group ( id integer primary key, name char(64) );
create table map ( user integer, group integer );
TABLE USER:
Column | Type | Modifiers
--------+---------------+-----------
id | integer | not null
name | character(64) |
pass | character(64) |
TABLE GROUP:
Column | Type | Modifiers
--------+---------------+-----------
id | integer | not null
name | character(64) |
TABLE MAP:
Column | Type | Modifiers
--------+---------+-----------
user | integer |
group | integer |
Let's fill those tables with some data:
letme=# select id, name from user;
id | name
----+------------------
1 | Damian
2 | Clive
3 | Lana
(3 rows)
letme=# select * from group;
id | name
----+------------------
1 | Admin
2 | Users
3 | Moderators
(3 rows)
letme=# select * from map;
user | group
-----+-----
1 | 1
1 | 2
3 | 2
3 | 3
2 | 2
(4 rows)
Users in this example are attached to those groups:
Damian: Users, Admin
Clive: Users
Lana: Users, Moderators





