Changeset - ada6571a6d27
[Not reviewed]
default
0 3 0
domruf - 10 years ago 2015-10-04 20:43:12
dominikruf@gmail.com
auth: let container authentication get email, first and last name from custom headers
3 files changed with 144 insertions and 8 deletions:
0 comments (0 inline, 0 general)
docs/setup.rst
Show inline comments
 
@@ -427,6 +427,74 @@ reverse-proxy setup with basic auth:
 
      RequestHeader set X-Forwarded-User %{RU}e
 
    </Location>
 

	
 
Setting metadata in container/reverse-proxy
 
'''''''''''''''''''''''''''''''''''''''''''
 

	
 
When a new user account is created on the first login, Kallithea has no information about
 
the user's email and full name. So you can set some additional request headers like in the
 
example below. In this example the user is authenticated via Kerberos and an Apache
 
mod_python fixup handler is used to get the user information from a LDAP server. But you
 
could set the request headers however you want.
 

	
 
.. code-block:: apache
 

	
 
    <Location /someprefix>
 
      ProxyPass http://127.0.0.1:5000/someprefix
 
      ProxyPassReverse http://127.0.0.1:5000/someprefix
 
      SetEnvIf X-Url-Scheme https HTTPS=1
 

	
 
      AuthName "Kerberos Login"
 
      AuthType Kerberos
 
      Krb5Keytab /etc/apache2/http.keytab
 
      KrbMethodK5Passwd off
 
      KrbVerifyKDC on
 
      Require valid-user
 

	
 
      PythonFixupHandler ldapmetadata
 

	
 
      RequestHeader set X_REMOTE_USER %{X_REMOTE_USER}e
 
      RequestHeader set X_REMOTE_EMAIL %{X_REMOTE_EMAIL}e
 
      RequestHeader set X_REMOTE_FIRSTNAME %{X_REMOTE_FIRSTNAME}e
 
      RequestHeader set X_REMOTE_LASTNAME %{X_REMOTE_LASTNAME}e
 
    </Location>
 

	
 
.. code-block:: python
 

	
 
    from mod_python import apache
 
    import ldap
 

	
 
    LDAP_SERVER = "ldap://server.mydomain.com:389"
 
    LDAP_USER = ""
 
    LDAP_PASS = ""
 
    LDAP_ROOT = "dc=mydomain,dc=com"
 
    LDAP_FILTER = "sAMAcountName=%s"
 
    LDAP_ATTR_LIST = ['sAMAcountName','givenname','sn','mail']
 

	
 
    def fixuphandler(req):
 
        if req.user is None:
 
            # no user to search for
 
            return apache.OK
 
        else:
 
            try:
 
                if('\\' in req.user):
 
                    username = req.user.split('\\')[1]
 
                elif('@' in req.user):
 
                    username = req.user.split('@')[0]
 
                else:
 
                    username = req.user
 
                l = ldap.initialize(LDAP_SERVER)
 
                l.simple_bind_s(LDAP_USER, LDAP_PASS)
 
                r = l.search_s(LDAP_ROOT, ldap.SCOPE_SUBTREE, LDAP_FILTER % username, attrlist=LDAP_ATTR_LIST)
 

	
 
                req.subprocess_env['X_REMOTE_USER'] = username
 
                req.subprocess_env['X_REMOTE_EMAIL'] = r[0][1]['mail'][0].lower()
 
                req.subprocess_env['X_REMOTE_FIRSTNAME'] = "%s" % r[0][1]['givenname'][0]
 
                req.subprocess_env['X_REMOTE_LASTNAME'] = "%s" % r[0][1]['sn'][0]
 
            except Exception, e:
 
                apache.log_error("error getting data from ldap %s" % str(e), apache.APLOG_ERR)
 

	
 
            return apache.OK
 

	
 
.. note::
 
   If you enable proxy pass-through authentication, make sure your server is
 
   only accessible through the proxy. Otherwise, any client would be able to
kallithea/lib/auth_modules/auth_container.py
Show inline comments
 
@@ -29,7 +29,7 @@ import logging
 
from kallithea.lib import auth_modules
 
from kallithea.lib.utils2 import str2bool, safe_unicode
 
from kallithea.lib.compat import hybrid_property
 
from kallithea.model.db import User
 
from kallithea.model.db import User, Setting
 

	
 
log = logging.getLogger(__name__)
 

	
 
@@ -53,15 +53,39 @@ class KallitheaAuthPlugin(auth_modules.K
 
                "name": "header",
 
                "validator": self.validators.UnicodeString(strip=True, not_empty=True),
 
                "type": "string",
 
                "description": "Header to extract the user from",
 
                "description": "Request header to extract the username from",
 
                "default": "REMOTE_USER",
 
                "formname": "Header"
 
                "formname": "Username header"
 
            },
 
            {
 
                "name": "email_header",
 
                "validator": self.validators.UnicodeString(strip=True),
 
                "type": "string",
 
                "description": "Optional request header to extract the email from",
 
                "default": "",
 
                "formname": "Email header"
 
            },
 
            {
 
                "name": "firstname_header",
 
                "validator": self.validators.UnicodeString(strip=True),
 
                "type": "string",
 
                "description": "Optional request header to extract the first name from",
 
                "default": "",
 
                "formname": "Firstname header"
 
            },
 
            {
 
                "name": "lastname_header",
 
                "validator": self.validators.UnicodeString(strip=True),
 
                "type": "string",
 
                "description": "Optional request header to extract the last name from",
 
                "default": "",
 
                "formname": "Lastname header"
 
            },
 
            {
 
                "name": "fallback_header",
 
                "validator": self.validators.UnicodeString(strip=True),
 
                "type": "string",
 
                "description": "Header to extract the user from when main one fails",
 
                "description": "Request header to extract the user from when main one fails",
 
                "default": "HTTP_X_FORWARDED_USER",
 
                "formname": "Fallback header"
 
            },
 
