changeset 5609:ada6571a6d27

auth: let container authentication get email, first and last name from custom headers
author domruf <dominikruf@gmail.com>
date Sun, 04 Oct 2015 20:43:12 +0200
parents 9a4d4e623c85
children eb072b2cfa18
files docs/setup.rst kallithea/lib/auth_modules/auth_container.py kallithea/tests/functional/test_admin_auth_settings.py
diffstat 3 files changed, 144 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/docs/setup.rst	Fri Dec 25 13:50:18 2015 +0100
+++ b/docs/setup.rst	Sun Oct 04 20:43:12 2015 +0200
@@ -427,6 +427,74 @@
       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
--- a/kallithea/lib/auth_modules/auth_container.py	Fri Dec 25 13:50:18 2015 +0100
+++ b/kallithea/lib/auth_modules/auth_container.py	Sun Oct 04 20:43:12 2015 +0200
@@ -29,7 +29,7 @@
 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 @@
                 "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 @@
         # 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 @@
         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
--- a/kallithea/tests/functional/test_admin_auth_settings.py	Fri Dec 25 13:50:18 2015 +0100
+++ b/kallithea/tests/functional/test_admin_auth_settings.py	Sun Oct 04 20:43:12 2015 +0200
@@ -135,6 +135,9 @@
     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 @@
             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 @@
     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 @@
     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 @@
     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',
         )