+Auth has an optional ``secure=True`` argument, which will force authenticated pages to go over HTTPS. ``https``:inxx
+
-------
The ``password`` field of the ``db.auth_user`` table defaults to a ``CRYPT`` validator, which needs and ``hmac_key``. On legacy web2py applications you may see an extra argument passed to the Auth constructor: ``hmac_key = Auth.get_or_create_key()``. The latter is a function that read the hmac kay from a file "private/auth.key" within the application folder. If the file does not exist it creates a random ``hmac_key``. If multiple apps share the same auth database, make sure they also use the same ``hmac_key``. This is no loger necessary for new applications since passwords are salted with an individual random salt.
-------
By default, web2py uses email for login. If instead you want to log in using username set ``auth.define_tables(username=True)``
If multiple apps share the same auth database you may want to disable migrations: ``auth.define_tables(migrate=False)``.
To expose **Auth**, you also need the following function in a controller (for example in "default.py"):
``
def user(): return dict(form=auth())
``:code
-------
The ``auth`` object and the ``user`` action are already defined in the
scaffolding application.
-------
web2py also includes a sample view "welcome/views/default/user.html" to render this function properly that looks like this:
``
web2py also includes a sample view "welcome/views/default/user.html" to render t
<h2>{{=T( request.args(0).replace('_',' ').capitalize() )}}</h2>
<div id="web2py_user_form">
{{=form}}
{{if request.args(0)=='login':}}
{{if not 'register' in auth.settings.actions_disabled:}}
<br/><a href="{{=URL(args='register')}}">register</a>
{{pass}}
{{if not 'request_reset_password' in auth.settings.actions_disabled:}}
<br/>
<a href="{{=URL(args='request_reset_password')}}">lost password</a>
{{pass}}
{{pass}}
</div>
``:code
Notice that this function simply displays a ``form`` and therefore it can be customized using normal custom form syntax. The only caveat is that the form displayed by ``form=auth()`` depends on ``request.args(0)``; therefore, if you replace the default ``auth()`` login form with a custom login form, you may need an ``if`` statement like this in the view:
``
{{if request.args(0)=='login':}}...custom login form...{{pass}}
``:code
+``auth.impersonate``:inxx ``auth.is_impersonating``:inxx
+
The controller above exposes multiple actions:
``
http://.../[app]/default/user/register
http://.../[app]/default/user/login
http://.../[app]/default/user/logout
http://.../[app]/default/user/profile
http://.../[app]/default/user/change_password
http://.../[app]/default/user/verify_email
http://.../[app]/default/user/retrieve_username
http://.../[app]/default/user/request_reset_password
http://.../[app]/default/user/reset_password
http://.../[app]/default/user/impersonate
http://.../[app]/default/user/groups
http://.../[app]/default/user/not_authorized
``:code
- **register** allows users to register. It is integrated with CAPTCHA, although this is disabled by default.
- **login** allows users who are registered to log in (if the registration is verified or does not require verification, if it has been approved or does not require approval, and if it has not been blocked).
- **logout** does what you would expect but also, as the other methods, logs the event and can be used to trigger some event.
- **profile** allows users to edit their profile, i.e. the content of the ``auth_user`` table. Notice that this table does not have a fixed structure and can be customized.
- **change_password** allows users to change their password in a fail-safe way.
- **verify_email**. If email verification is turned on, then visitors, upon registration, receive an email with a link to verify their email information. The link points to this action.
- **retrieve_username**. By default, **Auth** uses email and password for login, but it can, optionally, use username instead of email. In this latter case, if a user forgets his/her username, the ``retrieve_username`` method allows the user to type the email address and retrieve the username by email.
- **request_reset_password**. Allows users who forgot their password to request a new password. They will get a confirmation email pointing to **reset_password**.
- **impersonate** allows a user to "impersonate" another user. This is important for debugging and for support purposes. ``request.args[0]`` is the id of the user to be impersonated. This is only allowed if the logged in user ``has_permission('impersonate', db.auth_user, user_id)``. You can use ``auth.is_impersonating()`` to check is the current user is impersonating somebody else.
- **groups** lists the groups of which the current logged in user is a member.
- **not_authorized** displays an error message when the visitor tried to do something that he/she is not authorized to do
- **navbar** is a helper that generates a bar with login/register/etc. links.
Logout, profile, change_password, impersonate, and groups require login.
By default they are all exposed, but it is possible to restrict access to only some of these actions.
All of the methods above can be extended or replaced by subclassing **Auth**.
All of the methods above can be used in separate actions. For example:
``
def mylogin(): return dict(form=auth.login())
def myregister(): return dict(form=auth.register())
def myprofile(): return dict(form=auth.profile())
...
``
To restrict access to functions to only logged in visitors, decorate the function as in the following example
``
@auth.requires_login()
def hello():
return dict(message='hello %(first_name)s' % auth.user)
``:code
Any function can be decorated, not just exposed actions. Of course this is still only a very simple example of access control. More complex examples will be discussed later.
``auth.user``:inxx ``auth.user_id``:inxx ``auth.user_groups``.
-----
``auth.user`` contains a copy of the ``db.auth_user`` records for the current logged in user or ``None`` otherwise. There is also a ``auth.user_id`` which is the same as ``auth.user.id`` (i.e. the id of the current logger in user) or ``None``. Similarly, ``auth.user_groups`` contains a dictionary where each key is the id of a group of with the current logged in user is member of, the value is the corresponding group role.
-----
``otherwise``:inxx
The ``auth.requires_login()`` decorator as well as the other ``auth.requires_*`` decorators take an optional ``otherwise`` argument. It can be set to a string where to redirect the user if registration files or to a callable object. It is called if registration fails.
#### Restrictions on registration
If you want to allow visitors to register but not to log in until registration has been approved by the administrator:
``
auth.settings.registration_requires_approval = True
``:code
You can approve a registration via the appadmin interface. Look into the table ``auth_user``. Pending registrations have a ``registration_key`` field set to "pending". A registration is approved when this field is set to blank.
Via the appadmin interface, you can also block a user from logging in. Locate the user in the table ``auth_user`` and set the ``registration_key`` to "blocked". "blocked" users are not allowed to log in. Notice that this will prevent a visitor from logging in but it will not force a visitor who is already logged in to log out. The word "disabled" may be used instead of "blocked" if preferred, with exactly the same behavior.
You can also block access to the "register" page completely with this statement:
``
auth.settings.actions_disabled.append('register')
curl -d "firstName=John&lastName=Smith" -G -v --key private.key \
This works out of the box with Rocket (the web2py built-in web server) but you may need some extra configuration work on the web server side if you are using a different web server. In particular you need to tell your web server where the certificates are located on local host and that it needs to verify certificates coming from the clients. How to do it is web server dependent and therefore omitted here.
##### Multiple login forms
Some login methods modify the login_form, some do not. When they do that, they may not be able to coexist. Yet some coexist by providing multiple login forms in the same page. web2py provides a way to do it. Here is an example mixing normal login (auth) and RPX login (janrain.com):
``
from gluon.contrib.login_methods.extended_login_form import ExtendedLoginForm
other_form = RPXAccount(request, api_key='...', domain='...', url='...')
auth.settings.login_form = ExtendedLoginForm(auth, other_form, signals=['token'])
``:code
If signals are set and a parameter in request matches any signals,
it will return the call of ``other_form.login_form`` instead.
``other_form`` can handle some particular situations, for example,
multiple steps of OpenID login inside ``other_form.login_form``.
Otherwise it will render the normal login form together with the ``other_form``.
+#### Record versioning
+
+You can use Auth to enable full record versioning:
+
+``
+db.enable_record_versioning(db,
+ archive_db=None,
+ archive_names='%(tablename)s_archive',
+ current_record='current_record'):
+``:code
+
+This tells web2py to create an archive table for each of the tables in ``db`` and store a copy of each record when modified. The old copy is stored. The new copy is not.
+
+The last three parameters are optional:
+
+- ``archive_db`` allows to specify another database where the archive tables are to be stored. Setting it to ``None`` is the same as setting it to ``db``.
+- ``archive_names`` provides a pattern for naming each archive table.
+- ``current_record`` specified the name of the reference field to be used in the archive table to refer to the original, unmodified, record. Notice that ``archive_db!=db`` then the reference field is just an integer field since cross database references are not possible.
+
+
+Only tables with ``modified_by`` and ``modified_on`` fields (as created
+for example by auth.signature) will be archived.
+
+When you ``enable_record_versioning``, if records have an
+``is_active`` field (also created by auth.signature),
+records will never be deleted but they will be marked with ``is_active=False``.
+In fact, ``enable_record_versioning`` adds a ``common_filter`` to
+every versioned table that filters out records with ``is_active=False`` so they essentially become invisible.
+
+If you ``enable_record_versioning``, you should not use
+``auth.archive`` or ``crud.archive`` else you will end up with duplicate records.
+Those functions do explicitly what ``enable_record_versioning`` does automatically and
+they will be deprecated.
+
+
### ``Mail`` and ``Auth``
One can define a mailer with
``
from gluon.tools import Mail
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'
``
or simply use the mailer provided by ``auth``:
``
mail = auth.settings.mailer
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'
To enable email, append the following lines in the model where ``auth`` is defin
``
auth.settings.registration_requires_verification = True
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True
auth.messages.verify_email = 'Click on the link http://' + \
request.env.http_host + \
URL(r=request,c='default',f='user',args=['verify_email']) + \
'/%(key)s to verify your email'
auth.messages.reset_password = 'Click on the link http://' + \
request.env.http_host + \
URL(r=request,c='default',f='user',args=['reset_password']) + \
'/%(key)s to reset your password'
``:code
In the two ``auth.messages`` above, you may need to replace the URL portion of the string with the proper complete URL of the action. This is necessary because web2py may be installed behind a proxy, and it cannot determine its own public URLs with absolute certainty. The above examples (which are the default values) should, however, work in most cases.
### Authorization
Once a new user is registered, a new group is created to contain the user. The role of the new user is conventionally "user_[id]" where [id] is the id of the newly created user. The creation of the group can be disabled with
``
auth.settings.create_user_groups = None
``:code
+although we do not suggest doing so. Notice that ``create_user_groups`` is not a boolean (althought it can be ``False``) but it defaults to:
+
+``
+auth.settings.create_user_groups="user_%(id)s"
+``:code
+
+It store a template for the name of the group created for user ``id``.
+
+Users have membership in groups. Each group is identified by a name/role. Groups have permissions. Users have permissions because of the groups they belong to. By default each user is made member of their own group.
+
+You can also also do
+``
+auth.settings.everybody_group_id = 5
+``:code
to make any new user automatically member of group number 5. Here 5 is used as an example and we assume the group was created already.