@@ -172,9 +196,9 @@ class KallitheaAuthPlugin(auth_modules.K
 
        # old attrs fetched from Kallithea database
 
        admin = getattr(userobj, 'admin', False)
 
        active = getattr(userobj, 'active', True)
 
        email = getattr(userobj, 'email', '')
 
        firstname = getattr(userobj, 'firstname', '')
 
        lastname = getattr(userobj, 'lastname', '')
 
        email = environ.get(settings.get('email_header'), getattr(userobj, 'email', ''))
 
        firstname = environ.get(settings.get('firstname_header'), getattr(userobj, 'firstname', ''))
 
        lastname = environ.get(settings.get('lastname_header'), getattr(userobj, 'lastname', ''))
 

	
 
        user_data = {
 
            'username': username,
 
@@ -192,4 +216,11 @@ class KallitheaAuthPlugin(auth_modules.K
 
        return user_data
 

	
 
    def get_managed_fields(self):
 
        return ['username', 'password']
 
        fields = ['username', 'password']
 
        if(Setting.get_by_name('auth_container_email_header').app_settings_value):
 
            fields.append('email')
 
        if(Setting.get_by_name('auth_container_firstname_header').app_settings_value):
 
            fields.append('firstname')
 
        if(Setting.get_by_name('auth_container_lastname_header').app_settings_value):
 
            fields.append('lastname')
 
        return fields
kallithea/tests/functional/test_admin_auth_settings.py
Show inline comments
 
@@ -135,6 +135,9 @@ class TestAuthSettingsController(TestCon
 
    def test_container_auth_login_header(self):
 
        self._container_auth_setup(
 
            auth_container_header='THE_USER_NAME',
 
            auth_container_email_header='',
 
            auth_container_firstname_header='',
 
            auth_container_lastname_header='',
 
            auth_container_fallback_header='',
 
            auth_container_clean_username='False',
 
        )
 
@@ -143,9 +146,34 @@ class TestAuthSettingsController(TestCon
 
            resulting_username='john@example.org',
 
        )
 

	
 
    def test_container_auth_login_header_attr(self):
 
        self._container_auth_setup(
 
            auth_container_header='THE_USER_NAME',
 
            auth_container_email_header='THE_USER_EMAIL',
 
            auth_container_firstname_header='THE_USER_FIRSTNAME',
 
            auth_container_lastname_header='THE_USER_LASTNAME',
 
            auth_container_fallback_header='',
 
            auth_container_clean_username='False',
 
        )
 
        response = self.app.get(
 
            url=url(controller='admin/my_account', action='my_account'),
 
            extra_environ={'THE_USER_NAME': 'johnd',
 
                           'THE_USER_EMAIL': 'john@example.org',
 
                           'THE_USER_FIRSTNAME': 'John',
 
                           'THE_USER_LASTNAME': 'Doe',
 
                           }
 
        )
 
        self.assertEqual(response.form['email'].value, 'john@example.org')
 
        self.assertEqual(response.form['firstname'].value, 'John')
 
        self.assertEqual(response.form['lastname'].value, 'Doe')
 

	
 

	
 
    def test_container_auth_login_fallback_header(self):
 
        self._container_auth_setup(
 
            auth_container_header='THE_USER_NAME',
 
            auth_container_email_header='',
 
            auth_container_firstname_header='',
 
            auth_container_lastname_header='',
 
            auth_container_fallback_header='HTTP_X_YZZY',
 
            auth_container_clean_username='False',
 
        )
 
@@ -157,6 +185,9 @@ class TestAuthSettingsController(TestCon
 
    def test_container_auth_clean_username_at(self):
 
        self._container_auth_setup(
 
            auth_container_header='REMOTE_USER',
 
            auth_container_email_header='',
 
            auth_container_firstname_header='',
 
            auth_container_lastname_header='',
 
            auth_container_fallback_header='',
 
            auth_container_clean_username='True',
 
        )
 
@@ -168,6 +199,9 @@ class TestAuthSettingsController(TestCon
 
    def test_container_auth_clean_username_backslash(self):
 
        self._container_auth_setup(
 
            auth_container_header='REMOTE_USER',
 
            auth_container_email_header='',
 
            auth_container_firstname_header='',
 
            auth_container_lastname_header='',
 
            auth_container_fallback_header='',
 
            auth_container_clean_username='True',
 
        )
 
@@ -179,6 +213,9 @@ class TestAuthSettingsController(TestCon
 
    def test_container_auth_no_logout(self):
 
        self._container_auth_setup(
 
            auth_container_header='REMOTE_USER',
 
            auth_container_email_header='',
 
            auth_container_firstname_header='',
 
            auth_container_lastname_header='',
 
            auth_container_fallback_header='',
 
            auth_container_clean_username='True',
 
        )
0 comments (0 inline, 0 general)