Development

sfGuardPluginExtraDocumentation

You must first sign up to be able to contribute.

sfGuard plugin - extra documentation

This wiki gives additional information on deploying the sfGuardPlugin for Symfony. It is intended for Symfony-beginners. Basically, it extends the existing README file with additional background information, comments and sometimes corrections on version 1.13 PRE.

As a beginning user of Symfony and the sfGuardPlugin, I had to collect my information from various sources to understand more of the framework and this plugin (specifically, the Symfony book and the Askeet guide). This process took quite some time and was very helpful. With this document I try to help everyone, as current documentation is correct but short and assumes a lot of background information and experience.

Introduction to authorization, security and authentication

The plugin documentation says 'The sfGuardPlugin adds authentication and authorization above the standard of Symfony'. But what is all this?

Basically, authentication & authorization is the limiting of access to (parts of) your application. It means that users have to log in (=authentication) to have access to certain areas. Different users can have different 'privileges' (=authorization). In this way you 'secure' your application for different types of users. Also, you can manage the privileges in the back-end.

If you read Chapter 6 of The Book ('Inside the controller layer', p.100 'Action Security'), this gives an idea on the security mechanism of Symfony. Here is a summary:

The access to 'actions' can be restricted in Symfony. The action-access-restriction is set in the file myProject/apps/myApplication/modules/myModule/config/security.yml (in italics is your own project, application and module).

If a user requests an action that is secured by security.yml, the user must:

  1. be authenticated (= logged in)
  2. have the right credentials (= necessary authorization privileges)

Access to parts of the application can be controlled by the 'credentials' in security.yml. For details, see page 103 (complex credentials) of the Symfony book.

Note: in this documentation the 'user' refers to the person that uses the application on the frontend. Symfony also has the sfUser object for handling of sessions.

Session-management is handled by the sfUser object (or $sf_user in templates). This is a singleton object and can be accesses from anywhere in your application. For example with $this->getUser()->setAuthenticated(true) which sets the status to logged-in and can be checked in your template with <?php if ($sf_user->isAuthenticated()) ?>.

You can add specific information to the sfUser-object to distinguish between different types of users by adding 'credentials' to the sfUser-object:

  1. $this->getUser()->setCredential('member') sets the type of login-user to 'member'.

The sfUser object is a session-object. This means that its information is available throughout your application as long as your session holds. In addition to credentials, you can add information to this sfUser-object that you need further on in your application by adding 'attributes', e.g. the ID of the user:

  1. $this->getUser()->setAttribute('member_id', 123, 'membernamespace') stores 123 as the member_id, which can be used in your application later by $this->getUser()->getAttribute('member_id'). The 'namespace' is name for a collection of attributes and is used for easy deletion of all the attributes related to the same namespace (deletion by $this->getUser()->getAttributeHolder()->removeNamespace('member')).

So, what happens if a user asks for an action that is 'secured' through the security.yml of a module?

  1. If the user is authenticated and has the right credentials, the action is executed
  2. If the user is not identified/authenticated, the default login action will be performed. The default login action is defined in 'settings.yml' of your application! (myProject/apps/myApp/config/settings.yml). The symfony book refers to 'security.yml', but this is an error and should be 'setting.yml'.
  3. If the user is identified but does not have the right credentials, the default secure action will be handled. Also this default secure action is defined in the 'settings.yml' config file of your application.

Both the default login and secure action will be re-defined to use the sfGuardPlugin. The templates for these actions can also be modified by yourself.

Enough on the standard security functions of Symfony, back to sfGuardPlugin!

sfGuard plugin

sfGuardPlugin gives you the model (user, group and permission objects) and the modules (backend and frontend) to secure your Symfony application quickly via a configurable plugin.

Installation

  • Install the plugin

    symfony plugin-install http://plugins.symfony-project.com/sfGuardPlugin

This command installs the plugin into Symfony and into your project under myProject/plugins/sfGuardPlugin. Look at it, and you can see that the plugin has its own config-, data-, lib-, and module-directory.

