##// END OF EJS Templates
auth: let container authentication get email, first and last name from custom headers
domruf -
r5593:ada6571a default
parent child Browse files
Show More
@@ -427,6 +427,74 b' reverse-proxy setup with basic auth:'
427 427 RequestHeader set X-Forwarded-User %{RU}e
428 428 </Location>
429 429
430 Setting metadata in container/reverse-proxy
431 '''''''''''''''''''''''''''''''''''''''''''
432
433 When a new user account is created on the first login, Kallithea has no information about
434 the user's email and full name. So you can set some additional request headers like in the
435 example below. In this example the user is authenticated via Kerberos and an Apache
436 mod_python fixup handler is used to get the user information from a LDAP server. But you
437 could set the request headers however you want.
438
439 .. code-block:: apache
440
441 <Location /someprefix>
442 ProxyPass http://127.0.0.1:5000/someprefix
443 ProxyPassReverse http://127.0.0.1:5000/someprefix
444 SetEnvIf X-Url-Scheme https HTTPS=1
445
446 AuthName "Kerberos Login"
447 AuthType Kerberos
448 Krb5Keytab /etc/apache2/http.keytab
449 KrbMethodK5Passwd off
450 KrbVerifyKDC on
451 Require valid-user
452
453 PythonFixupHandler ldapmetadata
454
455 RequestHeader set X_REMOTE_USER %{X_REMOTE_USER}e
456 RequestHeader set X_REMOTE_EMAIL %{X_REMOTE_EMAIL}e
457 RequestHeader set X_REMOTE_FIRSTNAME %{X_REMOTE_FIRSTNAME}e
458 RequestHeader set X_REMOTE_LASTNAME %{X_REMOTE_LASTNAME}e
459 </Location>
460
461 .. code-block:: python
462
463 from mod_python import apache
464 import ldap
465
466 LDAP_SERVER = "ldap://server.mydomain.com:389"
467 LDAP_USER = ""
468 LDAP_PASS = ""
469 LDAP_ROOT = "dc=mydomain,dc=com"
470 LDAP_FILTER = "sAMAcountName=%s"
471 LDAP_ATTR_LIST = ['sAMAcountName','givenname','sn','mail']
472
473 def fixuphandler(req):
474 if req.user is None:
475 # no user to search for
476 return apache.OK
477 else:
478 try:
479 if('\\' in req.user):
480 username = req.user.split('\\')[1]
481 elif('@' in req.user):
482 username = req.user.split('@')[0]
483 else:
484 username = req.user
485 l = ldap.initialize(LDAP_SERVER)
486 l.simple_bind_s(LDAP_USER, LDAP_PASS)
487 r = l.search_s(LDAP_ROOT, ldap.SCOPE_SUBTREE, LDAP_FILTER % username, attrlist=LDAP_ATTR_LIST)
488
489 req.subprocess_env['X_REMOTE_USER'] = username
490 req.subprocess_env['X_REMOTE_EMAIL'] = r[0][1]['mail'][0].lower()
491 req.subprocess_env['X_REMOTE_FIRSTNAME'] = "%s" % r[0][1]['givenname'][0]
492 req.subprocess_env['X_REMOTE_LASTNAME'] = "%s" % r[0][1]['sn'][0]
493 except Exception, e:
494 apache.log_error("error getting data from ldap %s" % str(e), apache.APLOG_ERR)
495
496 return apache.OK
497
430 498 .. note::
431 499 If you enable proxy pass-through authentication, make sure your server is
432 500 only accessible through the proxy. Otherwise, any client would be able to
@@ -29,7 +29,7 b' import logging'
29 29 from kallithea.lib import auth_modules
30 30 from kallithea.lib.utils2 import str2bool, safe_unicode
31 31 from kallithea.lib.compat import hybrid_property
32 from kallithea.model.db import User
32 from kallithea.model.db import User, Setting
33 33
34 34 log = logging.getLogger(__name__)
35 35
@@ -53,15 +53,39 b' class KallitheaAuthPlugin(auth_modules.K'
53 53 "name": "header",
54 54 "validator": self.validators.UnicodeString(strip=True, not_empty=True),
55 55 "type": "string",
56 "description": "Header to extract the user from",
56 "description": "Request header to extract the username from",
57 57 "default": "REMOTE_USER",
58 "formname": "Header"
58 "formname": "Username header"
59 },
60 {
61 "name": "email_header",
62 "validator": self.validators.UnicodeString(strip=True),
63 "type": "string",
64 "description": "Optional request header to extract the email from",
65 "default": "",
66 "formname": "Email header"
67 },
68 {
69 "name": "firstname_header",
70 "validator": self.validators.UnicodeString(strip=True),
71 "type": "string",
72 "description": "Optional request header to extract the first name from",
73 "default": "",
74 "formname": "Firstname header"
75 },
76 {
77 "name": "lastname_header",
78 "validator": self.validators.UnicodeString(strip=True),
79 "type": "string",
80 "description": "Optional request header to extract the last name from",
81 "default": "",
82 "formname": "Lastname header"
59 83 },
60 84 {
61 85 "name": "fallback_header",
62 86 "validator": self.validators.UnicodeString(strip=True),
63 87 "type": "string",
64 "description": "Header to extract the user from when main one fails",
88 "description": "Request header to extract the user from when main one fails",
65 89 "default": "HTTP_X_FORWARDED_USER",
66 90 "formname": "Fallback header"
67 91 },
@@ -172,9 +196,9 b' class KallitheaAuthPlugin(auth_modules.K'
172 196 # old attrs fetched from Kallithea database
173 197 admin = getattr(userobj, 'admin', False)
174 198 active = getattr(userobj, 'active', True)
175 email = getattr(userobj, 'email', '')
176 firstname = getattr(userobj, 'firstname', '')
177 lastname = getattr(userobj, 'lastname', '')
199 email = environ.get(settings.get('email_header'), getattr(userobj, 'email', ''))
200 firstname = environ.get(settings.get('firstname_header'), getattr(userobj, 'firstname', ''))
201 lastname = environ.get(settings.get('lastname_header'), getattr(userobj, 'lastname', ''))
178 202
179 203 user_data = {
180 204 'username': username,
@@ -192,4 +216,11 b' class KallitheaAuthPlugin(auth_modules.K'
192 216 return user_data
193 217
194 218 def get_managed_fields(self):
195 return ['username', 'password']
219 fields = ['username', 'password']
220 if(Setting.get_by_name('auth_container_email_header').app_settings_value):
221 fields.append('email')
222 if(Setting.get_by_name('auth_container_firstname_header').app_settings_value):
223 fields.append('firstname')
224 if(Setting.get_by_name('auth_container_lastname_header').app_settings_value):
225 fields.append('lastname')
226 return fields
@@ -135,6 +135,9 b' class TestAuthSettingsController(TestCon'
135 135 def test_container_auth_login_header(self):
136 136 self._container_auth_setup(
137 137 auth_container_header='THE_USER_NAME',
138 auth_container_email_header='',
139 auth_container_firstname_header='',
140 auth_container_lastname_header='',
138 141 auth_container_fallback_header='',
139 142 auth_container_clean_username='False',
140 143 )
@@ -143,9 +146,34 b' class TestAuthSettingsController(TestCon'
143 146 resulting_username='john@example.org',
144 147 )
145 148
149 def test_container_auth_login_header_attr(self):
150 self._container_auth_setup(
151 auth_container_header='THE_USER_NAME',
152 auth_container_email_header='THE_USER_EMAIL',
153 auth_container_firstname_header='THE_USER_FIRSTNAME',
154 auth_container_lastname_header='THE_USER_LASTNAME',
155 auth_container_fallback_header='',
156 auth_container_clean_username='False',
157 )
158 response = self.app.get(
159 url=url(controller='admin/my_account', action='my_account'),
160 extra_environ={'THE_USER_NAME': 'johnd',
161 'THE_USER_EMAIL': 'john@example.org',
162 'THE_USER_FIRSTNAME': 'John',
163 'THE_USER_LASTNAME': 'Doe',
164 }
165 )
166 self.assertEqual(response.form['email'].value, 'john@example.org')
167 self.assertEqual(response.form['firstname'].value, 'John')
168 self.assertEqual(response.form['lastname'].value, 'Doe')
169
170
146 171 def test_container_auth_login_fallback_header(self):
147 172 self._container_auth_setup(
148 173 auth_container_header='THE_USER_NAME',
174 auth_container_email_header='',
175 auth_container_firstname_header='',
176 auth_container_lastname_header='',
149 177 auth_container_fallback_header='HTTP_X_YZZY',
150 178 auth_container_clean_username='False',
151 179 )
@@ -157,6 +185,9 b' class TestAuthSettingsController(TestCon'
157 185 def test_container_auth_clean_username_at(self):
158 186 self._container_auth_setup(
159 187 auth_container_header='REMOTE_USER',
188 auth_container_email_header='',
189 auth_container_firstname_header='',
190 auth_container_lastname_header='',
160 191 auth_container_fallback_header='',
161 192 auth_container_clean_username='True',
162 193 )
@@ -168,6 +199,9 b' class TestAuthSettingsController(TestCon'
168 199 def test_container_auth_clean_username_backslash(self):
169 200 self._container_auth_setup(
170 201 auth_container_header='REMOTE_USER',
202 auth_container_email_header='',
203 auth_container_firstname_header='',
204 auth_container_lastname_header='',
171 205 auth_container_fallback_header='',
172 206 auth_container_clean_username='True',
173 207 )
@@ -179,6 +213,9 b' class TestAuthSettingsController(TestCon'
179 213 def test_container_auth_no_logout(self):
180 214 self._container_auth_setup(
181 215 auth_container_header='REMOTE_USER',
216 auth_container_email_header='',
217 auth_container_firstname_header='',
218 auth_container_lastname_header='',
182 219 auth_container_fallback_header='',
183 220 auth_container_clean_username='True',
184 221 )
General Comments 0
You need to be logged in to leave comments. Login now