OrmAuth - Introduction
Ormauth is a set of authentication and authorisation drivers that provide a similar functionality as Simpleauth, but stores its data in the database instead of in a configuration file. The data is accessed through ORM models.
Besides this, it also comes with additional functionality. Unlike Simpleauth, Ormauth supports roles assigned directly to users, and permissions assigned to both users and groups, allowing for a much more fine-grained permission system. It stores the user's metadata not in a serialized array, but in a separate metadata table, using the ORM's EAV functionality, allowing you to access metadata like any other property of the user. It also keeps track of the previous login time, which can be displayed to the user at login time as an additional security measure.
Auth setup
Configuration starts with telling the Auth package that you are going to use the Ormauth driver. This is done through the auth.php configuration file. A default file is provided in the Auth package. You should copy this file to your app/config folder before making any changes. The default file is configured for the Simpleauth drivers, so you need to change that. You will find an explaination of this config file here.
After you have done this, you can choose to autoload the package through the always_load section of the app/config/config.php.
As OrmAuth uses the ORM to access the database, make sure you have added the 'orm' package to the always_load section too!
ACL's
OrmAuth has a much more fine-grained ACL system then SimpleAuth. It uses standard ORM relations to construct the permission set for any given user, and has the following features:
- Every user is a member of one, and exactly one, group
- Every user has zero or more roles assigned to it
- Every group has zero or more roles assigned to it
- Every user can have zero or more permissions assigned to it
- Every group can have zero or more permissions assigned to it
- Every role can have zero or more permissions assigned to it
- Every permission belongs to a permission area
- Every permission can have zero or more associated actions
All the permissions are aggregated per user. A role can have special permission filters, that may alter the assigned aggregated permissions. These are:
- "All access", stored as "A". Users with this role have all access. This is typically used for a 'super-admin' role. It overrules all permissions set.
- "No access", stored as "D". Users with this role have no access. This is typically used for a 'banned' role. It overrules all permissions set.
- "Revoke permissions", stored as "R". Permissions set on this role will be removed from the aggregated permissions set.
Note that "revoked" permissions are checked before all others. This allows you to create permission constructs like "all access for this super-admin, except to the top-secret area of the application"...
Assigning a permission is using standard ORM relations, and is very straitforward:
// get the Role identified by $role_id
$role = \Model\Auth_Role::find($role_id);
// get the Permission identified by $perm_id
$perm = \Model\Auth_Permission::find($perm_id);
// relate the two
$role->permissions = $perm;
// and save the relation
$role->save();
Actions
As mentioned above, you can add additional granularity by adding a list of actions to a permission (a combination of area and permission).
Actions are stored as an indexed array of strings in the permission record, serialized and unserialized automatically by the ORM. You can define as many actions as you want, and chose any strings you like. If needed this allows you to set a permission on each and every action on a form, more fine-grained then you would probably ever need!
The action list defined on a permission specify which possible actions can be assigned when assigning the permission to either a user, a group, or a role. The assigned actions are stored as an array of keys, that defines which of the actions were assigned.
// if these are the possible actions:
array('add', 'view', 'edit', 'delete')
// then you should store this when assigning 'view' and 'edit':
array(1, 2)
To store this, ORM Models have been provided to directly access the relationship or though table that connects either the user, the role or the group to the permission.
// get the Role identified by $role_id
$role = \Model\Auth_Role::find($role_id);
// get the Permission identified by $perm_id
$perm = \Model\Auth_Permission::find($perm_id);
// relate the two, adding a subselection of actions
$role->rolepermission[] = \Model\Auth_Rolepermission::forge(array(
'role_id' => $role->id,
'perms_id' => $perm->id,
'actions' => array(1,2),
));
// and save the relation
$role->save();
When checking for access, you specify the required access as either area.permission
(when you want to check for
a single right, area.[permission,permission,...]
when you want to check for multiple permissions at once, or if you
want to check for associated actions, you can use area.permission[action,action,...]
. This is an AND check,
so when you specify multiple rights, the user must have ALL of them assigned to be granted access.
This will allow you to construct checks like blog.comments[read,create,write,write-own,delete,delete-own]
.
Caching
To reduce database I/O, the OrmAuth drivers make heavy use of caching, to avoid having to retrieve the entire permission set for the logged-in user on every page request. Make sure your cache configuration is setup before you start using OrmAuth.
All cache entries are created with the prefix defined in the OrmAuth configuration file. They are created without expiration timestamp, so when you design your admin backend, make sure do delete the required cache entries after an update, so the cache can be refreshed.
The following cache keys are used by OrmAuth:
- <prefix>.groups - complete list of all defined groups
- <prefix>.roles - complete list of all defined roles
- <prefix>.permissions.user_<id> - effective permissions for user <id>
After an update to the permissions system, make sure to flush the cached permissions, and, if you have changed either role or group definitions, flush them too.
// flush the permissions of a single user (with id 12211)
\Cache::delete(\Config::get('ormauth.cache_prefix', 'auth').'.permissions.user_12211');
// flush all the cached permissions
\Cache::delete_all(\Config::get('ormauth.cache_prefix', 'auth').'.permissions');
// flush all the cached groups
\Cache::delete(\Config::get('ormauth.cache_prefix', 'auth').'.groups');
// flush all the cached roles
\Cache::delete(\Config::get('ormauth.cache_prefix', 'auth').'.roles');
Configuration
The Ormauth authentication system is configured through a configuration file, not suprisingly called 'ormauth.php'. A default file is provided in the Auth package. You should copy this file to your app/config folder before making any changes.
The following configuration values can be defined:
Param | Type | Default | Description |
---|---|---|---|
db_connection | string |
|
Name of the database connection to use. This should match the definition in your applications db.php configuration file. Set it to null to use the default DB instance. |
table_name | string |
|
Name of the users table to use. |
table_columns | array |
|
List of columns to select from the users table, or '*' to select all columns. You have to at least include 'username', 'password', 'email', 'last_login', 'login_hash', 'group' and 'profile_fields'. |
cache_prefix | string |
|
Prefix used for cache keys when caching ORM data. |
guest_login | boolean |
|
If true a dummy 'guest' user will be created if no one is logged in. This allows you to use the group and acl drivers even when no one is logged in. |
remember_me | array |
|
Configuration for the Ormauth 'remember_me' functionality |
multiple_logins | boolean |
|
If true multiple concurrent logins of the same user are allowed. If false, when a user logs in, any previous login will be cancelled. Note that enabling this will disable some login session hijacking measures! |
login_hash_salt | string |
|
To make the passwords used by the OrmAuth drivers extra secure, a salt value is used when hashing the passwords to store them into the database. Make sure you change this default to a very random string! To hash passwords, OrmAuth uses PBKDF2, a very secure hashing mechanism. |
username_post_key | string |
|
Name of the input field on the login form that contains the username. |
password_post_key | string |
|
Name of the input field on the login form that contains the password. |
If you want to use the 'remember-me' functionality, make sure you have a valid Crypt configuration, as it uses an encrypted cookie to store the user information to be remembered.
Database table
OrmAuth relies on a quite a few tables to store all information. The Auth package contains the required migration files to create these tables.
Just run oil refine migrate --packages=auth
to have these tables created for you.
Example
This is a sample login action:
public function action_login()
{
$data = array();
// If so, you pressed the submit button. Let's go over the steps.
if (Input::post())
{
// Check the credentials. This assumes that you have the previous table created and
// you have used the table definition and configuration as mentioned above.
if (Auth::login())
{
// Credentials ok, go right in.
Response::redirect('success_page');
}
else
{
// Oops, no soup for you. Try to login again. Set some values to
// repopulate the username field and give some error text back to the view.
$data['username'] = Input::post('username');
$data['login_error'] = 'Wrong username/password combo. Try again';
}
}
// Show the login form.
echo View::forge('auth/login',$data);
}