##// 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 RequestHeader set X-Forwarded-User %{RU}e
427 RequestHeader set X-Forwarded-User %{RU}e
428 </Location>
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 .. note::
498 .. note::
431 If you enable proxy pass-through authentication, make sure your server is
499 If you enable proxy pass-through authentication, make sure your server is
432 only accessible through the proxy. Otherwise, any client would be able to
500 only accessible through the proxy. Otherwise, any client would be able to
@@ -29,7 +29,7 b' import logging'
29 from kallithea.lib import auth_modules
29 from kallithea.lib import auth_modules
30 from kallithea.lib.utils2 import str2bool, safe_unicode
30 from kallithea.lib.utils2 import str2bool, safe_unicode
31 from kallithea.lib.compat import hybrid_property
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 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
@@ -53,15 +53,39 b' class KallitheaAuthPlugin(auth_modules.K'
53 "name": "header",
53 "name": "header",
54 "validator": self.validators.UnicodeString(strip=True, not_empty=True),
54 "validator": self.validators.UnicodeString(strip=True, not_empty=True),
55 "type": "string",
55 "type": "string",
56 "description": "Header to extract the user from",
56 "description": "Request header to extract the username from",
57 "default": "REMOTE_USER",
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 "name": "fallback_header",
85 "name": "fallback_header",
62 "validator": self.validators.UnicodeString(strip=True),
86 "validator": self.validators.UnicodeString(strip=True),
63 "type": "string",
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 "default": "HTTP_X_FORWARDED_USER",
89 "default": "HTTP_X_FORWARDED_USER",
66 "formname": "Fallback header"
90 "formname": "Fallback header"
67 },
91 },
@@ -172,9 +196,9 b' class KallitheaAuthPlugin(auth_modules.K'
172 # old attrs fetched from Kallithea database
196 # old attrs fetched from Kallithea database
173 admin = getattr(userobj, 'admin', False)
197 admin = getattr(userobj, 'admin', False)
174 active = getattr(userobj, 'active', True)
198 active = getattr(userobj, 'active', True)
175 email = getattr(userobj, 'email', '')
199 email = environ.get(settings.get('email_header'), getattr(userobj, 'email', ''))
176 firstname = getattr(userobj, 'firstname', '')
200 firstname = environ.get(settings.get('firstname_header'), getattr(userobj, 'firstname', ''))
177 lastname = getattr(userobj, 'lastname', '')
201 lastname = environ.get(settings.get('lastname_header'), getattr(userobj, 'lastname', ''))
178
202
179 user_data = {
203 user_data = {
180 'username': username,
204 'username': username,
@@ -192,4 +216,11 b' class KallitheaAuthPlugin(auth_modules.K'
192 return user_data
216 return user_data
193
217
194 def get_managed_fields(self):
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 def test_container_auth_login_header(self):
135 def test_container_auth_login_header(self):
136 self._container_auth_setup(
136 self._container_auth_setup(
137 auth_container_header='THE_USER_NAME',
137 auth_container_header='THE_USER_NAME',
138 auth_container_email_header='',
139 auth_container_firstname_header='',
140 auth_container_lastname_header='',
138 auth_container_fallback_header='',
141 auth_container_fallback_header='',
139 auth_container_clean_username='False',
142 auth_container_clean_username='False',
140 )
143 )
@@ -143,9 +146,34 b' class TestAuthSettingsController(TestCon'
143 resulting_username='john@example.org',
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 def test_container_auth_login_fallback_header(self):
171 def test_container_auth_login_fallback_header(self):
147 self._container_auth_setup(
172 self._container_auth_setup(
148 auth_container_header='THE_USER_NAME',
173 auth_container_header='THE_USER_NAME',
174 auth_container_email_header='',
175 auth_container_firstname_header='',
176 auth_container_lastname_header='',
149 auth_container_fallback_header='HTTP_X_YZZY',
177 auth_container_fallback_header='HTTP_X_YZZY',
150 auth_container_clean_username='False',
178 auth_container_clean_username='False',
151 )
179 )
@@ -157,6 +185,9 b' class TestAuthSettingsController(TestCon'
157 def test_container_auth_clean_username_at(self):
185 def test_container_auth_clean_username_at(self):
158 self._container_auth_setup(
186 self._container_auth_setup(
159 auth_container_header='REMOTE_USER',
187 auth_container_header='REMOTE_USER',
188 auth_container_email_header='',
189 auth_container_firstname_header='',
190 auth_container_lastname_header='',
160 auth_container_fallback_header='',
191 auth_container_fallback_header='',
161 auth_container_clean_username='True',
192 auth_container_clean_username='True',
162 )
193 )
@@ -168,6 +199,9 b' class TestAuthSettingsController(TestCon'
168 def test_container_auth_clean_username_backslash(self):
199 def test_container_auth_clean_username_backslash(self):
169 self._container_auth_setup(
200 self._container_auth_setup(
170 auth_container_header='REMOTE_USER',
201 auth_container_header='REMOTE_USER',
202 auth_container_email_header='',
203 auth_container_firstname_header='',
204 auth_container_lastname_header='',
171 auth_container_fallback_header='',
205 auth_container_fallback_header='',
172 auth_container_clean_username='True',
206 auth_container_clean_username='True',
173 )
207 )
@@ -179,6 +213,9 b' class TestAuthSettingsController(TestCon'
179 def test_container_auth_no_logout(self):
213 def test_container_auth_no_logout(self):
180 self._container_auth_setup(
214 self._container_auth_setup(
181 auth_container_header='REMOTE_USER',
215 auth_container_header='REMOTE_USER',
216 auth_container_email_header='',
217 auth_container_firstname_header='',
218 auth_container_lastname_header='',
182 auth_container_fallback_header='',
219 auth_container_fallback_header='',
183 auth_container_clean_username='True',
220 auth_container_clean_username='True',
184 )
221 )
General Comments 0
You need to be logged in to leave comments. Login now