Writing Authentication Backends¶
New in version 1.6: The authentication backends described here are new in Review Board 1.6. For Review Board 1.0 and 1.5, see Writing Legacy Authentication Backends.
Overview¶
Authentication in Review Board is handled by classes called Authentication Backends. They perform the tasks of looking up users from some database or server, authenticating against it given user credentials, and creating local representations of the users in Review Board’s database.
Review Board provides local database, NIS, LDAP and Active Directory backends out of the box. New ones can be written to work with other types of authentication schemes.
Authentication Classes¶
An Authentication Backend class is a simple class inheriting from reviewboard.accounts.backends.AuthBackend. It must set the following attributes:
And can optionally set the following attributes:
- settings_form
- supports_registration
- supports_change_name
- supports_change_email
- supports_change_password
It must also define the methods:
We’ll go into each function and attribute in detail.
- class reviewboard.accounts.backends.AuthBackend¶
- name¶
This is the human-readable name of the authentication backend. This is what users will see when they go to select the authentication backend to use.
- settings_form¶
This is an optional attribute that can be used to specify a settings form to present for any configuration needed by the backend.
If this is not None, it must point to a djblets.siteconfig.forms.SiteSettingsForm subclass. This works like a standard Django Form, where each field name is the name of the settings key that will be automatically loaded and saved. See Settings Forms for more information.
- supports_registration¶
A boolean that indicates whether the registration form can be used. If this is set to True, then logged out users will have the ability to register a new account.
The registration process will create a new User in the database. Currenly, there is no support for handing off registration to the authentication backend, but it’s planned.
- supports_change_name¶
A boolean that indicates whether a user can change his full name on the My Account page. If this is set to True, fields for the first and last name will be available and editable.
Currently, there is no support for allowing the authentication module to handle setting the name, so it cannot update the backend server. This is planned for the future.
- supports_change_email¶
A boolean that indicates whether a user can change his e-mail address on the My Account page. If this is set to True, a field for the e-mail address will be available and editable.
Currently, there is no support for allowing the authentication module to handle setting the e-mail address, so it cannot update the backend server. This is planned for the future.
- supports_change_password¶
A boolean that indicates whether a user can change his password on the My Account page. If this is set to True, a field for the password will be available and editable.
Currently, there is no support for allowing the authentication module to handle setting the password, so it cannot update the backend server. This is planned for the future.
- authenticate(username, password)¶
Parameters: - username – The user’s username.
- password – The user’s password.
Return type: The authenticated user, if authentication succeeds. On failure, None.
Authenticates the user against a database or server.
This is responsible for making any necessary communication with the database or server and determining the validity of the credentials passed.
If the credentials are invalid, the function must return None, which will allow it to fall back to the next authentication backend in the chain (or fail, if this is the last authentication backend).
If the credentials are valid, the function must return a valid User. Generally, rather than constructing one itself, it should call its own get_or_create_user() with the username.
To help with debugging, this function should log any errors in communication using Python’s logging support.
The function may need to strip whitespace from the username before authentication. If the server itself strips whitespace when authenticating, but this function does not, it can lead to duplicate users in the database.
- get_or_create_user(username)¶
Parameters: username – The user’s username. Return type: The user, if it exists. Otherwise, None. Looks up or creates a User based on information from the database or server.
This tends to follow the pattern of:
username = username.strip() try: user = User.objects.get(username=username) except User.DoesNotExist: # Construct a user from the database... return user
Like authenticate(), this will look up the user from the database or server. However, it will not verify anything other than the username. It also must make sure to strip the username.
This function is used both when logging in and when adding a user to a review request as a reviewer. In the latter case, Review Board will look up the user using the authentication backend in order to see if the user exists and can be added.
Settings Forms¶
Authentication backends can provide a settings form just like the built-in backends (NIS, LDAP, etc.). The backend class just needs to set settings_form to a djblets.siteconfig.forms.SiteSettingsForm subclass (not an instance).
This is a special sort of form where each field name is the name of the key in the settings database to store the value. The proper convention for these classes is to prefix the field name with auth_backendid_. The backendid is a short, lowercase name that represents the auth backend. For example, nis, ldap, or ad.
Every field will be saved to the database with the exception of “blacklisted” fields. See Blacklisting Fields.
The form can also include some metadata by way of a Meta class within the form. It can contain a title attribute, containing the title to show on the settings form, and a save_blacklist for blacklisting fields.
The form may also provide custom load() and save() methods for handling any custom loading and saving. These must always call the parent class’s methods.
An example class would be:
from django import forms
from djblets.siteconfig.forms import SiteSettingsForm
class MySettingsForm(SiteSettingsForm):
auth_myauth_foo = forms.CharField(
label="Some setting",
help_text="Some useful help text",
required=True)
auth_myauth_bar = forms.BooleanField(
label="Another setting",
help_text="Some more useful help text",
required=False)
class Meta:
title = "My Auth Backend Settings"
These can use any Django form fields. The actual loading and saving of settings from the database are handled under the hood.
You can also make use of standard Django form validation to ensure that valid data was entered before save.
Blacklisting Fields¶
Sometimes it’s necessary to process a setting before it goes into the database or when it comes out. In this case, you don’t want the setting to be handled automatically. The field can be prevented from saving/loading by adding it to the Meta.save_blacklist attribute. This is a tuple of field names that will be ignored during save/load.
This is usually used in conjunction with custom load() and save() methods.
When loading a setting into a field, you should set the value in self.fields['fieldname'].initial and retrieve the value from the database when using self.siteconfig.get('settingname').
When saving a setting from a field, you should set the value in the database using self.siteconfig.set('settingname', value) and retrieving it from the field using self.cleaned_data['fieldname'].
For example:
class MySettingsForm(SiteSettingsForm):
auth_myauth_list = forms.CharField(
label="Comma-separated list of values")
def load(self):
self.fields['auth_myauth_list'].initial = \
','.join(self.siteconfig.get('auth_myauth_list'))
super(MySettingsForm, self).load()
def save(self):
self.siteconfig.set(
'auth_myauth_list',
re.split(r',\*', self.cleaned_data['auth_myauth_list']))
super(MySettingsForm, self).save()
Disabling Fields¶
It can be useful to disable fields based on different conditions, such as a missing Python module. In this case, you can disable any fields in the form and provide an inline message by setting the disabled_fields and :py:`disabled_reasons` attributes during load().
Both of these attributes are dictionaries mapping from a field name to a value. For disabled_fields, the value is a boolean indicating whether the field is disabled. For disabled_reasons, the value is a string describing why the field is disabled.
For example:
def load(self):
if not get_can_enable_myauth():
self.disabled_fields['auth_myauth_foo'] = True
self.disabled_reasons['auth_myauth_foo'] = \
'You must do a handstand before you can enable this ' \
'authentication backend.'
super(MySettingsForm, self).load()
Accessing Settings¶
The authentication backend can access any settings stored in the site configuration database (such as those defined in the Settings form through the djblets.siteconfig.models.SiteConfiguration API.
Working with this is pretty simple. First, you just need to get a SiteConfiguration object:
from djblets.siteconfig.models import SiteConfiguration
siteconfig = SiteConfiguration.objects.get_current()
You can then load and save through SiteConfiguration.set() and SiteConfiguration.get() methods. Each take a setting name and work with any native Python primitive (strings, booleans, lists, tuples, dictionaries).
For example:
from djblets.siteconfig.models import SiteConfiguration
siteconfig = SiteConfiguration.objects.get_current()
siteconfig.set('auth_myauth_foo', 'Some value')
bar = siteconfig.get('auth_myauth_bar')
Packaging¶
Authentication backends should be packaged as a standard Python egg module. Generally, this looks something like:
setup.py
myauth/__init__.py
The __init__.py would contain your authentication backend’s classes and logic.
You can of course split this up into separate files (such as backends.py for the backend class and forms.py for the settings form). This is entirely up to you. However, to be a proper Python module, you must have a __init__.py, though it can be blank.
setup.py must define an “entry point” for your module in order for Review Board to find it. This is done through the entry_points parameter passed to setup. For example:
setup(...,
entry_points={
'reviewboard.auth_backends': [
'myauth = myauth:MyAuthBackend',
],
}
)
Review Board will look in reviewboard.auth_backends for every module and attempt to load it. The module path specified must be the full Python module path for your class. The ID (myauth in the example above) can be anything, but generally should be consistent with your settings prefix for the settings form, and must not conflict with any other authentication modules.
The authentication module can then be installed by typing (as root):
$ python setup.py install