##// END OF EJS Templates
auth: refactor ldap parameter handling - make it clear that port is optional
Mads Kiilerich -
r6294:949c843b default
parent child Browse files
Show More
@@ -1,871 +1,874 b''
1 .. _setup:
1 .. _setup:
2
2
3 =====
3 =====
4 Setup
4 Setup
5 =====
5 =====
6
6
7
7
8 Setting up Kallithea
8 Setting up Kallithea
9 --------------------
9 --------------------
10
10
11 First, you will need to create a Kallithea configuration file. Run the
11 First, you will need to create a Kallithea configuration file. Run the
12 following command to do so::
12 following command to do so::
13
13
14 paster make-config Kallithea my.ini
14 paster make-config Kallithea my.ini
15
15
16 This will create the file ``my.ini`` in the current directory. This
16 This will create the file ``my.ini`` in the current directory. This
17 configuration file contains the various settings for Kallithea, e.g.
17 configuration file contains the various settings for Kallithea, e.g.
18 proxy port, email settings, usage of static files, cache, Celery
18 proxy port, email settings, usage of static files, cache, Celery
19 settings, and logging.
19 settings, and logging.
20
20
21 Next, you need to create the databases used by Kallithea. It is recommended to
21 Next, you need to create the databases used by Kallithea. It is recommended to
22 use PostgreSQL or SQLite (default). If you choose a database other than the
22 use PostgreSQL or SQLite (default). If you choose a database other than the
23 default, ensure you properly adjust the database URL in your ``my.ini``
23 default, ensure you properly adjust the database URL in your ``my.ini``
24 configuration file to use this other database. Kallithea currently supports
24 configuration file to use this other database. Kallithea currently supports
25 PostgreSQL, SQLite and MySQL databases. Create the database by running
25 PostgreSQL, SQLite and MySQL databases. Create the database by running
26 the following command::
26 the following command::
27
27
28 paster setup-db my.ini
28 paster setup-db my.ini
29
29
30 This will prompt you for a "root" path. This "root" path is the location where
30 This will prompt you for a "root" path. This "root" path is the location where
31 Kallithea will store all of its repositories on the current machine. After
31 Kallithea will store all of its repositories on the current machine. After
32 entering this "root" path ``setup-db`` will also prompt you for a username
32 entering this "root" path ``setup-db`` will also prompt you for a username
33 and password for the initial admin account which ``setup-db`` sets
33 and password for the initial admin account which ``setup-db`` sets
34 up for you.
34 up for you.
35
35
36 The ``setup-db`` values can also be given on the command line.
36 The ``setup-db`` values can also be given on the command line.
37 Example::
37 Example::
38
38
39 paster setup-db my.ini --user=nn --password=secret --email=nn@example.com --repos=/srv/repos
39 paster setup-db my.ini --user=nn --password=secret --email=nn@example.com --repos=/srv/repos
40
40
41 The ``setup-db`` command will create all needed tables and an
41 The ``setup-db`` command will create all needed tables and an
42 admin account. When choosing a root path you can either use a new
42 admin account. When choosing a root path you can either use a new
43 empty location, or a location which already contains existing
43 empty location, or a location which already contains existing
44 repositories. If you choose a location which contains existing
44 repositories. If you choose a location which contains existing
45 repositories Kallithea will add all of the repositories at the chosen
45 repositories Kallithea will add all of the repositories at the chosen
46 location to its database. (Note: make sure you specify the correct
46 location to its database. (Note: make sure you specify the correct
47 path to the root).
47 path to the root).
48
48
49 .. note:: the given path for Mercurial_ repositories **must** be write
49 .. note:: the given path for Mercurial_ repositories **must** be write
50 accessible for the application. It's very important since
50 accessible for the application. It's very important since
51 the Kallithea web interface will work without write access,
51 the Kallithea web interface will work without write access,
52 but when trying to do a push it will fail with permission
52 but when trying to do a push it will fail with permission
53 denied errors unless it has write access.
53 denied errors unless it has write access.
54
54
55 You are now ready to use Kallithea. To run it simply execute::
55 You are now ready to use Kallithea. To run it simply execute::
56
56
57 paster serve my.ini
57 paster serve my.ini
58
58
59 - This command runs the Kallithea server. The web app should be available at
59 - This command runs the Kallithea server. The web app should be available at
60 http://127.0.0.1:5000. The IP address and port is configurable via the
60 http://127.0.0.1:5000. The IP address and port is configurable via the
61 configuration file created in the previous step.
61 configuration file created in the previous step.
62 - Log in to Kallithea using the admin account created when running ``setup-db``.
62 - Log in to Kallithea using the admin account created when running ``setup-db``.
63 - The default permissions on each repository is read, and the owner is admin.
63 - The default permissions on each repository is read, and the owner is admin.
64 Remember to update these if needed.
64 Remember to update these if needed.
65 - In the admin panel you can toggle LDAP, anonymous, and permissions
65 - In the admin panel you can toggle LDAP, anonymous, and permissions
66 settings, as well as edit more advanced options on users and
66 settings, as well as edit more advanced options on users and
67 repositories.
67 repositories.
68
68
69
69
70 Using Kallithea with SSH
70 Using Kallithea with SSH
71 ------------------------
71 ------------------------
72
72
73 Kallithea currently only hosts repositories using http and https. (The addition
73 Kallithea currently only hosts repositories using http and https. (The addition
74 of ssh hosting is a planned future feature.) However you can easily use ssh in
74 of ssh hosting is a planned future feature.) However you can easily use ssh in
75 parallel with Kallithea. (Repository access via ssh is a standard "out of
75 parallel with Kallithea. (Repository access via ssh is a standard "out of
76 the box" feature of Mercurial_ and you can use this to access any of the
76 the box" feature of Mercurial_ and you can use this to access any of the
77 repositories that Kallithea is hosting. See PublishingRepositories_)
77 repositories that Kallithea is hosting. See PublishingRepositories_)
78
78
79 Kallithea repository structures are kept in directories with the same name
79 Kallithea repository structures are kept in directories with the same name
80 as the project. When using repository groups, each group is a subdirectory.
80 as the project. When using repository groups, each group is a subdirectory.
81 This allows you to easily use ssh for accessing repositories.
81 This allows you to easily use ssh for accessing repositories.
82
82
83 In order to use ssh you need to make sure that your web server and the users'
83 In order to use ssh you need to make sure that your web server and the users'
84 login accounts have the correct permissions set on the appropriate directories.
84 login accounts have the correct permissions set on the appropriate directories.
85
85
86 .. note:: These permissions are independent of any permissions you
86 .. note:: These permissions are independent of any permissions you
87 have set up using the Kallithea web interface.
87 have set up using the Kallithea web interface.
88
88
89 If your main directory (the same as set in Kallithea settings) is for
89 If your main directory (the same as set in Kallithea settings) is for
90 example set to ``/srv/repos`` and the repository you are using is
90 example set to ``/srv/repos`` and the repository you are using is
91 named ``kallithea``, then to clone via ssh you should run::
91 named ``kallithea``, then to clone via ssh you should run::
92
92
93 hg clone ssh://user@kallithea.example.com/srv/repos/kallithea
93 hg clone ssh://user@kallithea.example.com/srv/repos/kallithea
94
94
95 Using other external tools such as mercurial-server_ or using ssh key-based
95 Using other external tools such as mercurial-server_ or using ssh key-based
96 authentication is fully supported.
96 authentication is fully supported.
97
97
98 .. note:: In an advanced setup, in order for your ssh access to use
98 .. note:: In an advanced setup, in order for your ssh access to use
99 the same permissions as set up via the Kallithea web
99 the same permissions as set up via the Kallithea web
100 interface, you can create an authentication hook to connect
100 interface, you can create an authentication hook to connect
101 to the Kallithea db and run check functions for permissions
101 to the Kallithea db and run check functions for permissions
102 against that.
102 against that.
103
103
104
104
105 Setting up Whoosh full text search
105 Setting up Whoosh full text search
106 ----------------------------------
106 ----------------------------------
107
107
108 Kallithea provides full text search of repositories using `Whoosh`__.
108 Kallithea provides full text search of repositories using `Whoosh`__.
109
109
110 .. __: https://pythonhosted.org/Whoosh/
110 .. __: https://pythonhosted.org/Whoosh/
111
111
112 For an incremental index build, run::
112 For an incremental index build, run::
113
113
114 paster make-index my.ini
114 paster make-index my.ini
115
115
116 For a full index rebuild, run::
116 For a full index rebuild, run::
117
117
118 paster make-index my.ini -f
118 paster make-index my.ini -f
119
119
120 The ``--repo-location`` option allows the location of the repositories to be overridden;
120 The ``--repo-location`` option allows the location of the repositories to be overridden;
121 usually, the location is retrieved from the Kallithea database.
121 usually, the location is retrieved from the Kallithea database.
122
122
123 The ``--index-only`` option can be used to limit the indexed repositories to a comma-separated list::
123 The ``--index-only`` option can be used to limit the indexed repositories to a comma-separated list::
124
124
125 paster make-index my.ini --index-only=vcs,kallithea
125 paster make-index my.ini --index-only=vcs,kallithea
126
126
127 To keep your index up-to-date it is necessary to do periodic index builds;
127 To keep your index up-to-date it is necessary to do periodic index builds;
128 for this, it is recommended to use a crontab entry. Example::
128 for this, it is recommended to use a crontab entry. Example::
129
129
130 0 3 * * * /path/to/virtualenv/bin/paster make-index /path/to/kallithea/my.ini
130 0 3 * * * /path/to/virtualenv/bin/paster make-index /path/to/kallithea/my.ini
131
131
132 When using incremental mode (the default), Whoosh will check the last
132 When using incremental mode (the default), Whoosh will check the last
133 modification date of each file and add it to be reindexed if a newer file is
133 modification date of each file and add it to be reindexed if a newer file is
134 available. The indexing daemon checks for any removed files and removes them
134 available. The indexing daemon checks for any removed files and removes them
135 from index.
135 from index.
136
136
137 If you want to rebuild the index from scratch, you can use the ``-f`` flag as above,
137 If you want to rebuild the index from scratch, you can use the ``-f`` flag as above,
138 or in the admin panel you can check the "build from scratch" checkbox.
138 or in the admin panel you can check the "build from scratch" checkbox.
139
139
140 .. _ldap-setup:
140 .. _ldap-setup:
141
141
142
142
143 Setting up LDAP support
143 Setting up LDAP support
144 -----------------------
144 -----------------------
145
145
146 Kallithea supports LDAP authentication. In order
146 Kallithea supports LDAP authentication. In order
147 to use LDAP, you have to install the python-ldap_ package. This package is
147 to use LDAP, you have to install the python-ldap_ package. This package is
148 available via PyPI, so you can install it by running::
148 available via PyPI, so you can install it by running::
149
149
150 pip install python-ldap
150 pip install python-ldap
151
151
152 .. note:: ``python-ldap`` requires some libraries to be installed on
152 .. note:: ``python-ldap`` requires some libraries to be installed on
153 your system, so before installing it check that you have at
153 your system, so before installing it check that you have at
154 least the ``openldap`` and ``sasl`` libraries.
154 least the ``openldap`` and ``sasl`` libraries.
155
155
156 Choose *Admin > Authentication*, click the ``kallithea.lib.auth_modules.auth_ldap`` button
156 Choose *Admin > Authentication*, click the ``kallithea.lib.auth_modules.auth_ldap`` button
157 and then *Save*, to enable the LDAP plugin and configure its settings.
157 and then *Save*, to enable the LDAP plugin and configure its settings.
158
158
159 Here's a typical LDAP setup::
159 Here's a typical LDAP setup::
160
160
161 Connection settings
161 Connection settings
162 Enable LDAP = checked
162 Enable LDAP = checked
163 Host = host.example.com
163 Host = host.example.com
164 Port = 389
165 Account = <account>
164 Account = <account>
166 Password = <password>
165 Password = <password>
167 Connection Security = LDAPS connection
166 Connection Security = LDAPS connection
168 Certificate Checks = DEMAND
167 Certificate Checks = DEMAND
169
168
170 Search settings
169 Search settings
171 Base DN = CN=users,DC=host,DC=example,DC=org
170 Base DN = CN=users,DC=host,DC=example,DC=org
172 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
171 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
173 LDAP Search Scope = SUBTREE
172 LDAP Search Scope = SUBTREE
174
173
175 Attribute mappings
174 Attribute mappings
176 Login Attribute = uid
175 Login Attribute = uid
177 First Name Attribute = firstName
176 First Name Attribute = firstName
178 Last Name Attribute = lastName
177 Last Name Attribute = lastName
179 Email Attribute = mail
178 Email Attribute = mail
180
179
181 If your user groups are placed in an Organisation Unit (OU) structure, the Search Settings configuration differs::
180 If your user groups are placed in an Organisation Unit (OU) structure, the Search Settings configuration differs::
182
181
183 Search settings
182 Search settings
184 Base DN = DC=host,DC=example,DC=org
183 Base DN = DC=host,DC=example,DC=org
185 LDAP Filter = (&(memberOf=CN=your user group,OU=subunit,OU=unit,DC=host,DC=example,DC=org)(objectClass=user))
184 LDAP Filter = (&(memberOf=CN=your user group,OU=subunit,OU=unit,DC=host,DC=example,DC=org)(objectClass=user))
186 LDAP Search Scope = SUBTREE
185 LDAP Search Scope = SUBTREE
187
186
188 .. _enable_ldap:
187 .. _enable_ldap:
189
188
190 Enable LDAP : required
189 Enable LDAP : required
191 Whether to use LDAP for authenticating users.
190 Whether to use LDAP for authenticating users.
192
191
193 .. _ldap_host:
192 .. _ldap_host:
194
193
195 Host : required
194 Host : required
196 LDAP server hostname or IP address. Can be also a comma separated
195 LDAP server hostname or IP address. Can be also a comma separated
197 list of servers to support LDAP fail-over.
196 list of servers to support LDAP fail-over.
198
197
199 .. _Port:
198 .. _Port:
200
199
201 Port : required
200 Port : optional
202 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
201 Defaults to 389 for PLAIN un-encrypted LDAP and START_TLS.
202 Defaults to 636 for LDAPS.
203
203
204 .. _ldap_account:
204 .. _ldap_account:
205
205
206 Account : optional
206 Account : optional
207 Only required if the LDAP server does not allow anonymous browsing of
207 Only required if the LDAP server does not allow anonymous browsing of
208 records. This should be a special account for record browsing. This
208 records. This should be a special account for record browsing. This
209 will require `LDAP Password`_ below.
209 will require `LDAP Password`_ below.
210
210
211 .. _LDAP Password:
211 .. _LDAP Password:
212
212
213 Password : optional
213 Password : optional
214 Only required if the LDAP server does not allow anonymous browsing of
214 Only required if the LDAP server does not allow anonymous browsing of
215 records.
215 records.
216
216
217 .. _Enable LDAPS:
217 .. _Enable LDAPS:
218
218
219 Connection Security : required
219 Connection Security : required
220 Defines the connection to LDAP server
220 Defines the connection to LDAP server
221
221
222 No encryption
222 PLAIN
223 Plain non encrypted connection
223 Plain unencrypted LDAP connection.
224 This will by default use `Port`_ 389.
224
225
225 LDAPS connection
226 LDAPS
226 Enable LDAPS connections. It will likely require `Port`_ to be set to
227 Use secure LDAPS connections according to `Certificate
227 a different value (standard LDAPS port is 636). When LDAPS is enabled
228 Checks`_ configuration.
228 then `Certificate Checks`_ is required.
229 This will by default use `Port`_ 636.
229
230
230 START_TLS on LDAP connection
231 START_TLS
231 START TLS connection
232 Use START TLS according to `Certificate Checks`_ configuration on an
233 apparently "plain" LDAP connection.
234 This will by default use `Port`_ 389.
232
235
233 .. _Certificate Checks:
236 .. _Certificate Checks:
234
237
235 Certificate Checks : optional
238 Certificate Checks : optional
236 How SSL certificates verification is handled -- this is only useful when
239 How SSL certificates verification is handled -- this is only useful when
237 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
240 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
238 with mandatory certificate validation, while the other options are
241 with mandatory certificate validation, while the other options are
239 susceptible to man-in-the-middle attacks.
242 susceptible to man-in-the-middle attacks.
240
243
241 NEVER
244 NEVER
242 A serve certificate will never be requested or checked.
245 A serve certificate will never be requested or checked.
243
246
244 ALLOW
247 ALLOW
245 A server certificate is requested. Failure to provide a
248 A server certificate is requested. Failure to provide a
246 certificate or providing a bad certificate will not terminate the
249 certificate or providing a bad certificate will not terminate the
247 session.
250 session.
248
251
249 TRY
252 TRY
250 A server certificate is requested. Failure to provide a
253 A server certificate is requested. Failure to provide a
251 certificate does not halt the session; providing a bad certificate
254 certificate does not halt the session; providing a bad certificate
252 halts the session.
255 halts the session.
253
256
254 DEMAND
257 DEMAND
255 A server certificate is requested and must be provided and
258 A server certificate is requested and must be provided and
256 authenticated for the session to proceed.
259 authenticated for the session to proceed.
257
260
258 HARD
261 HARD
259 The same as DEMAND.
262 The same as DEMAND.
260
263
261 .. _Custom CA Certificates:
264 .. _Custom CA Certificates:
262
265
263 Custom CA Certificates : optional
266 Custom CA Certificates : optional
264 Directory used by OpenSSL to find CAs for validating the LDAP server certificate.
267 Directory used by OpenSSL to find CAs for validating the LDAP server certificate.
265 Python 2.7.10 and later default to using the system certificate store, and
268 Python 2.7.10 and later default to using the system certificate store, and
266 this should thus not be necessary when using certificates signed by a CA
269 this should thus not be necessary when using certificates signed by a CA
267 trusted by the system.
270 trusted by the system.
268 It can be set to something like `/etc/openldap/cacerts` on older systems or
271 It can be set to something like `/etc/openldap/cacerts` on older systems or
269 if using self-signed certificates.
272 if using self-signed certificates.
270
273
271 .. _Base DN:
274 .. _Base DN:
272
275
273 Base DN : required
276 Base DN : required
274 The Distinguished Name (DN) where searches for users will be performed.
277 The Distinguished Name (DN) where searches for users will be performed.
275 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
278 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
276
279
277 .. _LDAP Filter:
280 .. _LDAP Filter:
278
281
279 LDAP Filter : optional
282 LDAP Filter : optional
280 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
283 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
281 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
284 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
282 which LDAP objects are identified as representing Users for
285 which LDAP objects are identified as representing Users for
283 authentication. The filter is augmented by `Login Attribute`_ below.
286 authentication. The filter is augmented by `Login Attribute`_ below.
284 This can commonly be left blank.
287 This can commonly be left blank.
285
288
286 .. _LDAP Search Scope:
289 .. _LDAP Search Scope:
287
290
288 LDAP Search Scope : required
291 LDAP Search Scope : required
289 This limits how far LDAP will search for a matching object.
292 This limits how far LDAP will search for a matching object.
290
293
291 BASE
294 BASE
292 Only allows searching of `Base DN`_ and is usually not what you
295 Only allows searching of `Base DN`_ and is usually not what you
293 want.
296 want.
294
297
295 ONELEVEL
298 ONELEVEL
296 Searches all entries under `Base DN`_, but not Base DN itself.
299 Searches all entries under `Base DN`_, but not Base DN itself.
297
300
298 SUBTREE
301 SUBTREE
299 Searches all entries below `Base DN`_, but not Base DN itself.
302 Searches all entries below `Base DN`_, but not Base DN itself.
300 When using SUBTREE `LDAP Filter`_ is useful to limit object
303 When using SUBTREE `LDAP Filter`_ is useful to limit object
301 location.
304 location.
302
305
303 .. _Login Attribute:
306 .. _Login Attribute:
304
307
305 Login Attribute : required
308 Login Attribute : required
306 The LDAP record attribute that will be matched as the USERNAME or
309 The LDAP record attribute that will be matched as the USERNAME or
307 ACCOUNT used to connect to Kallithea. This will be added to `LDAP
310 ACCOUNT used to connect to Kallithea. This will be added to `LDAP
308 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
311 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
309 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
312 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
310 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
313 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
311 ::
314 ::
312
315
313 (&(LDAPFILTER)(uid=jsmith))
316 (&(LDAPFILTER)(uid=jsmith))
314
317
315 .. _ldap_attr_firstname:
318 .. _ldap_attr_firstname:
316
319
317 First Name Attribute : required
320 First Name Attribute : required
318 The LDAP record attribute which represents the user's first name.
321 The LDAP record attribute which represents the user's first name.
319
322
320 .. _ldap_attr_lastname:
323 .. _ldap_attr_lastname:
321
324
322 Last Name Attribute : required
325 Last Name Attribute : required
323 The LDAP record attribute which represents the user's last name.
326 The LDAP record attribute which represents the user's last name.
324
327
325 .. _ldap_attr_email:
328 .. _ldap_attr_email:
326
329
327 Email Attribute : required
330 Email Attribute : required
328 The LDAP record attribute which represents the user's email address.
331 The LDAP record attribute which represents the user's email address.
329
332
330 If all data are entered correctly, and python-ldap_ is properly installed
333 If all data are entered correctly, and python-ldap_ is properly installed
331 users should be granted access to Kallithea with LDAP accounts. At this
334 users should be granted access to Kallithea with LDAP accounts. At this
332 time user information is copied from LDAP into the Kallithea user database.
335 time user information is copied from LDAP into the Kallithea user database.
333 This means that updates of an LDAP user object may not be reflected as a
336 This means that updates of an LDAP user object may not be reflected as a
334 user update in Kallithea.
337 user update in Kallithea.
335
338
336 If You have problems with LDAP access and believe You entered correct
339 If You have problems with LDAP access and believe You entered correct
337 information check out the Kallithea logs, any error messages sent from LDAP
340 information check out the Kallithea logs, any error messages sent from LDAP
338 will be saved there.
341 will be saved there.
339
342
340 Active Directory
343 Active Directory
341 ^^^^^^^^^^^^^^^^
344 ^^^^^^^^^^^^^^^^
342
345
343 Kallithea can use Microsoft Active Directory for user authentication. This
346 Kallithea can use Microsoft Active Directory for user authentication. This
344 is done through an LDAP or LDAPS connection to Active Directory. The
347 is done through an LDAP or LDAPS connection to Active Directory. The
345 following LDAP configuration settings are typical for using Active
348 following LDAP configuration settings are typical for using Active
346 Directory ::
349 Directory ::
347
350
348 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
351 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
349 Login Attribute = sAMAccountName
352 Login Attribute = sAMAccountName
350 First Name Attribute = givenName
353 First Name Attribute = givenName
351 Last Name Attribute = sn
354 Last Name Attribute = sn
352 Email Attribute = mail
355 Email Attribute = mail
353
356
354 All other LDAP settings will likely be site-specific and should be
357 All other LDAP settings will likely be site-specific and should be
355 appropriately configured.
358 appropriately configured.
356
359
357
360
358 Authentication by container or reverse-proxy
361 Authentication by container or reverse-proxy
359 --------------------------------------------
362 --------------------------------------------
360
363
361 Kallithea supports delegating the authentication
364 Kallithea supports delegating the authentication
362 of users to its WSGI container, or to a reverse-proxy server through which all
365 of users to its WSGI container, or to a reverse-proxy server through which all
363 clients access the application.
366 clients access the application.
364
367
365 When these authentication methods are enabled in Kallithea, it uses the
368 When these authentication methods are enabled in Kallithea, it uses the
366 username that the container/proxy (Apache or Nginx, etc.) provides and doesn't
369 username that the container/proxy (Apache or Nginx, etc.) provides and doesn't
367 perform the authentication itself. The authorization, however, is still done by
370 perform the authentication itself. The authorization, however, is still done by
368 Kallithea according to its settings.
371 Kallithea according to its settings.
369
372
370 When a user logs in for the first time using these authentication methods,
373 When a user logs in for the first time using these authentication methods,
371 a matching user account is created in Kallithea with default permissions. An
374 a matching user account is created in Kallithea with default permissions. An
372 administrator can then modify it using Kallithea's admin interface.
375 administrator can then modify it using Kallithea's admin interface.
373
376
374 It's also possible for an administrator to create accounts and configure their
377 It's also possible for an administrator to create accounts and configure their
375 permissions before the user logs in for the first time, using the :ref:`create-user` API.
378 permissions before the user logs in for the first time, using the :ref:`create-user` API.
376
379
377 Container-based authentication
380 Container-based authentication
378 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
381 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
379
382
380 In a container-based authentication setup, Kallithea reads the user name from
383 In a container-based authentication setup, Kallithea reads the user name from
381 the ``REMOTE_USER`` server variable provided by the WSGI container.
384 the ``REMOTE_USER`` server variable provided by the WSGI container.
382
385
383 After setting up your container (see `Apache with mod_wsgi`_), you'll need
386 After setting up your container (see `Apache with mod_wsgi`_), you'll need
384 to configure it to require authentication on the location configured for
387 to configure it to require authentication on the location configured for
385 Kallithea.
388 Kallithea.
386
389
387 Proxy pass-through authentication
390 Proxy pass-through authentication
388 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
391 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
389
392
390 In a proxy pass-through authentication setup, Kallithea reads the user name
393 In a proxy pass-through authentication setup, Kallithea reads the user name
391 from the ``X-Forwarded-User`` request header, which should be configured to be
394 from the ``X-Forwarded-User`` request header, which should be configured to be
392 sent by the reverse-proxy server.
395 sent by the reverse-proxy server.
393
396
394 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
397 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
395 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'll need to
398 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'll need to
396 configure the authentication and add the username in a request header named
399 configure the authentication and add the username in a request header named
397 ``X-Forwarded-User``.
400 ``X-Forwarded-User``.
398
401
399 For example, the following config section for Apache sets a subdirectory in a
402 For example, the following config section for Apache sets a subdirectory in a
400 reverse-proxy setup with basic auth:
403 reverse-proxy setup with basic auth:
401
404
402 .. code-block:: apache
405 .. code-block:: apache
403
406
404 <Location /someprefix>
407 <Location /someprefix>
405 ProxyPass http://127.0.0.1:5000/someprefix
408 ProxyPass http://127.0.0.1:5000/someprefix
406 ProxyPassReverse http://127.0.0.1:5000/someprefix
409 ProxyPassReverse http://127.0.0.1:5000/someprefix
407 SetEnvIf X-Url-Scheme https HTTPS=1
410 SetEnvIf X-Url-Scheme https HTTPS=1
408
411
409 AuthType Basic
412 AuthType Basic
410 AuthName "Kallithea authentication"
413 AuthName "Kallithea authentication"
411 AuthUserFile /srv/kallithea/.htpasswd
414 AuthUserFile /srv/kallithea/.htpasswd
412 Require valid-user
415 Require valid-user
413
416
414 RequestHeader unset X-Forwarded-User
417 RequestHeader unset X-Forwarded-User
415
418
416 RewriteEngine On
419 RewriteEngine On
417 RewriteCond %{LA-U:REMOTE_USER} (.+)
420 RewriteCond %{LA-U:REMOTE_USER} (.+)
418 RewriteRule .* - [E=RU:%1]
421 RewriteRule .* - [E=RU:%1]
419 RequestHeader set X-Forwarded-User %{RU}e
422 RequestHeader set X-Forwarded-User %{RU}e
420 </Location>
423 </Location>
421
424
422 Setting metadata in container/reverse-proxy
425 Setting metadata in container/reverse-proxy
423 """""""""""""""""""""""""""""""""""""""""""
426 """""""""""""""""""""""""""""""""""""""""""
424 When a new user account is created on the first login, Kallithea has no information about
427 When a new user account is created on the first login, Kallithea has no information about
425 the user's email and full name. So you can set some additional request headers like in the
428 the user's email and full name. So you can set some additional request headers like in the
426 example below. In this example the user is authenticated via Kerberos and an Apache
429 example below. In this example the user is authenticated via Kerberos and an Apache
427 mod_python fixup handler is used to get the user information from a LDAP server. But you
430 mod_python fixup handler is used to get the user information from a LDAP server. But you
428 could set the request headers however you want.
431 could set the request headers however you want.
429
432
430 .. code-block:: apache
433 .. code-block:: apache
431
434
432 <Location /someprefix>
435 <Location /someprefix>
433 ProxyPass http://127.0.0.1:5000/someprefix
436 ProxyPass http://127.0.0.1:5000/someprefix
434 ProxyPassReverse http://127.0.0.1:5000/someprefix
437 ProxyPassReverse http://127.0.0.1:5000/someprefix
435 SetEnvIf X-Url-Scheme https HTTPS=1
438 SetEnvIf X-Url-Scheme https HTTPS=1
436
439
437 AuthName "Kerberos Login"
440 AuthName "Kerberos Login"
438 AuthType Kerberos
441 AuthType Kerberos
439 Krb5Keytab /etc/apache2/http.keytab
442 Krb5Keytab /etc/apache2/http.keytab
440 KrbMethodK5Passwd off
443 KrbMethodK5Passwd off
441 KrbVerifyKDC on
444 KrbVerifyKDC on
442 Require valid-user
445 Require valid-user
443
446
444 PythonFixupHandler ldapmetadata
447 PythonFixupHandler ldapmetadata
445
448
446 RequestHeader set X_REMOTE_USER %{X_REMOTE_USER}e
449 RequestHeader set X_REMOTE_USER %{X_REMOTE_USER}e
447 RequestHeader set X_REMOTE_EMAIL %{X_REMOTE_EMAIL}e
450 RequestHeader set X_REMOTE_EMAIL %{X_REMOTE_EMAIL}e
448 RequestHeader set X_REMOTE_FIRSTNAME %{X_REMOTE_FIRSTNAME}e
451 RequestHeader set X_REMOTE_FIRSTNAME %{X_REMOTE_FIRSTNAME}e
449 RequestHeader set X_REMOTE_LASTNAME %{X_REMOTE_LASTNAME}e
452 RequestHeader set X_REMOTE_LASTNAME %{X_REMOTE_LASTNAME}e
450 </Location>
453 </Location>
451
454
452 .. code-block:: python
455 .. code-block:: python
453
456
454 from mod_python import apache
457 from mod_python import apache
455 import ldap
458 import ldap
456
459
457 LDAP_SERVER = "ldap://server.mydomain.com:389"
460 LDAP_SERVER = "ldap://server.mydomain.com:389"
458 LDAP_USER = ""
461 LDAP_USER = ""
459 LDAP_PASS = ""
462 LDAP_PASS = ""
460 LDAP_ROOT = "dc=mydomain,dc=com"
463 LDAP_ROOT = "dc=mydomain,dc=com"
461 LDAP_FILTER = "sAMAccountName=%s"
464 LDAP_FILTER = "sAMAccountName=%s"
462 LDAP_ATTR_LIST = ['sAMAccountName','givenname','sn','mail']
465 LDAP_ATTR_LIST = ['sAMAccountName','givenname','sn','mail']
463
466
464 def fixuphandler(req):
467 def fixuphandler(req):
465 if req.user is None:
468 if req.user is None:
466 # no user to search for
469 # no user to search for
467 return apache.OK
470 return apache.OK
468 else:
471 else:
469 try:
472 try:
470 if('\\' in req.user):
473 if('\\' in req.user):
471 username = req.user.split('\\')[1]
474 username = req.user.split('\\')[1]
472 elif('@' in req.user):
475 elif('@' in req.user):
473 username = req.user.split('@')[0]
476 username = req.user.split('@')[0]
474 else:
477 else:
475 username = req.user
478 username = req.user
476 l = ldap.initialize(LDAP_SERVER)
479 l = ldap.initialize(LDAP_SERVER)
477 l.simple_bind_s(LDAP_USER, LDAP_PASS)
480 l.simple_bind_s(LDAP_USER, LDAP_PASS)
478 r = l.search_s(LDAP_ROOT, ldap.SCOPE_SUBTREE, LDAP_FILTER % username, attrlist=LDAP_ATTR_LIST)
481 r = l.search_s(LDAP_ROOT, ldap.SCOPE_SUBTREE, LDAP_FILTER % username, attrlist=LDAP_ATTR_LIST)
479
482
480 req.subprocess_env['X_REMOTE_USER'] = username
483 req.subprocess_env['X_REMOTE_USER'] = username
481 req.subprocess_env['X_REMOTE_EMAIL'] = r[0][1]['mail'][0].lower()
484 req.subprocess_env['X_REMOTE_EMAIL'] = r[0][1]['mail'][0].lower()
482 req.subprocess_env['X_REMOTE_FIRSTNAME'] = "%s" % r[0][1]['givenname'][0]
485 req.subprocess_env['X_REMOTE_FIRSTNAME'] = "%s" % r[0][1]['givenname'][0]
483 req.subprocess_env['X_REMOTE_LASTNAME'] = "%s" % r[0][1]['sn'][0]
486 req.subprocess_env['X_REMOTE_LASTNAME'] = "%s" % r[0][1]['sn'][0]
484 except Exception, e:
487 except Exception, e:
485 apache.log_error("error getting data from ldap %s" % str(e), apache.APLOG_ERR)
488 apache.log_error("error getting data from ldap %s" % str(e), apache.APLOG_ERR)
486
489
487 return apache.OK
490 return apache.OK
488
491
489 .. note::
492 .. note::
490 If you enable proxy pass-through authentication, make sure your server is
493 If you enable proxy pass-through authentication, make sure your server is
491 only accessible through the proxy. Otherwise, any client would be able to
494 only accessible through the proxy. Otherwise, any client would be able to
492 forge the authentication header and could effectively become authenticated
495 forge the authentication header and could effectively become authenticated
493 using any account of their liking.
496 using any account of their liking.
494
497
495
498
496 Integration with issue trackers
499 Integration with issue trackers
497 -------------------------------
500 -------------------------------
498
501
499 Kallithea provides a simple integration with issue trackers. It's possible
502 Kallithea provides a simple integration with issue trackers. It's possible
500 to define a regular expression that will match an issue ID in commit messages,
503 to define a regular expression that will match an issue ID in commit messages,
501 and have that replaced with a URL to the issue. To enable this simply
504 and have that replaced with a URL to the issue. To enable this simply
502 uncomment the following variables in the ini file::
505 uncomment the following variables in the ini file::
503
506
504 issue_pat = (?:^#|\s#)(\w+)
507 issue_pat = (?:^#|\s#)(\w+)
505 issue_server_link = https://issues.example.com/{repo}/issue/{id}
508 issue_server_link = https://issues.example.com/{repo}/issue/{id}
506 issue_prefix = #
509 issue_prefix = #
507
510
508 ``issue_pat`` is the regular expression describing which strings in
511 ``issue_pat`` is the regular expression describing which strings in
509 commit messages will be treated as issue references. A match group in
512 commit messages will be treated as issue references. A match group in
510 parentheses should be used to specify the actual issue id.
513 parentheses should be used to specify the actual issue id.
511
514
512 The default expression matches issues in the format ``#<number>``, e.g., ``#300``.
515 The default expression matches issues in the format ``#<number>``, e.g., ``#300``.
513
516
514 Matched issue references are replaced with the link specified in
517 Matched issue references are replaced with the link specified in
515 ``issue_server_link``. ``{id}`` is replaced with the issue ID, and
518 ``issue_server_link``. ``{id}`` is replaced with the issue ID, and
516 ``{repo}`` with the repository name. Since the # is stripped away,
519 ``{repo}`` with the repository name. Since the # is stripped away,
517 ``issue_prefix`` is prepended to the link text. ``issue_prefix`` doesn't
520 ``issue_prefix`` is prepended to the link text. ``issue_prefix`` doesn't
518 necessarily need to be ``#``: if you set issue prefix to ``ISSUE-`` this will
521 necessarily need to be ``#``: if you set issue prefix to ``ISSUE-`` this will
519 generate a URL in the format:
522 generate a URL in the format:
520
523
521 .. code-block:: html
524 .. code-block:: html
522
525
523 <a href="https://issues.example.com/example_repo/issue/300">ISSUE-300</a>
526 <a href="https://issues.example.com/example_repo/issue/300">ISSUE-300</a>
524
527
525 If needed, more than one pattern can be specified by appending a unique suffix to
528 If needed, more than one pattern can be specified by appending a unique suffix to
526 the variables. For example::
529 the variables. For example::
527
530
528 issue_pat_wiki = (?:wiki-)(.+)
531 issue_pat_wiki = (?:wiki-)(.+)
529 issue_server_link_wiki = https://wiki.example.com/{id}
532 issue_server_link_wiki = https://wiki.example.com/{id}
530 issue_prefix_wiki = WIKI-
533 issue_prefix_wiki = WIKI-
531
534
532 With these settings, wiki pages can be referenced as wiki-some-id, and every
535 With these settings, wiki pages can be referenced as wiki-some-id, and every
533 such reference will be transformed into:
536 such reference will be transformed into:
534
537
535 .. code-block:: html
538 .. code-block:: html
536
539
537 <a href="https://wiki.example.com/some-id">WIKI-some-id</a>
540 <a href="https://wiki.example.com/some-id">WIKI-some-id</a>
538
541
539
542
540 Hook management
543 Hook management
541 ---------------
544 ---------------
542
545
543 Hooks can be managed in similar way to that used in ``.hgrc`` files.
546 Hooks can be managed in similar way to that used in ``.hgrc`` files.
544 To manage hooks, choose *Admin > Settings > Hooks*.
547 To manage hooks, choose *Admin > Settings > Hooks*.
545
548
546 The built-in hooks cannot be modified, though they can be enabled or disabled in the *VCS* section.
549 The built-in hooks cannot be modified, though they can be enabled or disabled in the *VCS* section.
547
550
548 To add another custom hook simply fill in the first textbox with
551 To add another custom hook simply fill in the first textbox with
549 ``<name>.<hook_type>`` and the second with the hook path. Example hooks
552 ``<name>.<hook_type>`` and the second with the hook path. Example hooks
550 can be found in ``kallithea.lib.hooks``.
553 can be found in ``kallithea.lib.hooks``.
551
554
552
555
553 Changing default encoding
556 Changing default encoding
554 -------------------------
557 -------------------------
555
558
556 By default, Kallithea uses UTF-8 encoding.
559 By default, Kallithea uses UTF-8 encoding.
557 This is configurable as ``default_encoding`` in the .ini file.
560 This is configurable as ``default_encoding`` in the .ini file.
558 This affects many parts in Kallithea including user names, filenames, and
561 This affects many parts in Kallithea including user names, filenames, and
559 encoding of commit messages. In addition Kallithea can detect if the ``chardet``
562 encoding of commit messages. In addition Kallithea can detect if the ``chardet``
560 library is installed. If ``chardet`` is detected Kallithea will fallback to it
563 library is installed. If ``chardet`` is detected Kallithea will fallback to it
561 when there are encode/decode errors.
564 when there are encode/decode errors.
562
565
563
566
564 Celery configuration
567 Celery configuration
565 --------------------
568 --------------------
566
569
567 Kallithea can use the distributed task queue system Celery_ to run tasks like
570 Kallithea can use the distributed task queue system Celery_ to run tasks like
568 cloning repositories or sending emails.
571 cloning repositories or sending emails.
569
572
570 Kallithea will in most setups work perfectly fine out of the box (without
573 Kallithea will in most setups work perfectly fine out of the box (without
571 Celery), executing all tasks in the web server process. Some tasks can however
574 Celery), executing all tasks in the web server process. Some tasks can however
572 take some time to run and it can be better to run such tasks asynchronously in
575 take some time to run and it can be better to run such tasks asynchronously in
573 a separate process so the web server can focus on serving web requests.
576 a separate process so the web server can focus on serving web requests.
574
577
575 For installation and configuration of Celery, see the `Celery documentation`_.
578 For installation and configuration of Celery, see the `Celery documentation`_.
576 Note that Celery requires a message broker service like RabbitMQ_ (recommended)
579 Note that Celery requires a message broker service like RabbitMQ_ (recommended)
577 or Redis_.
580 or Redis_.
578
581
579 The use of Celery is configured in the Kallithea ini configuration file.
582 The use of Celery is configured in the Kallithea ini configuration file.
580 To enable it, simply set::
583 To enable it, simply set::
581
584
582 use_celery = true
585 use_celery = true
583
586
584 and add or change the ``celery.*`` and ``broker.*`` configuration variables.
587 and add or change the ``celery.*`` and ``broker.*`` configuration variables.
585
588
586 Remember that the ini files use the format with '.' and not with '_' like
589 Remember that the ini files use the format with '.' and not with '_' like
587 Celery. So for example setting `BROKER_HOST` in Celery means setting
590 Celery. So for example setting `BROKER_HOST` in Celery means setting
588 `broker.host` in the configuration file.
591 `broker.host` in the configuration file.
589
592
590 To start the Celery process, run::
593 To start the Celery process, run::
591
594
592 paster celeryd <configfile.ini>
595 paster celeryd <configfile.ini>
593
596
594 .. note::
597 .. note::
595 Make sure you run this command from the same virtualenv, and with the same
598 Make sure you run this command from the same virtualenv, and with the same
596 user that Kallithea runs.
599 user that Kallithea runs.
597
600
598
601
599 HTTPS support
602 HTTPS support
600 -------------
603 -------------
601
604
602 Kallithea will by default generate URLs based on the WSGI environment.
605 Kallithea will by default generate URLs based on the WSGI environment.
603
606
604 Alternatively, you can use some special configuration settings to control
607 Alternatively, you can use some special configuration settings to control
605 directly which scheme/protocol Kallithea will use when generating URLs:
608 directly which scheme/protocol Kallithea will use when generating URLs:
606
609
607 - With ``https_fixup = true``, the scheme will be taken from the
610 - With ``https_fixup = true``, the scheme will be taken from the
608 ``X-Url-Scheme``, ``X-Forwarded-Scheme`` or ``X-Forwarded-Proto`` HTTP header
611 ``X-Url-Scheme``, ``X-Forwarded-Scheme`` or ``X-Forwarded-Proto`` HTTP header
609 (default ``http``).
612 (default ``http``).
610 - With ``force_https = true`` the default will be ``https``.
613 - With ``force_https = true`` the default will be ``https``.
611 - With ``use_htsts = true``, Kallithea will set ``Strict-Transport-Security`` when using https.
614 - With ``use_htsts = true``, Kallithea will set ``Strict-Transport-Security`` when using https.
612
615
613
616
614 Nginx virtual host example
617 Nginx virtual host example
615 --------------------------
618 --------------------------
616
619
617 Sample config for Nginx using proxy:
620 Sample config for Nginx using proxy:
618
621
619 .. code-block:: nginx
622 .. code-block:: nginx
620
623
621 upstream kallithea {
624 upstream kallithea {
622 server 127.0.0.1:5000;
625 server 127.0.0.1:5000;
623 # add more instances for load balancing
626 # add more instances for load balancing
624 #server 127.0.0.1:5001;
627 #server 127.0.0.1:5001;
625 #server 127.0.0.1:5002;
628 #server 127.0.0.1:5002;
626 }
629 }
627
630
628 ## gist alias
631 ## gist alias
629 server {
632 server {
630 listen 443;
633 listen 443;
631 server_name gist.example.com;
634 server_name gist.example.com;
632 access_log /var/log/nginx/gist.access.log;
635 access_log /var/log/nginx/gist.access.log;
633 error_log /var/log/nginx/gist.error.log;
636 error_log /var/log/nginx/gist.error.log;
634
637
635 ssl on;
638 ssl on;
636 ssl_certificate gist.your.kallithea.server.crt;
639 ssl_certificate gist.your.kallithea.server.crt;
637 ssl_certificate_key gist.your.kallithea.server.key;
640 ssl_certificate_key gist.your.kallithea.server.key;
638
641
639 ssl_session_timeout 5m;
642 ssl_session_timeout 5m;
640
643
641 ssl_protocols SSLv3 TLSv1;
644 ssl_protocols SSLv3 TLSv1;
642 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
645 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
643 ssl_prefer_server_ciphers on;
646 ssl_prefer_server_ciphers on;
644
647
645 rewrite ^/(.+)$ https://kallithea.example.com/_admin/gists/$1;
648 rewrite ^/(.+)$ https://kallithea.example.com/_admin/gists/$1;
646 rewrite (.*) https://kallithea.example.com/_admin/gists;
649 rewrite (.*) https://kallithea.example.com/_admin/gists;
647 }
650 }
648
651
649 server {
652 server {
650 listen 443;
653 listen 443;
651 server_name kallithea.example.com
654 server_name kallithea.example.com
652 access_log /var/log/nginx/kallithea.access.log;
655 access_log /var/log/nginx/kallithea.access.log;
653 error_log /var/log/nginx/kallithea.error.log;
656 error_log /var/log/nginx/kallithea.error.log;
654
657
655 ssl on;
658 ssl on;
656 ssl_certificate your.kallithea.server.crt;
659 ssl_certificate your.kallithea.server.crt;
657 ssl_certificate_key your.kallithea.server.key;
660 ssl_certificate_key your.kallithea.server.key;
658
661
659 ssl_session_timeout 5m;
662 ssl_session_timeout 5m;
660
663
661 ssl_protocols SSLv3 TLSv1;
664 ssl_protocols SSLv3 TLSv1;
662 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
665 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
663 ssl_prefer_server_ciphers on;
666 ssl_prefer_server_ciphers on;
664
667
665 ## uncomment root directive if you want to serve static files by nginx
668 ## uncomment root directive if you want to serve static files by nginx
666 ## requires static_files = false in .ini file
669 ## requires static_files = false in .ini file
667 #root /srv/kallithea/kallithea/kallithea/public;
670 #root /srv/kallithea/kallithea/kallithea/public;
668 include /etc/nginx/proxy.conf;
671 include /etc/nginx/proxy.conf;
669 location / {
672 location / {
670 try_files $uri @kallithea;
673 try_files $uri @kallithea;
671 }
674 }
672
675
673 location @kallithea {
676 location @kallithea {
674 proxy_pass http://127.0.0.1:5000;
677 proxy_pass http://127.0.0.1:5000;
675 }
678 }
676
679
677 }
680 }
678
681
679 Here's the proxy.conf. It's tuned so it will not timeout on long
682 Here's the proxy.conf. It's tuned so it will not timeout on long
680 pushes or large pushes::
683 pushes or large pushes::
681
684
682 proxy_redirect off;
685 proxy_redirect off;
683 proxy_set_header Host $host;
686 proxy_set_header Host $host;
684 ## needed for container auth
687 ## needed for container auth
685 #proxy_set_header REMOTE_USER $remote_user;
688 #proxy_set_header REMOTE_USER $remote_user;
686 #proxy_set_header X-Forwarded-User $remote_user;
689 #proxy_set_header X-Forwarded-User $remote_user;
687 proxy_set_header X-Url-Scheme $scheme;
690 proxy_set_header X-Url-Scheme $scheme;
688 proxy_set_header X-Host $http_host;
691 proxy_set_header X-Host $http_host;
689 proxy_set_header X-Real-IP $remote_addr;
692 proxy_set_header X-Real-IP $remote_addr;
690 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
693 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
691 proxy_set_header Proxy-host $proxy_host;
694 proxy_set_header Proxy-host $proxy_host;
692 proxy_buffering off;
695 proxy_buffering off;
693 proxy_connect_timeout 7200;
696 proxy_connect_timeout 7200;
694 proxy_send_timeout 7200;
697 proxy_send_timeout 7200;
695 proxy_read_timeout 7200;
698 proxy_read_timeout 7200;
696 proxy_buffers 8 32k;
699 proxy_buffers 8 32k;
697 client_max_body_size 1024m;
700 client_max_body_size 1024m;
698 client_body_buffer_size 128k;
701 client_body_buffer_size 128k;
699 large_client_header_buffers 8 64k;
702 large_client_header_buffers 8 64k;
700
703
701
704
702 Apache virtual host reverse proxy example
705 Apache virtual host reverse proxy example
703 -----------------------------------------
706 -----------------------------------------
704
707
705 Here is a sample configuration file for Apache using proxy:
708 Here is a sample configuration file for Apache using proxy:
706
709
707 .. code-block:: apache
710 .. code-block:: apache
708
711
709 <VirtualHost *:80>
712 <VirtualHost *:80>
710 ServerName kallithea.example.com
713 ServerName kallithea.example.com
711
714
712 <Proxy *>
715 <Proxy *>
713 # For Apache 2.4 and later:
716 # For Apache 2.4 and later:
714 Require all granted
717 Require all granted
715
718
716 # For Apache 2.2 and earlier, instead use:
719 # For Apache 2.2 and earlier, instead use:
717 # Order allow,deny
720 # Order allow,deny
718 # Allow from all
721 # Allow from all
719 </Proxy>
722 </Proxy>
720
723
721 #important !
724 #important !
722 #Directive to properly generate url (clone url) for pylons
725 #Directive to properly generate url (clone url) for pylons
723 ProxyPreserveHost On
726 ProxyPreserveHost On
724
727
725 #kallithea instance
728 #kallithea instance
726 ProxyPass / http://127.0.0.1:5000/
729 ProxyPass / http://127.0.0.1:5000/
727 ProxyPassReverse / http://127.0.0.1:5000/
730 ProxyPassReverse / http://127.0.0.1:5000/
728
731
729 #to enable https use line below
732 #to enable https use line below
730 #SetEnvIf X-Url-Scheme https HTTPS=1
733 #SetEnvIf X-Url-Scheme https HTTPS=1
731 </VirtualHost>
734 </VirtualHost>
732
735
733 Additional tutorial
736 Additional tutorial
734 http://pylonsbook.com/en/1.1/deployment.html#using-apache-to-proxy-requests-to-pylons
737 http://pylonsbook.com/en/1.1/deployment.html#using-apache-to-proxy-requests-to-pylons
735
738
736
739
737 Apache as subdirectory
740 Apache as subdirectory
738 ----------------------
741 ----------------------
739
742
740 Apache subdirectory part:
743 Apache subdirectory part:
741
744
742 .. code-block:: apache
745 .. code-block:: apache
743
746
744 <Location /<someprefix> >
747 <Location /<someprefix> >
745 ProxyPass http://127.0.0.1:5000/<someprefix>
748 ProxyPass http://127.0.0.1:5000/<someprefix>
746 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
749 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
747 SetEnvIf X-Url-Scheme https HTTPS=1
750 SetEnvIf X-Url-Scheme https HTTPS=1
748 </Location>
751 </Location>
749
752
750 Besides the regular apache setup you will need to add the following line
753 Besides the regular apache setup you will need to add the following line
751 into ``[app:main]`` section of your .ini file::
754 into ``[app:main]`` section of your .ini file::
752
755
753 filter-with = proxy-prefix
756 filter-with = proxy-prefix
754
757
755 Add the following at the end of the .ini file::
758 Add the following at the end of the .ini file::
756
759
757 [filter:proxy-prefix]
760 [filter:proxy-prefix]
758 use = egg:PasteDeploy#prefix
761 use = egg:PasteDeploy#prefix
759 prefix = /<someprefix>
762 prefix = /<someprefix>
760
763
761 then change ``<someprefix>`` into your chosen prefix
764 then change ``<someprefix>`` into your chosen prefix
762
765
763
766
764 Apache with mod_wsgi
767 Apache with mod_wsgi
765 --------------------
768 --------------------
766
769
767 Alternatively, Kallithea can be set up with Apache under mod_wsgi. For
770 Alternatively, Kallithea can be set up with Apache under mod_wsgi. For
768 that, you'll need to:
771 that, you'll need to:
769
772
770 - Install mod_wsgi. If using a Debian-based distro, you can install
773 - Install mod_wsgi. If using a Debian-based distro, you can install
771 the package libapache2-mod-wsgi::
774 the package libapache2-mod-wsgi::
772
775
773 aptitude install libapache2-mod-wsgi
776 aptitude install libapache2-mod-wsgi
774
777
775 - Enable mod_wsgi::
778 - Enable mod_wsgi::
776
779
777 a2enmod wsgi
780 a2enmod wsgi
778
781
779 - Add global Apache configuration to tell mod_wsgi that Python only will be
782 - Add global Apache configuration to tell mod_wsgi that Python only will be
780 used in the WSGI processes and shouldn't be initialized in the Apache
783 used in the WSGI processes and shouldn't be initialized in the Apache
781 processes::
784 processes::
782
785
783 WSGIRestrictEmbedded On
786 WSGIRestrictEmbedded On
784
787
785 - Create a wsgi dispatch script, like the one below. Make sure you
788 - Create a wsgi dispatch script, like the one below. Make sure you
786 check that the paths correctly point to where you installed Kallithea
789 check that the paths correctly point to where you installed Kallithea
787 and its Python Virtual Environment.
790 and its Python Virtual Environment.
788 - Enable the ``WSGIScriptAlias`` directive for the WSGI dispatch script,
791 - Enable the ``WSGIScriptAlias`` directive for the WSGI dispatch script,
789 as in the following example. Once again, check the paths are
792 as in the following example. Once again, check the paths are
790 correctly specified.
793 correctly specified.
791
794
792 Here is a sample excerpt from an Apache Virtual Host configuration file:
795 Here is a sample excerpt from an Apache Virtual Host configuration file:
793
796
794 .. code-block:: apache
797 .. code-block:: apache
795
798
796 WSGIDaemonProcess kallithea processes=5 threads=1 maximum-requests=100 \
799 WSGIDaemonProcess kallithea processes=5 threads=1 maximum-requests=100 \
797 python-home=/srv/kallithea/venv
800 python-home=/srv/kallithea/venv
798 WSGIProcessGroup kallithea
801 WSGIProcessGroup kallithea
799 WSGIScriptAlias / /srv/kallithea/dispatch.wsgi
802 WSGIScriptAlias / /srv/kallithea/dispatch.wsgi
800 WSGIPassAuthorization On
803 WSGIPassAuthorization On
801
804
802 Or if using a dispatcher WSGI script with proper virtualenv activation:
805 Or if using a dispatcher WSGI script with proper virtualenv activation:
803
806
804 .. code-block:: apache
807 .. code-block:: apache
805
808
806 WSGIDaemonProcess kallithea processes=5 threads=1 maximum-requests=100
809 WSGIDaemonProcess kallithea processes=5 threads=1 maximum-requests=100
807 WSGIProcessGroup kallithea
810 WSGIProcessGroup kallithea
808 WSGIScriptAlias / /srv/kallithea/dispatch.wsgi
811 WSGIScriptAlias / /srv/kallithea/dispatch.wsgi
809 WSGIPassAuthorization On
812 WSGIPassAuthorization On
810
813
811 Apache will by default run as a special Apache user, on Linux systems
814 Apache will by default run as a special Apache user, on Linux systems
812 usually ``www-data`` or ``apache``. If you need to have the repositories
815 usually ``www-data`` or ``apache``. If you need to have the repositories
813 directory owned by a different user, use the user and group options to
816 directory owned by a different user, use the user and group options to
814 WSGIDaemonProcess to set the name of the user and group.
817 WSGIDaemonProcess to set the name of the user and group.
815
818
816 Example WSGI dispatch script:
819 Example WSGI dispatch script:
817
820
818 .. code-block:: python
821 .. code-block:: python
819
822
820 import os
823 import os
821 os.environ["HGENCODING"] = "UTF-8"
824 os.environ["HGENCODING"] = "UTF-8"
822 os.environ['PYTHON_EGG_CACHE'] = '/srv/kallithea/.egg-cache'
825 os.environ['PYTHON_EGG_CACHE'] = '/srv/kallithea/.egg-cache'
823
826
824 # sometimes it's needed to set the current dir
827 # sometimes it's needed to set the current dir
825 os.chdir('/srv/kallithea/')
828 os.chdir('/srv/kallithea/')
826
829
827 import site
830 import site
828 site.addsitedir("/srv/kallithea/venv/lib/python2.7/site-packages")
831 site.addsitedir("/srv/kallithea/venv/lib/python2.7/site-packages")
829
832
830 ini = '/srv/kallithea/my.ini'
833 ini = '/srv/kallithea/my.ini'
831 from paste.script.util.logging_config import fileConfig
834 from paste.script.util.logging_config import fileConfig
832 fileConfig(ini)
835 fileConfig(ini)
833 from paste.deploy import loadapp
836 from paste.deploy import loadapp
834 application = loadapp('config:' + ini)
837 application = loadapp('config:' + ini)
835
838
836 Or using proper virtualenv activation:
839 Or using proper virtualenv activation:
837
840
838 .. code-block:: python
841 .. code-block:: python
839
842
840 activate_this = '/srv/kallithea/venv/bin/activate_this.py'
843 activate_this = '/srv/kallithea/venv/bin/activate_this.py'
841 execfile(activate_this, dict(__file__=activate_this))
844 execfile(activate_this, dict(__file__=activate_this))
842
845
843 import os
846 import os
844 os.environ['HOME'] = '/srv/kallithea'
847 os.environ['HOME'] = '/srv/kallithea'
845
848
846 ini = '/srv/kallithea/kallithea.ini'
849 ini = '/srv/kallithea/kallithea.ini'
847 from paste.script.util.logging_config import fileConfig
850 from paste.script.util.logging_config import fileConfig
848 fileConfig(ini)
851 fileConfig(ini)
849 from paste.deploy import loadapp
852 from paste.deploy import loadapp
850 application = loadapp('config:' + ini)
853 application = loadapp('config:' + ini)
851
854
852
855
853 Other configuration files
856 Other configuration files
854 -------------------------
857 -------------------------
855
858
856 A number of `example init.d scripts`__ can be found in
859 A number of `example init.d scripts`__ can be found in
857 the ``init.d`` directory of the Kallithea source.
860 the ``init.d`` directory of the Kallithea source.
858
861
859 .. __: https://kallithea-scm.org/repos/kallithea/files/tip/init.d/ .
862 .. __: https://kallithea-scm.org/repos/kallithea/files/tip/init.d/ .
860
863
861
864
862 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
865 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
863 .. _python: http://www.python.org/
866 .. _python: http://www.python.org/
864 .. _Mercurial: http://mercurial.selenic.com/
867 .. _Mercurial: http://mercurial.selenic.com/
865 .. _Celery: http://celeryproject.org/
868 .. _Celery: http://celeryproject.org/
866 .. _Celery documentation: http://docs.celeryproject.org/en/latest/getting-started/index.html
869 .. _Celery documentation: http://docs.celeryproject.org/en/latest/getting-started/index.html
867 .. _RabbitMQ: http://www.rabbitmq.com/
870 .. _RabbitMQ: http://www.rabbitmq.com/
868 .. _Redis: http://redis.io/
871 .. _Redis: http://redis.io/
869 .. _python-ldap: http://www.python-ldap.org/
872 .. _python-ldap: http://www.python-ldap.org/
870 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
873 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
871 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
874 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
@@ -1,379 +1,368 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.auth_modules.auth_ldap
15 kallithea.lib.auth_modules.auth_ldap
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Kallithea authentication plugin for LDAP
18 Kallithea authentication plugin for LDAP
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Created on Nov 17, 2010
22 :created_on: Created on Nov 17, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from kallithea.lib import auth_modules
32 from kallithea.lib import auth_modules
33 from kallithea.lib.compat import hybrid_property
33 from kallithea.lib.compat import hybrid_property
34 from kallithea.lib.utils2 import safe_unicode, safe_str
34 from kallithea.lib.utils2 import safe_unicode, safe_str
35 from kallithea.lib.exceptions import (
35 from kallithea.lib.exceptions import (
36 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
36 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
37 )
37 )
38 from kallithea.model.db import User
38 from kallithea.model.db import User
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42 try:
42 try:
43 import ldap
43 import ldap
44 except ImportError:
44 except ImportError:
45 # means that python-ldap is not installed
45 # means that python-ldap is not installed
46 ldap = None
46 ldap = None
47
47
48
48
49 class AuthLdap(object):
49 class AuthLdap(object):
50
50
51 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
51 def __init__(self, server, base_dn, port=None, bind_dn='', bind_pass='',
52 tls_kind='PLAIN', tls_reqcert='DEMAND', cacertdir=None, ldap_version=3,
52 tls_kind='PLAIN', tls_reqcert='DEMAND', cacertdir=None, ldap_version=3,
53 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
53 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
54 search_scope='SUBTREE', attr_login='uid'):
54 search_scope='SUBTREE', attr_login='uid'):
55 if ldap is None:
55 if ldap is None:
56 raise LdapImportError
56 raise LdapImportError
57
57
58 self.ldap_version = ldap_version
58 self.ldap_version = ldap_version
59 ldap_server_type = 'ldap'
60
59
61 self.TLS_KIND = tls_kind
60 self.TLS_KIND = tls_kind
62
63 if self.TLS_KIND == 'LDAPS':
64 port = port or 689
65 ldap_server_type = ldap_server_type + 's'
66
67 OPT_X_TLS_DEMAND = 2
61 OPT_X_TLS_DEMAND = 2
68 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
62 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
69 OPT_X_TLS_DEMAND)
63 OPT_X_TLS_DEMAND)
70 self.cacertdir = cacertdir
64 self.cacertdir = cacertdir
71
65
72 # split server into list
66 protocol = 'ldaps' if self.TLS_KIND == 'LDAPS' else 'ldap'
73 self.LDAP_SERVER_ADDRESS = server.split(',')
67 if not port:
74 self.LDAP_SERVER_PORT = port
68 port = 636 if self.TLS_KIND == 'LDAPS' else 389
69 self.LDAP_SERVER = str(', '.join(
70 "%s://%s:%s" % (protocol,
71 host.strip(),
72 port)
73 for host in server.split(',')))
75
74
76 # USE FOR READ ONLY BIND TO LDAP SERVER
77 self.LDAP_BIND_DN = safe_str(bind_dn)
75 self.LDAP_BIND_DN = safe_str(bind_dn)
78 self.LDAP_BIND_PASS = safe_str(bind_pass)
76 self.LDAP_BIND_PASS = safe_str(bind_pass)
79 _LDAP_SERVERS = []
77
80 for host in self.LDAP_SERVER_ADDRESS:
81 _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type,
82 host.replace(' ', ''),
83 self.LDAP_SERVER_PORT))
84 self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS))
85 self.BASE_DN = safe_str(base_dn)
78 self.BASE_DN = safe_str(base_dn)
86 self.LDAP_FILTER = safe_str(ldap_filter)
79 self.LDAP_FILTER = safe_str(ldap_filter)
87 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
80 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
88 self.attr_login = attr_login
81 self.attr_login = attr_login
89
82
90 def authenticate_ldap(self, username, password):
83 def authenticate_ldap(self, username, password):
91 """
84 """
92 Authenticate a user via LDAP and return his/her LDAP properties.
85 Authenticate a user via LDAP and return his/her LDAP properties.
93
86
94 Raises AuthenticationError if the credentials are rejected, or
87 Raises AuthenticationError if the credentials are rejected, or
95 EnvironmentError if the LDAP server can't be reached.
88 EnvironmentError if the LDAP server can't be reached.
96
89
97 :param username: username
90 :param username: username
98 :param password: password
91 :param password: password
99 """
92 """
100
93
101 from kallithea.lib.helpers import chop_at
102
103 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
104
105 if not password:
94 if not password:
106 log.debug("Attempt to authenticate LDAP user "
95 log.debug("Attempt to authenticate LDAP user "
107 "with blank password rejected.")
96 "with blank password rejected.")
108 raise LdapPasswordError()
97 raise LdapPasswordError()
109 if "," in username:
98 if "," in username:
110 raise LdapUsernameError("invalid character in username: ,")
99 raise LdapUsernameError("invalid character in username: ,")
111 try:
100 try:
112 if self.cacertdir:
101 if self.cacertdir:
113 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
102 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
114 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.cacertdir)
103 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.cacertdir)
115 else:
104 else:
116 log.debug("OPT_X_TLS_CACERTDIR is not available - can't set %s", self.cacertdir)
105 log.debug("OPT_X_TLS_CACERTDIR is not available - can't set %s", self.cacertdir)
117 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
106 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
118 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
107 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
119 ldap.set_option(ldap.OPT_TIMEOUT, 20)
108 ldap.set_option(ldap.OPT_TIMEOUT, 20)
120 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
109 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
121 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
110 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
122 if self.TLS_KIND != 'PLAIN':
111 if self.TLS_KIND != 'PLAIN':
123 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
112 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
124 server = ldap.initialize(self.LDAP_SERVER)
113 server = ldap.initialize(self.LDAP_SERVER)
125 if self.ldap_version == 2:
114 if self.ldap_version == 2:
126 server.protocol = ldap.VERSION2
115 server.protocol = ldap.VERSION2
127 else:
116 else:
128 server.protocol = ldap.VERSION3
117 server.protocol = ldap.VERSION3
129
118
130 if self.TLS_KIND == 'START_TLS':
119 if self.TLS_KIND == 'START_TLS':
131 server.start_tls_s()
120 server.start_tls_s()
132
121
133 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
122 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
134 log.debug('Trying simple_bind with password and given DN: %s',
123 log.debug('Trying simple_bind with password and given DN: %s',
135 self.LDAP_BIND_DN)
124 self.LDAP_BIND_DN)
136 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
125 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
137
126
138 filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
127 filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
139 username)
128 username)
140 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
129 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
141 filter_, self.LDAP_SERVER)
130 filter_, self.LDAP_SERVER)
142 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
131 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
143 filter_)
132 filter_)
144
133
145 if not lobjects:
134 if not lobjects:
146 raise ldap.NO_SUCH_OBJECT()
135 raise ldap.NO_SUCH_OBJECT()
147
136
148 for (dn, _attrs) in lobjects:
137 for (dn, _attrs) in lobjects:
149 if dn is None:
138 if dn is None:
150 continue
139 continue
151
140
152 try:
141 try:
153 log.debug('Trying simple bind with %s', dn)
142 log.debug('Trying simple bind with %s', dn)
154 server.simple_bind_s(dn, safe_str(password))
143 server.simple_bind_s(dn, safe_str(password))
155 results = server.search_ext_s(dn, ldap.SCOPE_BASE,
144 results = server.search_ext_s(dn, ldap.SCOPE_BASE,
156 '(objectClass=*)')
145 '(objectClass=*)')
157 if len(results) == 1:
146 if len(results) == 1:
158 dn_, attrs = results[0]
147 dn_, attrs = results[0]
159 assert dn_ == dn
148 assert dn_ == dn
160 return dn, attrs
149 return dn, attrs
161
150
162 except ldap.INVALID_CREDENTIALS:
151 except ldap.INVALID_CREDENTIALS:
163 log.debug("LDAP rejected password for user '%s' (%s): %s",
152 log.debug("LDAP rejected password for user '%s': %s",
164 uid, username, dn)
153 username, dn)
165 continue # accept authentication as another ldap user with same username
154 continue # accept authentication as another ldap user with same username
166
155
167 log.debug("No matching LDAP objects for authentication "
156 log.debug("No matching LDAP objects for authentication "
168 "of '%s' (%s)", uid, username)
157 "of '%s'", username)
169 raise LdapPasswordError()
158 raise LdapPasswordError()
170
159
171 except ldap.NO_SUCH_OBJECT:
160 except ldap.NO_SUCH_OBJECT:
172 log.debug("LDAP says no such user '%s' (%s)", uid, username)
161 log.debug("LDAP says no such user '%s'", username)
173 raise LdapUsernameError()
162 raise LdapUsernameError()
174 except ldap.SERVER_DOWN:
163 except ldap.SERVER_DOWN:
175 # [0] might be {'info': "TLS error -8179:Peer's Certificate issuer is not recognized.", 'desc': "Can't contact LDAP server"}
164 # [0] might be {'info': "TLS error -8179:Peer's Certificate issuer is not recognized.", 'desc': "Can't contact LDAP server"}
176 raise LdapConnectionError("LDAP can't connect to authentication server")
165 raise LdapConnectionError("LDAP can't connect to authentication server")
177
166
178
167
179 class KallitheaAuthPlugin(auth_modules.KallitheaExternalAuthPlugin):
168 class KallitheaAuthPlugin(auth_modules.KallitheaExternalAuthPlugin):
180 def __init__(self):
169 def __init__(self):
181 self._logger = logging.getLogger(__name__)
170 self._logger = logging.getLogger(__name__)
182 self._tls_kind_values = ["PLAIN", "LDAPS", "START_TLS"]
171 self._tls_kind_values = ["PLAIN", "LDAPS", "START_TLS"]
183 self._tls_reqcert_values = ["NEVER", "ALLOW", "TRY", "DEMAND", "HARD"]
172 self._tls_reqcert_values = ["NEVER", "ALLOW", "TRY", "DEMAND", "HARD"]
184 self._search_scopes = ["BASE", "ONELEVEL", "SUBTREE"]
173 self._search_scopes = ["BASE", "ONELEVEL", "SUBTREE"]
185
174
186 @hybrid_property
175 @hybrid_property
187 def name(self):
176 def name(self):
188 return "ldap"
177 return "ldap"
189
178
190 def settings(self):
179 def settings(self):
191 settings = [
180 settings = [
192 {
181 {
193 "name": "host",
182 "name": "host",
194 "validator": self.validators.UnicodeString(strip=True),
183 "validator": self.validators.UnicodeString(strip=True),
195 "type": "string",
184 "type": "string",
196 "description": "Host of the LDAP Server",
185 "description": "Host of the LDAP Server",
197 "formname": "LDAP Host"
186 "formname": "LDAP Host"
198 },
187 },
199 {
188 {
200 "name": "port",
189 "name": "port",
201 "validator": self.validators.Number(strip=True, not_empty=True),
190 "validator": self.validators.Number(strip=True),
202 "type": "string",
191 "type": "string",
203 "description": "Port that the LDAP server is listening on",
192 "description": "Port that the LDAP server is listening on. Defaults to 389 for PLAIN/START_TLS and 636 for LDAPS.",
204 "default": 389,
193 "default": "",
205 "formname": "Port"
194 "formname": "Custom LDAP Port"
206 },
195 },
207 {
196 {
208 "name": "dn_user",
197 "name": "dn_user",
209 "validator": self.validators.UnicodeString(strip=True),
198 "validator": self.validators.UnicodeString(strip=True),
210 "type": "string",
199 "type": "string",
211 "description": "User to connect to LDAP",
200 "description": "User to connect to LDAP",
212 "formname": "Account"
201 "formname": "Account"
213 },
202 },
214 {
203 {
215 "name": "dn_pass",
204 "name": "dn_pass",
216 "validator": self.validators.UnicodeString(strip=True),
205 "validator": self.validators.UnicodeString(strip=True),
217 "type": "password",
206 "type": "password",
218 "description": "Password to connect to LDAP",
207 "description": "Password to connect to LDAP",
219 "formname": "Password"
208 "formname": "Password"
220 },
209 },
221 {
210 {
222 "name": "tls_kind",
211 "name": "tls_kind",
223 "validator": self.validators.OneOf(self._tls_kind_values),
212 "validator": self.validators.OneOf(self._tls_kind_values),
224 "type": "select",
213 "type": "select",
225 "values": self._tls_kind_values,
214 "values": self._tls_kind_values,
226 "description": "TLS Type",
215 "description": "TLS Type",
227 "default": 'PLAIN',
216 "default": 'PLAIN',
228 "formname": "Connection Security"
217 "formname": "Connection Security"
229 },
218 },
230 {
219 {
231 "name": "tls_reqcert",
220 "name": "tls_reqcert",
232 "validator": self.validators.OneOf(self._tls_reqcert_values),
221 "validator": self.validators.OneOf(self._tls_reqcert_values),
233 "type": "select",
222 "type": "select",
234 "values": self._tls_reqcert_values,
223 "values": self._tls_reqcert_values,
235 "description": "Require Cert over TLS?",
224 "description": "Require Cert over TLS?",
236 "formname": "Certificate Checks"
225 "formname": "Certificate Checks"
237 },
226 },
238 {
227 {
239 "name": "cacertdir",
228 "name": "cacertdir",
240 "validator": self.validators.UnicodeString(strip=True),
229 "validator": self.validators.UnicodeString(strip=True),
241 "type": "string",
230 "type": "string",
242 "description": "Optional: Custom CA certificate directory for validating LDAPS",
231 "description": "Optional: Custom CA certificate directory for validating LDAPS",
243 "formname": "Custom CA Certificates"
232 "formname": "Custom CA Certificates"
244 },
233 },
245 {
234 {
246 "name": "base_dn",
235 "name": "base_dn",
247 "validator": self.validators.UnicodeString(strip=True),
236 "validator": self.validators.UnicodeString(strip=True),
248 "type": "string",
237 "type": "string",
249 "description": "Base DN to search (e.g., dc=mydomain,dc=com)",
238 "description": "Base DN to search (e.g., dc=mydomain,dc=com)",
250 "formname": "Base DN"
239 "formname": "Base DN"
251 },
240 },
252 {
241 {
253 "name": "filter",
242 "name": "filter",
254 "validator": self.validators.UnicodeString(strip=True),
243 "validator": self.validators.UnicodeString(strip=True),
255 "type": "string",
244 "type": "string",
256 "description": "Filter to narrow results (e.g., ou=Users, etc)",
245 "description": "Filter to narrow results (e.g., ou=Users, etc)",
257 "formname": "LDAP Search Filter"
246 "formname": "LDAP Search Filter"
258 },
247 },
259 {
248 {
260 "name": "search_scope",
249 "name": "search_scope",
261 "validator": self.validators.OneOf(self._search_scopes),
250 "validator": self.validators.OneOf(self._search_scopes),
262 "type": "select",
251 "type": "select",
263 "values": self._search_scopes,
252 "values": self._search_scopes,
264 "description": "How deep to search LDAP",
253 "description": "How deep to search LDAP",
265 "formname": "LDAP Search Scope"
254 "formname": "LDAP Search Scope"
266 },
255 },
267 {
256 {
268 "name": "attr_login",
257 "name": "attr_login",
269 "validator": self.validators.AttrLoginValidator(not_empty=True, strip=True),
258 "validator": self.validators.AttrLoginValidator(not_empty=True, strip=True),
270 "type": "string",
259 "type": "string",
271 "description": "LDAP Attribute to map to user name",
260 "description": "LDAP Attribute to map to user name",
272 "formname": "Login Attribute"
261 "formname": "Login Attribute"
273 },
262 },
274 {
263 {
275 "name": "attr_firstname",
264 "name": "attr_firstname",
276 "validator": self.validators.UnicodeString(strip=True),
265 "validator": self.validators.UnicodeString(strip=True),
277 "type": "string",
266 "type": "string",
278 "description": "LDAP Attribute to map to first name",
267 "description": "LDAP Attribute to map to first name",
279 "formname": "First Name Attribute"
268 "formname": "First Name Attribute"
280 },
269 },
281 {
270 {
282 "name": "attr_lastname",
271 "name": "attr_lastname",
283 "validator": self.validators.UnicodeString(strip=True),
272 "validator": self.validators.UnicodeString(strip=True),
284 "type": "string",
273 "type": "string",
285 "description": "LDAP Attribute to map to last name",
274 "description": "LDAP Attribute to map to last name",
286 "formname": "Last Name Attribute"
275 "formname": "Last Name Attribute"
287 },
276 },
288 {
277 {
289 "name": "attr_email",
278 "name": "attr_email",
290 "validator": self.validators.UnicodeString(strip=True),
279 "validator": self.validators.UnicodeString(strip=True),
291 "type": "string",
280 "type": "string",
292 "description": "LDAP Attribute to map to email address",
281 "description": "LDAP Attribute to map to email address",
293 "formname": "Email Attribute"
282 "formname": "Email Attribute"
294 }
283 }
295 ]
284 ]
296 return settings
285 return settings
297
286
298 def use_fake_password(self):
287 def use_fake_password(self):
299 return True
288 return True
300
289
301 def user_activation_state(self):
290 def user_activation_state(self):
302 def_user_perms = User.get_default_user().AuthUser.permissions['global']
291 def_user_perms = User.get_default_user().AuthUser.permissions['global']
303 return 'hg.extern_activate.auto' in def_user_perms
292 return 'hg.extern_activate.auto' in def_user_perms
304
293
305 def auth(self, userobj, username, password, settings, **kwargs):
294 def auth(self, userobj, username, password, settings, **kwargs):
306 """
295 """
307 Given a user object (which may be null), username, a plaintext password,
296 Given a user object (which may be null), username, a plaintext password,
308 and a settings object (containing all the keys needed as listed in settings()),
297 and a settings object (containing all the keys needed as listed in settings()),
309 authenticate this user's login attempt.
298 authenticate this user's login attempt.
310
299
311 Return None on failure. On success, return a dictionary of the form:
300 Return None on failure. On success, return a dictionary of the form:
312
301
313 see: KallitheaAuthPluginBase.auth_func_attrs
302 see: KallitheaAuthPluginBase.auth_func_attrs
314 This is later validated for correctness
303 This is later validated for correctness
315 """
304 """
316
305
317 if not username or not password:
306 if not username or not password:
318 log.debug('Empty username or password skipping...')
307 log.debug('Empty username or password skipping...')
319 return None
308 return None
320
309
321 kwargs = {
310 kwargs = {
322 'server': settings.get('host', ''),
311 'server': settings.get('host', ''),
323 'base_dn': settings.get('base_dn', ''),
312 'base_dn': settings.get('base_dn', ''),
324 'port': settings.get('port'),
313 'port': settings.get('port'),
325 'bind_dn': settings.get('dn_user'),
314 'bind_dn': settings.get('dn_user'),
326 'bind_pass': settings.get('dn_pass'),
315 'bind_pass': settings.get('dn_pass'),
327 'tls_kind': settings.get('tls_kind'),
316 'tls_kind': settings.get('tls_kind'),
328 'tls_reqcert': settings.get('tls_reqcert'),
317 'tls_reqcert': settings.get('tls_reqcert'),
329 'cacertdir': settings.get('cacertdir'),
318 'cacertdir': settings.get('cacertdir'),
330 'ldap_filter': settings.get('filter'),
319 'ldap_filter': settings.get('filter'),
331 'search_scope': settings.get('search_scope'),
320 'search_scope': settings.get('search_scope'),
332 'attr_login': settings.get('attr_login'),
321 'attr_login': settings.get('attr_login'),
333 'ldap_version': 3,
322 'ldap_version': 3,
334 }
323 }
335
324
336 if kwargs['bind_dn'] and not kwargs['bind_pass']:
325 if kwargs['bind_dn'] and not kwargs['bind_pass']:
337 log.debug('Using dynamic binding.')
326 log.debug('Using dynamic binding.')
338 kwargs['bind_dn'] = kwargs['bind_dn'].replace('$login', username)
327 kwargs['bind_dn'] = kwargs['bind_dn'].replace('$login', username)
339 kwargs['bind_pass'] = password
328 kwargs['bind_pass'] = password
340 log.debug('Checking for ldap authentication')
329 log.debug('Checking for ldap authentication')
341
330
342 try:
331 try:
343 aldap = AuthLdap(**kwargs)
332 aldap = AuthLdap(**kwargs)
344 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
333 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
345 log.debug('Got ldap DN response %s', user_dn)
334 log.debug('Got ldap DN response %s', user_dn)
346
335
347 get_ldap_attr = lambda k: ldap_attrs.get(settings.get(k), [''])[0]
336 get_ldap_attr = lambda k: ldap_attrs.get(settings.get(k), [''])[0]
348
337
349 # old attrs fetched from Kallithea database
338 # old attrs fetched from Kallithea database
350 admin = getattr(userobj, 'admin', False)
339 admin = getattr(userobj, 'admin', False)
351 active = getattr(userobj, 'active', self.user_activation_state())
340 active = getattr(userobj, 'active', self.user_activation_state())
352 email = getattr(userobj, 'email', '')
341 email = getattr(userobj, 'email', '')
353 firstname = getattr(userobj, 'firstname', '')
342 firstname = getattr(userobj, 'firstname', '')
354 lastname = getattr(userobj, 'lastname', '')
343 lastname = getattr(userobj, 'lastname', '')
355
344
356 user_data = {
345 user_data = {
357 'username': username,
346 'username': username,
358 'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname),
347 'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname),
359 'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname),
348 'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname),
360 'groups': [],
349 'groups': [],
361 'email': get_ldap_attr('attr_email') or email,
350 'email': get_ldap_attr('attr_email') or email,
362 'admin': admin,
351 'admin': admin,
363 'active': active,
352 'active': active,
364 "active_from_extern": None,
353 "active_from_extern": None,
365 'extern_name': user_dn,
354 'extern_name': user_dn,
366 }
355 }
367 log.info('user %s authenticated correctly', user_data['username'])
356 log.info('user %s authenticated correctly', user_data['username'])
368 return user_data
357 return user_data
369
358
370 except LdapUsernameError:
359 except LdapUsernameError:
371 log.info('Error authenticating %s with LDAP: User not found', username)
360 log.info('Error authenticating %s with LDAP: User not found', username)
372 except LdapPasswordError:
361 except LdapPasswordError:
373 log.info('Error authenticating %s with LDAP: Password error', username)
362 log.info('Error authenticating %s with LDAP: Password error', username)
374 except LdapImportError:
363 except LdapImportError:
375 log.error('Error authenticating %s with LDAP: LDAP not available', username)
364 log.error('Error authenticating %s with LDAP: LDAP not available', username)
376 return None
365 return None
377
366
378 def get_managed_fields(self):
367 def get_managed_fields(self):
379 return ['username', 'firstname', 'lastname', 'email', 'password']
368 return ['username', 'firstname', 'lastname', 'email', 'password']
General Comments 0
You need to be logged in to leave comments. Login now