sfGuardPlugin adds 4 modules to your project in the directory '/plugins/sfGuardPlugin/modules':

  1. sfGuardAuth - for the frontend-application
  2. sfGuardGroup - for the backend-application
  3. sfGuardPermission - for the backend-application
  4. sfGuardUser - for the backend-application
  • Enable one or more of these modules in your settings.yml
    • For your backend application you need: sfGuardUser, sfGuardGroup, sfGuardPermission
    • For your frontend application you only need: sfGuardAuth

For your frontend:

    all:
      .settings:
        enabled_modules:      [default, sfGuardAuth]

or for your backend

    all:
      .settings:
        enabled_modules:      [default, sfGuardGroup, sfGuardUser, sfGuardPermission]

Installed plugins are not activated by default in Symfony, so you have to activate them per application by adding them into the 'settings.yml', e.g. for frontend or backend (You can set the activated plugins per application, as you might have different required plugins per applications).

  • Clear your cache

    symfony cc

Every time you add classes or functions, you have the clear the cache of symfony.

  • Rebuild your model

    symfony propel-build-all

Now, sfGuardPlugin has added 7 tables to your database! These are defined in '/plugins/sfGuardPlugin/config/schema.xml':

  1. sf_guard_user - The table with all users, contains the username and password etc.
  2. sf_guard_permission - The table with all permissions, including permission name and description
  3. sf_guard_user_permission - The table that n:m relates 'user' and 'permission', i.e. a user can have multiple permissions and a permission can be shared with multiple users.
  4. sf_guard_group - The table that defines groups
  5. sf_guard_user_group - The tables that lists the users of a group, a user can be part of multiple groups
  6. sf_guard_group_permission - The table that n:m relates 'group' and 'permission'
  7. sf_guard_remember - stores the the ip-address and remember-key of the users

Check whether the tables are added in your database; if not, an error has happened.

Later in the documentation (customize the sfAuthUser model) you can link your user-profile information to the standard 'sf_guard_user' table.

  • Load default fixtures (=database data for e.g. testing) (optional - it creates a superadmin user)
    symfony propel-load-data myApplication

Fixtures are used for loading data in your database model. This loads both your existing schema.yml of your application, as the schema.yml from the plugin into your database.

  • Optionally enable the "Remember Me" filter in filters.yml
    security:
      class: sfGuardBasicSecurityFilter

Secure your application

To secure a Symfony application:

  • Enable the module sfGuardAuth in settings.yml (myProject/apps/myApp/config/settings.yml) for your frontend application
    all:
      .settings:
        enabled_modules: [..., sfGuardAuth]

Do this by removing the '#' in front of the line in your default 'settings.yml'. Better still, copy the enabled lines above all the lines without '#'s; this gives you a cleaner view of your active settings.

  • Change the default login and secure modules in settings.yml (myProject/apps/myApp/config/settings.yml), and not in security.yml as described on p.101 of the book!
  all:
    .actions:
      login_module:           sfGuardAuth
      login_action:           signin

      secure_module:          sfGuardAuth
      secure_action:          secure

This changes the default behaviour in your application when a user is not logged-in or does not have the right credentials when requesting a page, as described before in the introduction of this wiki.

  • Change the parent class in myUser.class.php of your application (myProject/apps/myApp/lib/myUser.class.php)
    class myUser extends sfGuardSecurityUser
    {
    }

In standard Symfony without the sfGuardPlugin, myUser is based on sfBasicSecurityUser. With above change, myUser is instead based on sfGuardSecurityUser. The sfGuardSecurityUser is itself based on sfBasicSecurityUser, so you do not lose any functionality. sfGuardSecurityUser extends sfBasicSecurityUser by adding methods signIn and signOut (see myProject/plugins/sfGuardPlugin/lib/user). sfGuardSecurityUser is described in more detail later.

  • Optionally add the following routing rules to routing.yml
    sf_guard_signin:
      url:   /login
      param: { module: sfGuardAuth, action: signin }

    sf_guard_signout:
      url:   /logout
      param: { module: sfGuardAuth, action: signout }

    sf_guard_password:
      url:   /request_password
      param: { module: sfGuardAuth, action: password }

