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