Search code examples
turbogears2

How to use very simple http-auth based authentication in turbogears2?


After disabling the default configuration of repoze.who by removing all the base_config.sa_auth... and base_config.auth_backend from config/app_.cfg.pyit should be possible to configure repoze.who as middleware in config/middleware.py.

so i created a file config/auth.py like this:

from logging import getLogger

from repoze.who.middleware import PluggableAuthenticationMiddleware
from repoze.who.classifiers import default_challenge_decider, default_request_classifier

from repoze.who.plugins.basicauth import BasicAuthPlugin
from repoze.who.plugins.htpasswd import HTPasswdPlugin, plain_check

def add_auth(app):
    htpasswd = HTPasswdPlugin('/.../htpasswd', plain_check)
    authenticators = [('htpasswd', htpasswd)]

    base_auth = BasicAuthPlugin('Inventory DB')
    challengers = [('base_auth', base_auth)]
    identifiers = [('base_auth', base_auth)]

    mdproviders = []

    log_stream = getLogger('auth')

    app_with_mw = PluggableAuthenticationMiddleware(
        app,
        identifiers,
        authenticators,
        challengers,
        mdproviders,
        default_request_classifier,
        default_challenge_decider,
        log_stream,
        )
    return app_with_mw

where plain_text passwords are used just for testing. Then, in config/middleware.py this function is imported and applied to the app as the last step in the make_app function.

from invdb.config.app_cfg import base_config
from invdb.config.environment import load_environment
from auth import add_auth

__all__ = ['make_app']
make_base_app = base_config.setup_tg_wsgi_app(load_environment)
def make_app(global_conf, full_stack=True, **app_conf):
    app = make_base_app(global_conf, full_stack=True, **app_conf)
    app = add_auth(app)
    return app

The problem is now, that the authentication does not really work. Controllers that do not require any authentication don't challenge. A controller with allow_only = tg.predicate.not_anonymous will challenge a http-authentication. But even if plain_check returns True the login is instantly forgotten and the challenge is displayed again. tg.request.identity stays None.

What am I doing wrong?


Solution

  • There is a far easier solution that replacing the whole authentication stack with a custom middleware. As mentioned by authentication documentation you can configure the authentication stack from your app_cfg.py itself.

    For example if you want to force the basic auth usage you just need to set the challengers, identifiers and authenticators and then return the data to fill tg.request.identity using the authmetadata

    Here is a sample app_cfg for basic auth (remember to remove any other basic_config.sa_auth entry or it might crash for unexpected parameters):

    # Name our custom auth backend, if this is None TG will
    # disable the whole authentication stack.
    base_config.auth_backend = 'htpasswd'
    
    from tg.configuration.auth import TGAuthMetadata
    
    #This tells to TurboGears how to retrieve the data for your user
    class ApplicationAuthMetadata(TGAuthMetadata):
        def __init__(self, sa_auth):
            self.sa_auth = sa_auth
    
        def get_user(self, identity, userid):
            # As we use htpasswd for authentication
            # we cannot lookup the user in a database,
            # so just return a fake user object
            from tg.util import Bunch
            return Bunch(display_name=userid, user_name=userid)
    
        def get_groups(self, identity, userid):
            # If the user is manager we give him the
            # managers group, otherwise no groups
            if userid == 'manager':
                return ['managers']
            else:
                return []
    
        def get_permissions(self, identity, userid):
            return []
    
    base_config.sa_auth.authmetadata = ApplicationAuthMetadata(base_config.sa_auth)
    
    from repoze.who.plugins.basicauth import BasicAuthPlugin
    from repoze.who.plugins.htpasswd import HTPasswdPlugin, plain_check
    
    # Use htpasswd for checking user credentials, remember to write the password in clear
    # text as we are using the plain_check function to check them.
    base_config.sa_auth.authenticators = [('htpasswd', HTPasswdPlugin('./passwd_file', plain_check))]
    
    # Use BasicAuth plugin to ask user for credentials, this will replace
    # the whole login form and cookie based authentication
    base_auth = BasicAuthPlugin('MyTGApp')
    base_config.sa_auth.challengers = [('basicauth', base_auth)]
    base_config.sa_auth.identifiers = [('basicauth', base_auth)]
    
    # Disable the login form, it won't work anyway as the credentials
    # for basic auth must be provided through the browser itself
    base_config.sa_auth.form_identifies = False