This routes the url .../login to the action 'signin' of the module sfGuardAuth. You can customize the url parameter of each route. This is optional because the above routing-rules are automatically registered by the plugin if you enable the module sfGuardAuth. However, if you want, you can disable the routing-rules by defining sfGuardPlugin_routes_register to false in the app.yml configuration file.

N.B.: You must have a @homepage routing rule (used when a user sign out), for example:

    homepage:
      url:   /
      param: { module: member, action: index }
  • Clear your cache

    symfony cc

Every time you add classes or functions, you have the clear the cache of Symfony.

  • Secure some modules or your entire application in security.yml
    default:
      is_secure: on
  • You're done. Now, if you try to access a secure page, you will be redirected to the login page. If you have loaded the default fixture file, try to login with admin as username and admin as password.

Adding your own your own user-(profile)-model: Customize the sfAuthUser model

The sfAuthUser model that comes with this plugin is quite simple. It contains:

  • username
  • algorithm
  • salt
  • password
  • created_at
  • last_login
  • is_active
  • is_super_admin

There are no email, first_name, birthday or whatever columns. However, there is an easy way to add whatever fields you need.

You would not want to change the plugin's schema.yml nor the sfGuardUser class directly, because you may want to install updates of the plugin without having to add your own fields again. Instead, you can add your own user-profile in another class, the user-profile class. By default, sfGuardUser looks for the associated user-profile-class sfGuardUserProfile, that you have to define yourself in your own schema.yml.

Here is a simple example of a sfGuardUserProfile class that you can add to your own schema.yml:

    sf_guard_user_profile:
      _attributes: { phpName: sfGuardUserProfile }
      id:
      user_id:     { type: integer, foreignTable: sf_guard_user, foreignReference: id, required: true, onDelete: cascade }
      first_name:  varchar(20)
      last_name:   varchar(20)
      birthday:    date

Here is a working example of a sfGuardUserProfile class that works with the doctrine version of sfGuardPlugin:

sfGuardUserProfile:
  columns:
    id: { type: integer, primary: true, autoincrement: true }
    user_id: { type: integer }
    firstname: { type: string(255) }
    lastname: { type: string(255) }
  relations:
    User:
      class: sfGuardUser
      local: user_id
      foreign: id
      type: one
      foreignType: one
      foreignAlias: Profile

user_id is the foreign-key to sf_guard_user. You can change this foreign key name and also the associated user-profile-class in app.yml:

    all:
      sf_guard_plugin:
        profile_class:      sfGuardUserProfile
        profile_field_name: user_id

You can now access the user profile via the user object:

    $this->getUser()->getGuardUser()->getProfile()->getFirstName()

    // or via the proxy method
    $this->getUser()->getProfile()->getFirstName()

The getProfile() method gets the associated user-profile-object or creates a new one if none already exists.

When you delete a user, the associated user-profile is also deleted.

Manage your users, permissions and groups in the backend

To be able to manage your users, permissions and groups, sfGuardPlugin comes with 3 modules that can be integrated in your backend application. These modules are auto-generated thanks to the Symfony admin-generator.

  • Enable the modules in settings.yml
    all:
      .settings:
        enabled_modules: [..., sfGuardGroup, sfGuardPermission, sfGuardUser]
  • Access the modules with the default route:

    http://www.example.com/backend.php/sfGuardUser

Customize sfGuardAuth module templates

By default, sfGuardAuth module comes with 2 very simple templates:

  • signinSuccess.php
  • secureSuccess.php

