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