##// END OF EJS Templates
auth: let container authentication get email, first and last name from custom headers
domruf -
r5593:ada6571a default
parent child Browse files
Show More
@@ -1,804 +1,872 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 Extensions
71 71 ----------
72 72
73 73 Optionally one can create an ``rcextensions`` package that extends Kallithea
74 74 functionality.
75 75 To generate a skeleton extensions package, run::
76 76
77 77 paster make-rcext my.ini
78 78
79 79 This will create an ``rcextensions`` package next to the specified ``ini`` file.
80 80 With ``rcextensions`` it's possible to add additional mapping for whoosh,
81 81 stats and add additional code into the push/pull/create/delete repo hooks,
82 82 for example for sending signals to build-bots such as Jenkins.
83 83
84 84 See the ``__init__.py`` file inside the generated ``rcextensions`` package
85 85 for more details.
86 86
87 87
88 88 Using Kallithea with SSH
89 89 ------------------------
90 90
91 91 Kallithea currently only hosts repositories using http and https. (The addition
92 92 of ssh hosting is a planned future feature.) However you can easily use ssh in
93 93 parallel with Kallithea. (Repository access via ssh is a standard "out of
94 94 the box" feature of Mercurial_ and you can use this to access any of the
95 95 repositories that Kallithea is hosting. See PublishingRepositories_)
96 96
97 97 Kallithea repository structures are kept in directories with the same name
98 98 as the project. When using repository groups, each group is a subdirectory.
99 99 This allows you to easily use ssh for accessing repositories.
100 100
101 101 In order to use ssh you need to make sure that your web server and the users'
102 102 login accounts have the correct permissions set on the appropriate directories.
103 103
104 104 .. note:: These permissions are independent of any permissions you
105 105 have set up using the Kallithea web interface.
106 106
107 107 If your main directory (the same as set in Kallithea settings) is for
108 108 example set to ``/srv/repos`` and the repository you are using is
109 109 named ``kallithea``, then to clone via ssh you should run::
110 110
111 111 hg clone ssh://user@kallithea.example.com/srv/repos/kallithea
112 112
113 113 Using other external tools such as mercurial-server_ or using ssh key-based
114 114 authentication is fully supported.
115 115
116 116 .. note:: In an advanced setup, in order for your ssh access to use
117 117 the same permissions as set up via the Kallithea web
118 118 interface, you can create an authentication hook to connect
119 119 to the Kallithea db and run check functions for permissions
120 120 against that.
121 121
122 122
123 123 Setting up Whoosh full text search
124 124 ----------------------------------
125 125
126 126 Kallithea provides full text search of repositories using `Whoosh`__.
127 127
128 128 .. __: https://pythonhosted.org/Whoosh/
129 129
130 130 For an incremental index build, run::
131 131
132 132 paster make-index my.ini
133 133
134 134 For a full index rebuild, run::
135 135
136 136 paster make-index my.ini -f
137 137
138 138 The ``--repo-location`` option allows the location of the repositories to be overriden;
139 139 usually, the location is retrieved from the Kallithea database.
140 140
141 141 The ``--index-only`` option can be used to limit the indexed repositories to a comma-separated list::
142 142
143 143 paster make-index my.ini --index-only=vcs,kallithea
144 144
145 145 To keep your index up-to-date it is necessary to do periodic index builds;
146 146 for this, it is recommended to use a crontab entry. Example::
147 147
148 148 0 3 * * * /path/to/virtualenv/bin/paster make-index /path/to/kallithea/my.ini
149 149
150 150 When using incremental mode (the default), Whoosh will check the last
151 151 modification date of each file and add it to be reindexed if a newer file is
152 152 available. The indexing daemon checks for any removed files and removes them
153 153 from index.
154 154
155 155 If you want to rebuild the index from scratch, you can use the ``-f`` flag as above,
156 156 or in the admin panel you can check the "build from scratch" checkbox.
157 157
158 158
159 159 Setting up LDAP support
160 160 -----------------------
161 161
162 162 Kallithea supports LDAP authentication. In order
163 163 to use LDAP, you have to install the python-ldap_ package. This package is
164 164 available via PyPI, so you can install it by running::
165 165
166 166 pip install python-ldap
167 167
168 168 .. note:: ``python-ldap`` requires some libraries to be installed on
169 169 your system, so before installing it check that you have at
170 170 least the ``openldap`` and ``sasl`` libraries.
171 171
172 172 Choose *Admin > Authentication*, click the ``kallithea.lib.auth_modules.auth_ldap`` button
173 173 and then *Save*, to enable the LDAP plugin and configure its settings.
174 174
175 175 Here's a typical LDAP setup::
176 176
177 177 Connection settings
178 178 Enable LDAP = checked
179 179 Host = host.example.com
180 180 Port = 389
181 181 Account = <account>
182 182 Password = <password>
183 183 Connection Security = LDAPS connection
184 184 Certificate Checks = DEMAND
185 185
186 186 Search settings
187 187 Base DN = CN=users,DC=host,DC=example,DC=org
188 188 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
189 189 LDAP Search Scope = SUBTREE
190 190
191 191 Attribute mappings
192 192 Login Attribute = uid
193 193 First Name Attribute = firstName
194 194 Last Name Attribute = lastName
195 195 Email Attribute = mail
196 196
197 197 If your user groups are placed in an Organisation Unit (OU) structure, the Search Settings configuration differs::
198 198
199 199 Search settings
200 200 Base DN = DC=host,DC=example,DC=org
201 201 LDAP Filter = (&(memberOf=CN=your user group,OU=subunit,OU=unit,DC=host,DC=example,DC=org)(objectClass=user))
202 202 LDAP Search Scope = SUBTREE
203 203
204 204 .. _enable_ldap:
205 205
206 206 Enable LDAP : required
207 207 Whether to use LDAP for authenticating users.
208 208
209 209 .. _ldap_host:
210 210
211 211 Host : required
212 212 LDAP server hostname or IP address. Can be also a comma separated
213 213 list of servers to support LDAP fail-over.
214 214
215 215 .. _Port:
216 216
217 217 Port : required
218 218 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
219 219
220 220 .. _ldap_account:
221 221
222 222 Account : optional
223 223 Only required if the LDAP server does not allow anonymous browsing of
224 224 records. This should be a special account for record browsing. This
225 225 will require `LDAP Password`_ below.
226 226
227 227 .. _LDAP Password:
228 228
229 229 Password : optional
230 230 Only required if the LDAP server does not allow anonymous browsing of
231 231 records.
232 232
233 233 .. _Enable LDAPS:
234 234
235 235 Connection Security : required
236 236 Defines the connection to LDAP server
237 237
238 238 No encryption
239 239 Plain non encrypted connection
240 240
241 241 LDAPS connection
242 242 Enable LDAPS connections. It will likely require `Port`_ to be set to
243 243 a different value (standard LDAPS port is 636). When LDAPS is enabled
244 244 then `Certificate Checks`_ is required.
245 245
246 246 START_TLS on LDAP connection
247 247 START TLS connection
248 248
249 249 .. _Certificate Checks:
250 250
251 251 Certificate Checks : optional
252 252 How SSL certificates verification is handled -- this is only useful when
253 253 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
254 254 while the other options are susceptible to man-in-the-middle attacks. SSL
255 255 certificates can be installed to /etc/openldap/cacerts so that the
256 256 DEMAND or HARD options can be used with self-signed certificates or
257 257 certificates that do not have traceable certificates of authority.
258 258
259 259 NEVER
260 260 A serve certificate will never be requested or checked.
261 261
262 262 ALLOW
263 263 A server certificate is requested. Failure to provide a
264 264 certificate or providing a bad certificate will not terminate the
265 265 session.
266 266
267 267 TRY
268 268 A server certificate is requested. Failure to provide a
269 269 certificate does not halt the session; providing a bad certificate
270 270 halts the session.
271 271
272 272 DEMAND
273 273 A server certificate is requested and must be provided and
274 274 authenticated for the session to proceed.
275 275
276 276 HARD
277 277 The same as DEMAND.
278 278
279 279 .. _Base DN:
280 280
281 281 Base DN : required
282 282 The Distinguished Name (DN) where searches for users will be performed.
283 283 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
284 284
285 285 .. _LDAP Filter:
286 286
287 287 LDAP Filter : optional
288 288 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
289 289 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
290 290 which LDAP objects are identified as representing Users for
291 291 authentication. The filter is augmented by `Login Attribute`_ below.
292 292 This can commonly be left blank.
293 293
294 294 .. _LDAP Search Scope:
295 295
296 296 LDAP Search Scope : required
297 297 This limits how far LDAP will search for a matching object.
298 298
299 299 BASE
300 300 Only allows searching of `Base DN`_ and is usually not what you
301 301 want.
302 302
303 303 ONELEVEL
304 304 Searches all entries under `Base DN`_, but not Base DN itself.
305 305
306 306 SUBTREE
307 307 Searches all entries below `Base DN`_, but not Base DN itself.
308 308 When using SUBTREE `LDAP Filter`_ is useful to limit object
309 309 location.
310 310
311 311 .. _Login Attribute:
312 312
313 313 Login Attribute : required
314 314 The LDAP record attribute that will be matched as the USERNAME or
315 315 ACCOUNT used to connect to Kallithea. This will be added to `LDAP
316 316 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
317 317 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
318 318 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
319 319 ::
320 320
321 321 (&(LDAPFILTER)(uid=jsmith))
322 322
323 323 .. _ldap_attr_firstname:
324 324
325 325 First Name Attribute : required
326 326 The LDAP record attribute which represents the user's first name.
327 327
328 328 .. _ldap_attr_lastname:
329 329
330 330 Last Name Attribute : required
331 331 The LDAP record attribute which represents the user's last name.
332 332
333 333 .. _ldap_attr_email:
334 334
335 335 Email Attribute : required
336 336 The LDAP record attribute which represents the user's email address.
337 337
338 338 If all data are entered correctly, and python-ldap_ is properly installed
339 339 users should be granted access to Kallithea with LDAP accounts. At this
340 340 time user information is copied from LDAP into the Kallithea user database.
341 341 This means that updates of an LDAP user object may not be reflected as a
342 342 user update in Kallithea.
343 343
344 344 If You have problems with LDAP access and believe You entered correct
345 345 information check out the Kallithea logs, any error messages sent from LDAP
346 346 will be saved there.
347 347
348 348 Active Directory
349 349 ^^^^^^^^^^^^^^^^
350 350
351 351 Kallithea can use Microsoft Active Directory for user authentication. This
352 352 is done through an LDAP or LDAPS connection to Active Directory. The
353 353 following LDAP configuration settings are typical for using Active
354 354 Directory ::
355 355
356 356 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
357 357 Login Attribute = sAMAccountName
358 358 First Name Attribute = givenName
359 359 Last Name Attribute = sn
360 360 Email Attribute = mail
361 361
362 362 All other LDAP settings will likely be site-specific and should be
363 363 appropriately configured.
364 364
365 365
366 366 Authentication by container or reverse-proxy
367 367 --------------------------------------------
368 368
369 369 Kallithea supports delegating the authentication
370 370 of users to its WSGI container, or to a reverse-proxy server through which all
371 371 clients access the application.
372 372
373 373 When these authentication methods are enabled in Kallithea, it uses the
374 374 username that the container/proxy (Apache or Nginx, etc.) provides and doesn't
375 375 perform the authentication itself. The authorization, however, is still done by
376 376 Kallithea according to its settings.
377 377
378 378 When a user logs in for the first time using these authentication methods,
379 379 a matching user account is created in Kallithea with default permissions. An
380 380 administrator can then modify it using Kallithea's admin interface.
381 381
382 382 It's also possible for an administrator to create accounts and configure their
383 383 permissions before the user logs in for the first time, using the :ref:`create-user` API.
384 384
385 385 Container-based authentication
386 386 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
387 387
388 388 In a container-based authentication setup, Kallithea reads the user name from
389 389 the ``REMOTE_USER`` server variable provided by the WSGI container.
390 390
391 391 After setting up your container (see `Apache with mod_wsgi`_), you'll need
392 392 to configure it to require authentication on the location configured for
393 393 Kallithea.
394 394
395 395 Proxy pass-through authentication
396 396 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
397 397
398 398 In a proxy pass-through authentication setup, Kallithea reads the user name
399 399 from the ``X-Forwarded-User`` request header, which should be configured to be
400 400 sent by the reverse-proxy server.
401 401
402 402 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
403 403 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'll need to
404 404 configure the authentication and add the username in a request header named
405 405 ``X-Forwarded-User``.
406 406
407 407 For example, the following config section for Apache sets a subdirectory in a
408 408 reverse-proxy setup with basic auth:
409 409
410 410 .. code-block:: apache
411 411
412 412 <Location /someprefix>
413 413 ProxyPass http://127.0.0.1:5000/someprefix
414 414 ProxyPassReverse http://127.0.0.1:5000/someprefix
415 415 SetEnvIf X-Url-Scheme https HTTPS=1
416 416
417 417 AuthType Basic
418 418 AuthName "Kallithea authentication"
419 419 AuthUserFile /srv/kallithea/.htpasswd
420 420 Require valid-user
421 421
422 422 RequestHeader unset X-Forwarded-User
423 423
424 424 RewriteEngine On
425 425 RewriteCond %{LA-U:REMOTE_USER} (.+)
426 426 RewriteRule .* - [E=RU:%1]
427 427 RequestHeader set X-Forwarded-User %{RU}e
428 428 </Location>
429 429
430 Setting metadata in container/reverse-proxy
431 '''''''''''''''''''''''''''''''''''''''''''
432
433 When a new user account is created on the first login, Kallithea has no information about
434 the user's email and full name. So you can set some additional request headers like in the
435 example below. In this example the user is authenticated via Kerberos and an Apache
436 mod_python fixup handler is used to get the user information from a LDAP server. But you
437 could set the request headers however you want.
438
439 .. code-block:: apache
440
441 <Location /someprefix>
442 ProxyPass http://127.0.0.1:5000/someprefix
443 ProxyPassReverse http://127.0.0.1:5000/someprefix
444 SetEnvIf X-Url-Scheme https HTTPS=1
445
446 AuthName "Kerberos Login"
447 AuthType Kerberos
448 Krb5Keytab /etc/apache2/http.keytab
449 KrbMethodK5Passwd off
450 KrbVerifyKDC on
451 Require valid-user
452
453 PythonFixupHandler ldapmetadata
454
455 RequestHeader set X_REMOTE_USER %{X_REMOTE_USER}e
456 RequestHeader set X_REMOTE_EMAIL %{X_REMOTE_EMAIL}e
457 RequestHeader set X_REMOTE_FIRSTNAME %{X_REMOTE_FIRSTNAME}e
458 RequestHeader set X_REMOTE_LASTNAME %{X_REMOTE_LASTNAME}e
459 </Location>
460
461 .. code-block:: python
462
463 from mod_python import apache
464 import ldap
465
466 LDAP_SERVER = "ldap://server.mydomain.com:389"
467 LDAP_USER = ""
468 LDAP_PASS = ""
469 LDAP_ROOT = "dc=mydomain,dc=com"
470 LDAP_FILTER = "sAMAcountName=%s"
471 LDAP_ATTR_LIST = ['sAMAcountName','givenname','sn','mail']
472
473 def fixuphandler(req):
474 if req.user is None:
475 # no user to search for
476 return apache.OK
477 else:
478 try:
479 if('\\' in req.user):
480 username = req.user.split('\\')[1]
481 elif('@' in req.user):
482 username = req.user.split('@')[0]
483 else:
484 username = req.user
485 l = ldap.initialize(LDAP_SERVER)
486 l.simple_bind_s(LDAP_USER, LDAP_PASS)
487 r = l.search_s(LDAP_ROOT, ldap.SCOPE_SUBTREE, LDAP_FILTER % username, attrlist=LDAP_ATTR_LIST)
488
489 req.subprocess_env['X_REMOTE_USER'] = username
490 req.subprocess_env['X_REMOTE_EMAIL'] = r[0][1]['mail'][0].lower()
491 req.subprocess_env['X_REMOTE_FIRSTNAME'] = "%s" % r[0][1]['givenname'][0]
492 req.subprocess_env['X_REMOTE_LASTNAME'] = "%s" % r[0][1]['sn'][0]
493 except Exception, e:
494 apache.log_error("error getting data from ldap %s" % str(e), apache.APLOG_ERR)
495
496 return apache.OK
497
430 498 .. note::
431 499 If you enable proxy pass-through authentication, make sure your server is
432 500 only accessible through the proxy. Otherwise, any client would be able to
433 501 forge the authentication header and could effectively become authenticated
434 502 using any account of their liking.
435 503
436 504
437 505 Integration with issue trackers
438 506 -------------------------------
439 507
440 508 Kallithea provides a simple integration with issue trackers. It's possible
441 509 to define a regular expression that will match an issue ID in commit messages,
442 510 and have that replaced with a URL to the issue. To enable this simply
443 511 uncomment the following variables in the ini file::
444 512
445 513 issue_pat = (?:^#|\s#)(\w+)
446 514 issue_server_link = https://issues.example.com/{repo}/issue/{id}
447 515 issue_prefix = #
448 516
449 517 ``issue_pat`` is the regular expression describing which strings in
450 518 commit messages will be treated as issue references. A match group in
451 519 parentheses should be used to specify the actual issue id.
452 520
453 521 The default expression matches issues in the format ``#<number>``, e.g., ``#300``.
454 522
455 523 Matched issue references are replaced with the link specified in
456 524 ``issue_server_link``. ``{id}`` is replaced with the issue ID, and
457 525 ``{repo}`` with the repository name. Since the # is stripped away,
458 526 ``issue_prefix`` is prepended to the link text. ``issue_prefix`` doesn't
459 527 necessarily need to be ``#``: if you set issue prefix to ``ISSUE-`` this will
460 528 generate a URL in the format:
461 529
462 530 .. code-block:: html
463 531
464 532 <a href="https://issues.example.com/example_repo/issue/300">ISSUE-300</a>
465 533
466 534 If needed, more than one pattern can be specified by appending a unique suffix to
467 535 the variables. For example::
468 536
469 537 issue_pat_wiki = (?:wiki-)(.+)
470 538 issue_server_link_wiki = https://wiki.example.com/{id}
471 539 issue_prefix_wiki = WIKI-
472 540
473 541 With these settings, wiki pages can be referenced as wiki-some-id, and every
474 542 such reference will be transformed into:
475 543
476 544 .. code-block:: html
477 545
478 546 <a href="https://wiki.example.com/some-id">WIKI-some-id</a>
479 547
480 548
481 549 Hook management
482 550 ---------------
483 551
484 552 Hooks can be managed in similar way to that used in ``.hgrc`` files.
485 553 To manage hooks, choose *Admin > Settings > Hooks*.
486 554
487 555 The built-in hooks cannot be modified, though they can be enabled or disabled in the *VCS* section.
488 556
489 557 To add another custom hook simply fill in the first textbox with
490 558 ``<name>.<hook_type>`` and the second with the hook path. Example hooks
491 559 can be found in ``kallithea.lib.hooks``.
492 560
493 561
494 562 Changing default encoding
495 563 -------------------------
496 564
497 565 By default, Kallithea uses UTF-8 encoding.
498 566 This is configurable as ``default_encoding`` in the .ini file.
499 567 This affects many parts in Kallithea including user names, filenames, and
500 568 encoding of commit messages. In addition Kallithea can detect if the ``chardet``
501 569 library is installed. If ``chardet`` is detected Kallithea will fallback to it
502 570 when there are encode/decode errors.
503 571
504 572
505 573 Celery configuration
506 574 --------------------
507 575
508 576 Kallithea can use the distributed task queue system Celery_ to run tasks like
509 577 cloning repositories or sending emails.
510 578
511 579 Kallithea will in most setups work perfectly fine out of the box (without
512 580 Celery), executing all tasks in the web server process. Some tasks can however
513 581 take some time to run and it can be better to run such tasks asynchronously in
514 582 a separate process so the web server can focus on serving web requests.
515 583
516 584 For installation and configuration of Celery, see the `Celery documentation`_.
517 585 Note that Celery requires a message broker service like RabbitMQ_ (recommended)
518 586 or Redis_.
519 587
520 588 The use of Celery is configured in the Kallithea ini configuration file.
521 589 To enable it, simply set::
522 590
523 591 use_celery = true
524 592
525 593 and add or change the ``celery.*`` and ``broker.*`` configuration variables.
526 594
527 595 Remember that the ini files use the format with '.' and not with '_' like
528 596 Celery. So for example setting `BROKER_HOST` in Celery means setting
529 597 `broker.host` in the configuration file.
530 598
531 599 To start the Celery process, run::
532 600
533 601 paster celeryd <configfile.ini>
534 602
535 603 .. note::
536 604 Make sure you run this command from the same virtualenv, and with the same
537 605 user that Kallithea runs.
538 606
539 607
540 608 HTTPS support
541 609 -------------
542 610
543 611 Kallithea will by default generate URLs based on the WSGI environment.
544 612
545 613 Alternatively, you can use some special configuration settings to control
546 614 directly which scheme/protocol Kallithea will use when generating URLs:
547 615
548 616 - With ``https_fixup = true``, the scheme will be taken from the
549 617 ``X-Url-Scheme``, ``X-Forwarded-Scheme`` or ``X-Forwarded-Proto`` HTTP header
550 618 (default ``http``).
551 619 - With ``force_https = true`` the default will be ``https``.
552 620 - With ``use_htsts = true``, Kallithea will set ``Strict-Transport-Security`` when using https.
553 621
554 622
555 623 Nginx virtual host example
556 624 --------------------------
557 625
558 626 Sample config for Nginx using proxy:
559 627
560 628 .. code-block:: nginx
561 629
562 630 upstream kallithea {
563 631 server 127.0.0.1:5000;
564 632 # add more instances for load balancing
565 633 #server 127.0.0.1:5001;
566 634 #server 127.0.0.1:5002;
567 635 }
568 636
569 637 ## gist alias
570 638 server {
571 639 listen 443;
572 640 server_name gist.example.com;
573 641 access_log /var/log/nginx/gist.access.log;
574 642 error_log /var/log/nginx/gist.error.log;
575 643
576 644 ssl on;
577 645 ssl_certificate gist.your.kallithea.server.crt;
578 646 ssl_certificate_key gist.your.kallithea.server.key;
579 647
580 648 ssl_session_timeout 5m;
581 649
582 650 ssl_protocols SSLv3 TLSv1;
583 651 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;
584 652 ssl_prefer_server_ciphers on;
585 653
586 654 rewrite ^/(.+)$ https://kallithea.example.com/_admin/gists/$1;
587 655 rewrite (.*) https://kallithea.example.com/_admin/gists;
588 656 }
589 657
590 658 server {
591 659 listen 443;
592 660 server_name kallithea.example.com
593 661 access_log /var/log/nginx/kallithea.access.log;
594 662 error_log /var/log/nginx/kallithea.error.log;
595 663
596 664 ssl on;
597 665 ssl_certificate your.kallithea.server.crt;
598 666 ssl_certificate_key your.kallithea.server.key;
599 667
600 668 ssl_session_timeout 5m;
601 669
602 670 ssl_protocols SSLv3 TLSv1;
603 671 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;
604 672 ssl_prefer_server_ciphers on;
605 673
606 674 ## uncomment root directive if you want to serve static files by nginx
607 675 ## requires static_files = false in .ini file
608 676 #root /path/to/installation/kallithea/public;
609 677 include /etc/nginx/proxy.conf;
610 678 location / {
611 679 try_files $uri @kallithea;
612 680 }
613 681
614 682 location @kallithea {
615 683 proxy_pass http://127.0.0.1:5000;
616 684 }
617 685
618 686 }
619 687
620 688 Here's the proxy.conf. It's tuned so it will not timeout on long
621 689 pushes or large pushes::
622 690
623 691 proxy_redirect off;
624 692 proxy_set_header Host $host;
625 693 ## needed for container auth
626 694 #proxy_set_header REMOTE_USER $remote_user;
627 695 #proxy_set_header X-Forwarded-User $remote_user;
628 696 proxy_set_header X-Url-Scheme $scheme;
629 697 proxy_set_header X-Host $http_host;
630 698 proxy_set_header X-Real-IP $remote_addr;
631 699 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
632 700 proxy_set_header Proxy-host $proxy_host;
633 701 proxy_buffering off;
634 702 proxy_connect_timeout 7200;
635 703 proxy_send_timeout 7200;
636 704 proxy_read_timeout 7200;
637 705 proxy_buffers 8 32k;
638 706 client_max_body_size 1024m;
639 707 client_body_buffer_size 128k;
640 708 large_client_header_buffers 8 64k;
641 709
642 710
643 711 Apache virtual host reverse proxy example
644 712 -----------------------------------------
645 713
646 714 Here is a sample configuration file for Apache using proxy:
647 715
648 716 .. code-block:: apache
649 717
650 718 <VirtualHost *:80>
651 719 ServerName kallithea.example.com
652 720
653 721 <Proxy *>
654 722 # For Apache 2.4 and later:
655 723 Require all granted
656 724
657 725 # For Apache 2.2 and earlier, instead use:
658 726 # Order allow,deny
659 727 # Allow from all
660 728 </Proxy>
661 729
662 730 #important !
663 731 #Directive to properly generate url (clone url) for pylons
664 732 ProxyPreserveHost On
665 733
666 734 #kallithea instance
667 735 ProxyPass / http://127.0.0.1:5000/
668 736 ProxyPassReverse / http://127.0.0.1:5000/
669 737
670 738 #to enable https use line below
671 739 #SetEnvIf X-Url-Scheme https HTTPS=1
672 740 </VirtualHost>
673 741
674 742 Additional tutorial
675 743 http://pylonsbook.com/en/1.1/deployment.html#using-apache-to-proxy-requests-to-pylons
676 744
677 745
678 746 Apache as subdirectory
679 747 ----------------------
680 748
681 749 Apache subdirectory part:
682 750
683 751 .. code-block:: apache
684 752
685 753 <Location /<someprefix> >
686 754 ProxyPass http://127.0.0.1:5000/<someprefix>
687 755 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
688 756 SetEnvIf X-Url-Scheme https HTTPS=1
689 757 </Location>
690 758
691 759 Besides the regular apache setup you will need to add the following line
692 760 into ``[app:main]`` section of your .ini file::
693 761
694 762 filter-with = proxy-prefix
695 763
696 764 Add the following at the end of the .ini file::
697 765
698 766 [filter:proxy-prefix]
699 767 use = egg:PasteDeploy#prefix
700 768 prefix = /<someprefix>
701 769
702 770 then change ``<someprefix>`` into your chosen prefix
703 771
704 772
705 773 Apache with mod_wsgi
706 774 --------------------
707 775
708 776 Alternatively, Kallithea can be set up with Apache under mod_wsgi. For
709 777 that, you'll need to:
710 778
711 779 - Install mod_wsgi. If using a Debian-based distro, you can install
712 780 the package libapache2-mod-wsgi::
713 781
714 782 aptitude install libapache2-mod-wsgi
715 783
716 784 - Enable mod_wsgi::
717 785
718 786 a2enmod wsgi
719 787
720 788 - Create a wsgi dispatch script, like the one below. Make sure you
721 789 check that the paths correctly point to where you installed Kallithea
722 790 and its Python Virtual Environment.
723 791 - Enable the ``WSGIScriptAlias`` directive for the WSGI dispatch script,
724 792 as in the following example. Once again, check the paths are
725 793 correctly specified.
726 794
727 795 Here is a sample excerpt from an Apache Virtual Host configuration file:
728 796
729 797 .. code-block:: apache
730 798
731 799 WSGIDaemonProcess kallithea \
732 800 processes=1 threads=4 \
733 801 python-path=/srv/kallithea/venv/lib/python2.7/site-packages
734 802 WSGIScriptAlias / /srv/kallithea/dispatch.wsgi
735 803 WSGIPassAuthorization On
736 804
737 805 Or if using a dispatcher WSGI script with proper virtualenv activation:
738 806
739 807 .. code-block:: apache
740 808
741 809 WSGIDaemonProcess kallithea processes=1 threads=4
742 810 WSGIScriptAlias / /srv/kallithea/dispatch.wsgi
743 811 WSGIPassAuthorization On
744 812
745 813 .. note::
746 814 When running apache as root, please make sure it doesn't run Kallithea as
747 815 root, for examply by adding: ``user=www-data group=www-data`` to the configuration.
748 816
749 817 Example WSGI dispatch script:
750 818
751 819 .. code-block:: python
752 820
753 821 import os
754 822 os.environ["HGENCODING"] = "UTF-8"
755 823 os.environ['PYTHON_EGG_CACHE'] = '/srv/kallithea/.egg-cache'
756 824
757 825 # sometimes it's needed to set the curent dir
758 826 os.chdir('/srv/kallithea/')
759 827
760 828 import site
761 829 site.addsitedir("/srv/kallithea/venv/lib/python2.7/site-packages")
762 830
763 831 from paste.deploy import loadapp
764 832 from paste.script.util.logging_config import fileConfig
765 833
766 834 fileConfig('/srv/kallithea/my.ini')
767 835 application = loadapp('config:/srv/kallithea/my.ini')
768 836
769 837 Or using proper virtualenv activation:
770 838
771 839 .. code-block:: python
772 840
773 841 activate_this = '/srv/kallithea/venv/bin/activate_this.py'
774 842 execfile(activate_this, dict(__file__=activate_this))
775 843
776 844 import os
777 845 os.environ['HOME'] = '/srv/kallithea'
778 846
779 847 ini = '/srv/kallithea/kallithea.ini'
780 848 from paste.script.util.logging_config import fileConfig
781 849 fileConfig(ini)
782 850 from paste.deploy import loadapp
783 851 application = loadapp('config:' + ini)
784 852
785 853
786 854 Other configuration files
787 855 -------------------------
788 856
789 857 A number of `example init.d scripts`__ can be found in
790 858 the ``init.d`` directory of the Kallithea source.
791 859
792 860 .. __: https://kallithea-scm.org/repos/kallithea/files/tip/init.d/ .
793 861
794 862
795 863 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
796 864 .. _python: http://www.python.org/
797 865 .. _Mercurial: http://mercurial.selenic.com/
798 866 .. _Celery: http://celeryproject.org/
799 867 .. _Celery documentation: http://docs.celeryproject.org/en/latest/getting-started/index.html
800 868 .. _RabbitMQ: http://www.rabbitmq.com/
801 869 .. _Redis: http://redis.io/
802 870 .. _python-ldap: http://www.python-ldap.org/
803 871 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
804 872 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
@@ -1,195 +1,226 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_container
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Kallithea container based authentication plugin
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, 2012
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 import logging
29 29 from kallithea.lib import auth_modules
30 30 from kallithea.lib.utils2 import str2bool, safe_unicode
31 31 from kallithea.lib.compat import hybrid_property
32 from kallithea.model.db import User
32 from kallithea.model.db import User, Setting
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 class KallitheaAuthPlugin(auth_modules.KallitheaExternalAuthPlugin):
38 38 def __init__(self):
39 39 pass
40 40
41 41 @hybrid_property
42 42 def name(self):
43 43 return "container"
44 44
45 45 @hybrid_property
46 46 def is_container_auth(self):
47 47 return True
48 48
49 49 def settings(self):
50 50
51 51 settings = [
52 52 {
53 53 "name": "header",
54 54 "validator": self.validators.UnicodeString(strip=True, not_empty=True),
55 55 "type": "string",
56 "description": "Header to extract the user from",
56 "description": "Request header to extract the username from",
57 57 "default": "REMOTE_USER",
58 "formname": "Header"
58 "formname": "Username header"
59 },
60 {
61 "name": "email_header",
62 "validator": self.validators.UnicodeString(strip=True),
63 "type": "string",
64 "description": "Optional request header to extract the email from",
65 "default": "",
66 "formname": "Email header"
67 },
68 {
69 "name": "firstname_header",
70 "validator": self.validators.UnicodeString(strip=True),
71 "type": "string",
72 "description": "Optional request header to extract the first name from",
73 "default": "",
74 "formname": "Firstname header"
75 },
76 {
77 "name": "lastname_header",
78 "validator": self.validators.UnicodeString(strip=True),
79 "type": "string",
80 "description": "Optional request header to extract the last name from",
81 "default": "",
82 "formname": "Lastname header"
59 83 },
60 84 {
61 85 "name": "fallback_header",
62 86 "validator": self.validators.UnicodeString(strip=True),
63 87 "type": "string",
64 "description": "Header to extract the user from when main one fails",
88 "description": "Request header to extract the user from when main one fails",
65 89 "default": "HTTP_X_FORWARDED_USER",
66 90 "formname": "Fallback header"
67 91 },
68 92 {
69 93 "name": "clean_username",
70 94 "validator": self.validators.StringBoolean(if_missing=False),
71 95 "type": "bool",
72 96 "description": "Perform cleaning of user, if passed user has @ in username "
73 97 "then first part before @ is taken. "
74 98 "If there's \\ in the username only the part after \\ is taken",
75 99 "default": "True",
76 100 "formname": "Clean username"
77 101 },
78 102 ]
79 103 return settings
80 104
81 105 def use_fake_password(self):
82 106 return True
83 107
84 108 def user_activation_state(self):
85 109 def_user_perms = User.get_default_user().AuthUser.permissions['global']
86 110 return 'hg.extern_activate.auto' in def_user_perms
87 111
88 112 def _clean_username(self, username):
89 113 # Removing realm and domain from username
90 114 username = username.partition('@')[0]
91 115 username = username.rpartition('\\')[2]
92 116 return username
93 117
94 118 def _get_username(self, environ, settings):
95 119 username = None
96 120 environ = environ or {}
97 121 if not environ:
98 122 log.debug('got empty environ: %s', environ)
99 123
100 124 settings = settings or {}
101 125 if settings.get('header'):
102 126 header = settings.get('header')
103 127 username = environ.get(header)
104 128 log.debug('extracted %s:%s', header, username)
105 129
106 130 # fallback mode
107 131 if not username and settings.get('fallback_header'):
108 132 header = settings.get('fallback_header')
109 133 username = environ.get(header)
110 134 log.debug('extracted %s:%s', header, username)
111 135
112 136 if username and str2bool(settings.get('clean_username')):
113 137 log.debug('Received username %s from container', username)
114 138 username = self._clean_username(username)
115 139 log.debug('New cleanup user is: %s', username)
116 140 return username
117 141
118 142 def get_user(self, username=None, **kwargs):
119 143 """
120 144 Helper method for user fetching in plugins, by default it's using
121 145 simple fetch by username, but this method can be customized in plugins
122 146 eg. container auth plugin to fetch user by environ params
123 147 :param username: username if given to fetch
124 148 :param kwargs: extra arguments needed for user fetching.
125 149 """
126 150 environ = kwargs.get('environ') or {}
127 151 settings = kwargs.get('settings') or {}
128 152 username = self._get_username(environ, settings)
129 153 # we got the username, so use default method now
130 154 return super(KallitheaAuthPlugin, self).get_user(username)
131 155
132 156 def auth(self, userobj, username, password, settings, **kwargs):
133 157 """
134 158 Gets the container_auth username (or email). It tries to get username
135 159 from REMOTE_USER if this plugin is enabled, if that fails
136 160 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
137 161 is set. clean_username extracts the username from this data if it's
138 162 having @ in it.
139 163 Return None on failure. On success, return a dictionary of the form:
140 164
141 165 see: KallitheaAuthPluginBase.auth_func_attrs
142 166
143 167 :param userobj:
144 168 :param username:
145 169 :param password:
146 170 :param settings:
147 171 :param kwargs:
148 172 """
149 173 environ = kwargs.get('environ')
150 174 if not environ:
151 175 log.debug('Empty environ data skipping...')
152 176 return None
153 177
154 178 if not userobj:
155 179 userobj = self.get_user('', environ=environ, settings=settings)
156 180
157 181 # we don't care passed username/password for container auth plugins.
158 182 # only way to log in is using environ
159 183 username = None
160 184 if userobj:
161 185 username = getattr(userobj, 'username')
162 186
163 187 if not username:
164 188 # we don't have any objects in DB, user doesn't exist, extract
165 189 # username from environ based on the settings
166 190 username = self._get_username(environ, settings)
167 191
168 192 # if cannot fetch username, it's a no-go for this plugin to proceed
169 193 if not username:
170 194 return None
171 195
172 196 # old attrs fetched from Kallithea database
173 197 admin = getattr(userobj, 'admin', False)
174 198 active = getattr(userobj, 'active', True)
175 email = getattr(userobj, 'email', '')
176 firstname = getattr(userobj, 'firstname', '')
177 lastname = getattr(userobj, 'lastname', '')
199 email = environ.get(settings.get('email_header'), getattr(userobj, 'email', ''))
200 firstname = environ.get(settings.get('firstname_header'), getattr(userobj, 'firstname', ''))
201 lastname = environ.get(settings.get('lastname_header'), getattr(userobj, 'lastname', ''))
178 202
179 203 user_data = {
180 204 'username': username,
181 205 'firstname': safe_unicode(firstname or username),
182 206 'lastname': safe_unicode(lastname or ''),
183 207 'groups': [],
184 208 'email': email or '',
185 209 'admin': admin or False,
186 210 'active': active,
187 211 'active_from_extern': True,
188 212 'extern_name': username,
189 213 }
190 214
191 215 log.info('user `%s` authenticated correctly', user_data['username'])
192 216 return user_data
193 217
194 218 def get_managed_fields(self):
195 return ['username', 'password']
219 fields = ['username', 'password']
220 if(Setting.get_by_name('auth_container_email_header').app_settings_value):
221 fields.append('email')
222 if(Setting.get_by_name('auth_container_firstname_header').app_settings_value):
223 fields.append('firstname')
224 if(Setting.get_by_name('auth_container_lastname_header').app_settings_value):
225 fields.append('lastname')
226 return fields
@@ -1,229 +1,266 b''
1 1 from kallithea.tests import *
2 2 from kallithea.model.db import Setting
3 3
4 4
5 5 class TestAuthSettingsController(TestController):
6 6 def _enable_plugins(self, plugins_list):
7 7 test_url = url(controller='admin/auth_settings',
8 8 action='auth_settings')
9 9 params={'auth_plugins': plugins_list, '_authentication_token': self.authentication_token()}
10 10
11 11 for plugin in plugins_list.split(','):
12 12 enable = plugin.partition('kallithea.lib.auth_modules.')[-1]
13 13 params.update({'%s_enabled' % enable: True})
14 14 response = self.app.post(url=test_url, params=params)
15 15 return params
16 16 #self.checkSessionFlash(response, 'Auth settings updated successfully')
17 17
18 18 def test_index(self):
19 19 self.log_user()
20 20 response = self.app.get(url(controller='admin/auth_settings',
21 21 action='index'))
22 22 response.mustcontain('Authentication Plugins')
23 23
24 24 def test_ldap_save_settings(self):
25 25 self.log_user()
26 26 if not ldap_lib_installed:
27 27 raise SkipTest('skipping due to missing ldap lib')
28 28
29 29 params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
30 30 params.update({'auth_ldap_host': u'dc.example.com',
31 31 'auth_ldap_port': '999',
32 32 'auth_ldap_tls_kind': 'PLAIN',
33 33 'auth_ldap_tls_reqcert': 'NEVER',
34 34 'auth_ldap_dn_user': 'test_user',
35 35 'auth_ldap_dn_pass': 'test_pass',
36 36 'auth_ldap_base_dn': 'test_base_dn',
37 37 'auth_ldap_filter': 'test_filter',
38 38 'auth_ldap_search_scope': 'BASE',
39 39 'auth_ldap_attr_login': 'test_attr_login',
40 40 'auth_ldap_attr_firstname': 'ima',
41 41 'auth_ldap_attr_lastname': 'tester',
42 42 'auth_ldap_attr_email': 'test@example.com'})
43 43
44 44 test_url = url(controller='admin/auth_settings',
45 45 action='auth_settings')
46 46
47 47 response = self.app.post(url=test_url, params=params)
48 48 self.checkSessionFlash(response, 'Auth settings updated successfully')
49 49
50 50 new_settings = Setting.get_auth_settings()
51 51 self.assertEqual(new_settings['auth_ldap_host'], u'dc.example.com',
52 52 'fail db write compare')
53 53
54 54 def test_ldap_error_form_wrong_port_number(self):
55 55 self.log_user()
56 56 if not ldap_lib_installed:
57 57 raise SkipTest('skipping due to missing ldap lib')
58 58
59 59 params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
60 60 params.update({'auth_ldap_host': '',
61 61 'auth_ldap_port': 'i-should-be-number', # bad port num
62 62 'auth_ldap_tls_kind': 'PLAIN',
63 63 'auth_ldap_tls_reqcert': 'NEVER',
64 64 'auth_ldap_dn_user': '',
65 65 'auth_ldap_dn_pass': '',
66 66 'auth_ldap_base_dn': '',
67 67 'auth_ldap_filter': '',
68 68 'auth_ldap_search_scope': 'BASE',
69 69 'auth_ldap_attr_login': '',
70 70 'auth_ldap_attr_firstname': '',
71 71 'auth_ldap_attr_lastname': '',
72 72 'auth_ldap_attr_email': ''})
73 73 test_url = url(controller='admin/auth_settings',
74 74 action='auth_settings')
75 75
76 76 response = self.app.post(url=test_url, params=params)
77 77
78 78 response.mustcontain("""<span class="error-message">"""
79 79 """Please enter a number</span>""")
80 80
81 81 def test_ldap_error_form(self):
82 82 self.log_user()
83 83 if not ldap_lib_installed:
84 84 raise SkipTest('skipping due to missing ldap lib')
85 85
86 86 params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
87 87 params.update({'auth_ldap_host': 'Host',
88 88 'auth_ldap_port': '123',
89 89 'auth_ldap_tls_kind': 'PLAIN',
90 90 'auth_ldap_tls_reqcert': 'NEVER',
91 91 'auth_ldap_dn_user': '',
92 92 'auth_ldap_dn_pass': '',
93 93 'auth_ldap_base_dn': '',
94 94 'auth_ldap_filter': '',
95 95 'auth_ldap_search_scope': 'BASE',
96 96 'auth_ldap_attr_login': '', # <----- missing required input
97 97 'auth_ldap_attr_firstname': '',
98 98 'auth_ldap_attr_lastname': '',
99 99 'auth_ldap_attr_email': ''})
100 100
101 101 test_url = url(controller='admin/auth_settings',
102 102 action='auth_settings')
103 103
104 104 response = self.app.post(url=test_url, params=params)
105 105
106 106 response.mustcontain("""<span class="error-message">The LDAP Login"""
107 107 """ attribute of the CN must be specified""")
108 108
109 109 def test_ldap_login(self):
110 110 pass
111 111
112 112 def test_ldap_login_incorrect(self):
113 113 pass
114 114
115 115 def _container_auth_setup(self, **settings):
116 116 self.log_user()
117 117
118 118 params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_container')
119 119 params.update(settings)
120 120
121 121 test_url = url(controller='admin/auth_settings',
122 122 action='auth_settings')
123 123
124 124 response = self.app.post(url=test_url, params=params)
125 125 response = response.follow()
126 126 response.click('Log Out') # end admin login session
127 127
128 128 def _container_auth_verify_login(self, resulting_username, **get_kwargs):
129 129 response = self.app.get(
130 130 url=url(controller='admin/my_account', action='my_account'),
131 131 **get_kwargs
132 132 )
133 133 response.mustcontain('My Account %s' % resulting_username)
134 134
135 135 def test_container_auth_login_header(self):
136 136 self._container_auth_setup(
137 137 auth_container_header='THE_USER_NAME',
138 auth_container_email_header='',
139 auth_container_firstname_header='',
140 auth_container_lastname_header='',
138 141 auth_container_fallback_header='',
139 142 auth_container_clean_username='False',
140 143 )
141 144 self._container_auth_verify_login(
142 145 extra_environ={'THE_USER_NAME': 'john@example.org'},
143 146 resulting_username='john@example.org',
144 147 )
145 148
149 def test_container_auth_login_header_attr(self):
150 self._container_auth_setup(
151 auth_container_header='THE_USER_NAME',
152 auth_container_email_header='THE_USER_EMAIL',
153 auth_container_firstname_header='THE_USER_FIRSTNAME',
154 auth_container_lastname_header='THE_USER_LASTNAME',
155 auth_container_fallback_header='',
156 auth_container_clean_username='False',
157 )
158 response = self.app.get(
159 url=url(controller='admin/my_account', action='my_account'),
160 extra_environ={'THE_USER_NAME': 'johnd',
161 'THE_USER_EMAIL': 'john@example.org',
162 'THE_USER_FIRSTNAME': 'John',
163 'THE_USER_LASTNAME': 'Doe',
164 }
165 )
166 self.assertEqual(response.form['email'].value, 'john@example.org')
167 self.assertEqual(response.form['firstname'].value, 'John')
168 self.assertEqual(response.form['lastname'].value, 'Doe')
169
170
146 171 def test_container_auth_login_fallback_header(self):
147 172 self._container_auth_setup(
148 173 auth_container_header='THE_USER_NAME',
174 auth_container_email_header='',
175 auth_container_firstname_header='',
176 auth_container_lastname_header='',
149 177 auth_container_fallback_header='HTTP_X_YZZY',
150 178 auth_container_clean_username='False',
151 179 )
152 180 self._container_auth_verify_login(
153 181 headers={'X-Yzzy': r'foo\bar'},
154 182 resulting_username=r'foo\bar',
155 183 )
156 184
157 185 def test_container_auth_clean_username_at(self):
158 186 self._container_auth_setup(
159 187 auth_container_header='REMOTE_USER',
188 auth_container_email_header='',
189 auth_container_firstname_header='',
190 auth_container_lastname_header='',
160 191 auth_container_fallback_header='',
161 192 auth_container_clean_username='True',
162 193 )
163 194 self._container_auth_verify_login(
164 195 extra_environ={'REMOTE_USER': 'john@example.org'},
165 196 resulting_username='john',
166 197 )
167 198
168 199 def test_container_auth_clean_username_backslash(self):
169 200 self._container_auth_setup(
170 201 auth_container_header='REMOTE_USER',
202 auth_container_email_header='',
203 auth_container_firstname_header='',
204 auth_container_lastname_header='',
171 205 auth_container_fallback_header='',
172 206 auth_container_clean_username='True',
173 207 )
174 208 self._container_auth_verify_login(
175 209 extra_environ={'REMOTE_USER': r'example\jane'},
176 210 resulting_username=r'jane',
177 211 )
178 212
179 213 def test_container_auth_no_logout(self):
180 214 self._container_auth_setup(
181 215 auth_container_header='REMOTE_USER',
216 auth_container_email_header='',
217 auth_container_firstname_header='',
218 auth_container_lastname_header='',
182 219 auth_container_fallback_header='',
183 220 auth_container_clean_username='True',
184 221 )
185 222 response = self.app.get(
186 223 url=url(controller='admin/my_account', action='my_account'),
187 224 extra_environ={'REMOTE_USER': 'john'},
188 225 )
189 226 self.assertNotIn('Log Out', response.normal_body)
190 227
191 228 def test_crowd_save_settings(self):
192 229 self.log_user()
193 230
194 231 params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_crowd')
195 232 params.update({'auth_crowd_host': ' hostname ',
196 233 'auth_crowd_app_password': 'secret',
197 234 'auth_crowd_admin_groups': 'mygroup',
198 235 'auth_crowd_port': '123',
199 236 'auth_crowd_app_name': 'xyzzy'})
200 237
201 238 test_url = url(controller='admin/auth_settings',
202 239 action='auth_settings')
203 240
204 241 response = self.app.post(url=test_url, params=params)
205 242 self.checkSessionFlash(response, 'Auth settings updated successfully')
206 243
207 244 new_settings = Setting.get_auth_settings()
208 245 self.assertEqual(new_settings['auth_crowd_host'], u'hostname',
209 246 'fail db write compare')
210 247
211 248 def test_pam_save_settings(self):
212 249 self.log_user()
213 250
214 251 if not pam_lib_installed:
215 252 raise SkipTest('skipping due to missing pam lib')
216 253
217 254 params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_pam')
218 255 params.update({'auth_pam_service': 'kallithea',
219 256 'auth_pam_gecos': '^foo-.*'})
220 257
221 258 test_url = url(controller='admin/auth_settings',
222 259 action='auth_settings')
223 260
224 261 response = self.app.post(url=test_url, params=params)
225 262 self.checkSessionFlash(response, 'Auth settings updated successfully')
226 263
227 264 new_settings = Setting.get_auth_settings()
228 265 self.assertEqual(new_settings['auth_pam_service'], u'kallithea',
229 266 'fail db write compare')
General Comments 0
You need to be logged in to leave comments. Login now