If you want to customize one of these templates:

  • Create a sfGuardAuth module in your application (don't use the init-module task, just create a sfGuardAuth directory)

  • Create a template with the name of the template you want to customize in the sfGuardAuth/templates directory
  • Symfony now renders your template instead of the default one

Note: When you define your own sfGuardAuth module within your application, and you set your whole application to 'secure', your sfGuardAuth will also be secure..... This gives an endless loop. So, if you define your own sfGuardAuth module, set this module 'is_secure: off' in security.yml.

Customize sfGuardAuth module actions

If you want to customize or add methods to the sfGuardAuth:

  • Create a sfGuardAuth module in your application

  • Create an actions.class.php file in your actions directory that inherits from BasesfGuardAuthActions
    <?php

    class sfGuardAuthActions extends BasesfGuardAuthActions
    {
      public function executeNewAction()
      {
        return $this->renderText('This is a new sfGuardAuth action.');
      }
    }

sfGuardSecurityUser class

This class inherits from the sfBasicSecurityUser class from Symfony and is used for the user object (typically of class myUser) in your Symfony application (because you configured it in factories.yml already).

So, to access it, you can use the standard $this->getUser() in your actions or $sf_user in your templates.

sfGuardSecurityUser adds some methods:

  • signIn() and signOut() methods
  • getGuardUser() that returns the sfGuardUser object
  • a bunch of proxy methods to access the sfGuardUser object directly

For example, to get the current username:

    $this->getUser()->getGuardUser()->getUsername()

    // or via the proxy method
    $this->getUser()->getUsername()

It is important to understand the permissions model provided by sfGuardPlugin. Symfony actions are restricted according to each user's credentials, as described above. But sfGuardPlugin associates users with permissions. So how do these two marry up?

The answer is provided by sfGuardSecurityUser and sfGuardUser (via its parent class PluginsfGuardUser). When a user signs in, the sfGuardSecurityUser::signIn() method calls sfGuardUser::getAllPermissionNames(). All the permission names are granted as credentials for the user. All the hard work is done by PluginsfGuardUser, which gets all the permissions granted to the user (from the sf_guard_user_permission table). Then, in addition, it gets all the groups to which the user belongs and adds all the permissions for each of them (from the sf_guard_user_group and sf_guard_group_permission tables). Therefore, the user has a set of credentials consisting of the permission names directly granted to the user, set-unioned with the permission names of all the permission sets of all the groups to which the user belongs (phew!).

In summary, when you edit a user with the sfGuardUser module, you can allocate credentials by

  • ticking the permissions boxes on the form, and also by
  • allocating the user to groups and granting permissions to those groups.

Super administrator flag

sfGuardPlugin has a notion of super administrator. A user that is a super administrator bypasses all credential checks.

The super administrator flag cannot be set on the web, you must set the flag directly in the database or use the pake task:

    symfony promote-super-admin admin

Validators

sfGuardPlugin comes with a validator that you can use in your modules: sfGuardUserValidator.

This validator is used by the sfGuardAuth module to validate a user and password and automatically signin the user.

Check the user password with an external method

If you don't want to store the password in the database because you already have a LDAP server, a .htaccess file or if you store your passwords in another table, you can provide your own checkPassword callable (static method or function) in app.yml:

    all:
      sf_guard_plugin:
        check_password_callable: [MyLDAPClass, checkPassword]

When symfony calls the $this->getUser()->checkPassword() method, it will call your method or function. Your function must takes 2 parameters, the first one is the username and the second one is the password. It must returns true or false. Here is a template for such a function:

    function checkLDAPPassword($username, $password)
    {
      $user = LDAP::getUser($username);
      if ($user->checkPassword($password))
      {
        return true;
      }
      else
      {
        return false;
      }
    }

Change the algorithm used to store passwords

By default, passwords are stored as a sha1() hash. But you can change this with any callable in app.yml:

    all:
      sf_guard_plugin:
        algorithm_callable: [MyCryptoClass, MyCryptoMethod]

or

    all:
      sf_guard_plugin:
        algorithm_callable: md5

As the algorithm is stored for each user, you can change your mind later without the need to regenerate all passwords for the current users.

Change the name or expiration period of the "Remember Me" cookie

By default, the "Remember Me" feature creates a cookie named sfRemember that will last 15 days. You can change this behavior in app.yml:

    all:
      sf_guard_plugin:
         remember_key_expiration_age:  2592000   # 30 days in seconds
         remember_cookie_name:         myAppRememberMe

Customize sfGuardAuth redirect handling

If you want to redirect the user to his profile after a success login or define a logout site. You can change the redirect values in app.yml:

    all:
      sf_guard_plugin:
        success_signin_url:      @my_route?param=value # the plugin use the referer as default
        success_signout_url:     module/action         # the plugin use the referer as default

Configure the signin form

You can change the signin form used by the sfGuardAuth module in app.yml:

    all:
      sf_guard_plugin:
        signin_form:    LoginForm

Copy the existing form to the project's library directory, and rename the file to match the name in app.yml.

    cp plugins/sfGuardPlugin/lib/form/sfGuardFormSignin.class.php lib/form/LoginForm.class.php

Edit the file and make the following change, as well as any other changes (for instance, l10n of the form labels).

    class LoginForm extends sfGuardFormSignin

Rebuild the forms and clear the cache.

    symfony propel:build-forms
    symfony cc

If you want to change the actual HTML emitted, you can obtain the formatter and override whatever settings are desired

    $this->getWidgetSchema()->getFormFormatter()->setRowFormat(
      "<tr>\n  <th align=\"right\">%label%</th>\n  <td>%error%%field%%help%%hidden_fields%</td>\n</tr>\n"
    );

Sometimes, though, styling the HTML element is sufficient (for intance in web/css/main.css):

    form table tr th {
      text-align: right;
    }

TODO

  • finish the promote_super_user task
  • finish the getPassword method
  • add support for HTTP Basic authentication

Changelog

1.1.13 PRE

1.1.12

  • fabien: fixed typo in secureSuccess template (closes #2260)
  • fabien: fixed 'secure' action in sfGuardPlugin do not need to be secure (closes #2254)
  • fabien: fixed typo in sfGuardUser::getProfile()
  • fabien: added some check in sfGuardSecurityUser proxy methods

1.1.11

  • fabien: fixed array_merge_recursive causes recursion warnings in sfGuardUser.php (closes #1834)
  • fabien: fixed groups, permissions, and profile saving when sfUser has no primary key (closes #1709)
  • fabien: added connection parameters to all methods that interacts with the database (closes #2237)
  • fabien: changed signout actions, so it doesn't require to be authenticated
  • fabien: added a ->isSuperAdmin() method to the User class
  • fabien: fixed connection should be used when saving model (closes #2152)
  • fabien: fixed typo in modules /sfGuardAuth/config/security.yml (closes #1930)
  • fabien: removed warning about foreign key and profile table
  • davedash: when displaying the signin form, if no referer is set for the user we default to the last page
  • davedash: updated documentation regarding remember me cookie settings (closes #2148)
  • davedash: default algorithm is now sha1 not \asha1\a (closes Ticket #2189)
  • davedash: made the default templates i18n compatible (closes #1662)

1.1.10

  • davedash: reordered the if/elseif structure so no loop starves

1.1.9

  • fabien: fixed a typo in sfGuardUser reloadGroupsAndPermissions() method (closes #1758)
  • fabien: fixed "Remember Me" filter documentation (closes #1705)

1.1.8

  • gordon: add two new config params 'success_signin_url' and 'success_signin_url'
  • gordon: split 'checkPassword()' to 'checkPassword()' and 'checkPasswordByGuard()' so it is callable by your own 'check_password()'
  • gordon: better redirect
  • gordon: 'login_module' and 'login_action' use for 'handleErrorSignin()' and after successe login
  • gordon: Added extra logic to make sure remember me code is only executed if user is not authenticated.
  • fabien: fixed is_super_admin has not default value (closes #1410)
  • fabien: fixed sfGuardPlugin signin.yml validation configuration (closes #1440)
  • fabien: fixed missing unique indexes in sf_guard_group and sf_guard_permission (closes #1454)
  • fabien: fixed sfGuardAuth should clear the credentials (closes #1537)
  • davedash: giving unique indexes unique names so that sfGuardPlugin works with postgres (closes ticket #1720)
  • davedash: fixed the model so getting groupPermissions works (fixes #1729)
  • davedash: the signin class would keep redirecting on itself if the user is logged in

1.1.7

  • fabien: fixed deleting sfGuardUser when no profile is defined (closes #1626)
  • davedash: The setPassword() function of sfGuardSecurityUser() now saves the sfGuardUser object after setting the password