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": " |
|
56 | "description": "Request header to extract the username from", | |
57 | "default": "REMOTE_USER", |
|
57 | "default": "REMOTE_USER", | |
58 |
"formname": " |
|
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": " |
|
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 |
|
|
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