##// END OF EJS Templates
ssh: introduce ini setting 'ssh_enabled', disabled by default...
Thomas De Schampheleire -
r7677:6da70f45 default
parent child Browse files
Show More
@@ -1,478 +1,485 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # Kallithea - config file generated with kallithea-config #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10
11 11 ################################################################################
12 12 ## Email settings ##
13 13 ## ##
14 14 ## Refer to the documentation ("Email settings") for more details. ##
15 15 ## ##
16 16 ## It is recommended to use a valid sender address that passes access ##
17 17 ## validation and spam filtering in mail servers. ##
18 18 ################################################################################
19 19
20 20 ## 'From' header for application emails. You can optionally add a name.
21 21 ## Default:
22 22 #app_email_from = Kallithea
23 23 ## Examples:
24 24 #app_email_from = Kallithea <kallithea-noreply@example.com>
25 25 #app_email_from = kallithea-noreply@example.com
26 26
27 27 ## Subject prefix for application emails.
28 28 ## A space between this prefix and the real subject is automatically added.
29 29 ## Default:
30 30 #email_prefix =
31 31 ## Example:
32 32 #email_prefix = [Kallithea]
33 33
34 34 ## Recipients for error emails and fallback recipients of application mails.
35 35 ## Multiple addresses can be specified, comma-separated.
36 36 ## Only addresses are allowed, do not add any name part.
37 37 ## Default:
38 38 #email_to =
39 39 ## Examples:
40 40 #email_to = admin@example.com
41 41 #email_to = admin@example.com,another_admin@example.com
42 42 email_to =
43 43
44 44 ## 'From' header for error emails. You can optionally add a name.
45 45 ## Default: (none)
46 46 ## Examples:
47 47 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
48 48 #error_email_from = kallithea_errors@example.com
49 49 error_email_from =
50 50
51 51 ## SMTP server settings
52 52 ## If specifying credentials, make sure to use secure connections.
53 53 ## Default: Send unencrypted unauthenticated mails to the specified smtp_server.
54 54 ## For "SSL", use smtp_use_ssl = true and smtp_port = 465.
55 55 ## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587.
56 56 smtp_server =
57 57 #smtp_username =
58 58 #smtp_password =
59 59 smtp_port =
60 60 #smtp_use_ssl = false
61 61 #smtp_use_tls = false
62 62
63 63 ## Entry point for 'gearbox serve'
64 64 [server:main]
65 65 #host = 127.0.0.1
66 66 host = 0.0.0.0
67 67 port = 5000
68 68
69 69 ## WAITRESS ##
70 70 use = egg:waitress#main
71 71 ## number of worker threads
72 72 threads = 1
73 73 ## MAX BODY SIZE 100GB
74 74 max_request_body_size = 107374182400
75 75 ## use poll instead of select, fixes fd limits, may not work on old
76 76 ## windows systems.
77 77 #asyncore_use_poll = True
78 78
79 79 ## middleware for hosting the WSGI application under a URL prefix
80 80 #[filter:proxy-prefix]
81 81 #use = egg:PasteDeploy#prefix
82 82 #prefix = /<your-prefix>
83 83
84 84 [app:main]
85 85 use = egg:kallithea
86 86 ## enable proxy prefix middleware
87 87 #filter-with = proxy-prefix
88 88
89 89 full_stack = true
90 90 static_files = true
91 91
92 92 ## Internationalization (see setup documentation for details)
93 93 ## By default, the language requested by the browser is used if available.
94 94 #i18n.enabled = false
95 95 ## Fallback language, empty for English (valid values are the names of subdirectories in kallithea/i18n):
96 96 i18n.lang =
97 97
98 98 cache_dir = %(here)s/data
99 99 index_dir = %(here)s/data/index
100 100
101 101 ## uncomment and set this path to use archive download cache
102 102 archive_cache_dir = %(here)s/tarballcache
103 103
104 104 ## change this to unique ID for security
105 105 #app_instance_uuid = VERY-SECRET
106 106 app_instance_uuid = development-not-secret
107 107
108 108 ## cut off limit for large diffs (size in bytes)
109 109 cut_off_limit = 256000
110 110
111 111 ## force https in Kallithea, fixes https redirects, assumes it's always https
112 112 force_https = false
113 113
114 114 ## use Strict-Transport-Security headers
115 115 use_htsts = false
116 116
117 117 ## number of commits stats will parse on each iteration
118 118 commit_parse_limit = 25
119 119
120 120 ## Path to Python executable to be used for git hooks.
121 121 ## This value will be written inside the git hook scripts as the text
122 122 ## after '#!' (shebang). When empty or not defined, the value of
123 123 ## 'sys.executable' at the time of installation of the git hooks is
124 124 ## used, which is correct in many cases but for example not when using uwsgi.
125 125 ## If you change this setting, you should reinstall the Git hooks via
126 126 ## Admin > Settings > Remap and Rescan.
127 127 # git_hook_interpreter = /srv/kallithea/venv/bin/python2
128 128
129 129 ## path to git executable
130 130 git_path = git
131 131
132 132 ## git rev filter option, --all is the default filter, if you need to
133 133 ## hide all refs in changelog switch this to --branches --tags
134 134 #git_rev_filter = --branches --tags
135 135
136 136 ## RSS feed options
137 137 rss_cut_off_limit = 256000
138 138 rss_items_per_page = 10
139 139 rss_include_diff = false
140 140
141 141 ## options for showing and identifying changesets
142 142 show_sha_length = 12
143 143 show_revision_number = false
144 144
145 145 ## Canonical URL to use when creating full URLs in UI and texts.
146 146 ## Useful when the site is available under different names or protocols.
147 147 ## Defaults to what is provided in the WSGI environment.
148 148 #canonical_url = https://kallithea.example.com/repos
149 149
150 150 ## gist URL alias, used to create nicer urls for gist. This should be an
151 151 ## url that does rewrites to _admin/gists/<gistid>.
152 152 ## example: http://gist.example.com/{gistid}. Empty means use the internal
153 153 ## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
154 154 gist_alias_url =
155 155
156 156 ## default encoding used to convert from and to unicode
157 157 ## can be also a comma separated list of encoding in case of mixed encodings
158 158 default_encoding = utf-8
159 159
160 160 ## Set Mercurial encoding, similar to setting HGENCODING before launching Kallithea
161 161 hgencoding = utf-8
162 162
163 163 ## issue tracker for Kallithea (leave blank to disable, absent for default)
164 164 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
165 165
166 166 ## issue tracking mapping for commit messages, comments, PR descriptions, ...
167 167 ## Refer to the documentation ("Integration with issue trackers") for more details.
168 168
169 169 ## regular expression to match issue references
170 170 ## This pattern may/should contain parenthesized groups, that can
171 171 ## be referred to in issue_server_link or issue_sub using Python backreferences
172 172 ## (e.g. \1, \2, ...). You can also create named groups with '(?P<groupname>)'.
173 173 ## To require mandatory whitespace before the issue pattern, use:
174 174 ## (?:^|(?<=\s)) before the actual pattern, and for mandatory whitespace
175 175 ## behind the issue pattern, use (?:$|(?=\s)) after the actual pattern.
176 176
177 177 issue_pat = #(\d+)
178 178
179 179 ## server url to the issue
180 180 ## This pattern may/should contain backreferences to parenthesized groups in issue_pat.
181 181 ## A backreference can be \1, \2, ... or \g<groupname> if you specified a named group
182 182 ## called 'groupname' in issue_pat.
183 183 ## The special token {repo} is replaced with the full repository name
184 184 ## including repository groups, while {repo_name} is replaced with just
185 185 ## the name of the repository.
186 186
187 187 issue_server_link = https://issues.example.com/{repo}/issue/\1
188 188
189 189 ## substitution pattern to use as the link text
190 190 ## If issue_sub is empty, the text matched by issue_pat is retained verbatim
191 191 ## for the link text. Otherwise, the link text is that of issue_sub, with any
192 192 ## backreferences to groups in issue_pat replaced.
193 193
194 194 issue_sub =
195 195
196 196 ## issue_pat, issue_server_link and issue_sub can have suffixes to specify
197 197 ## multiple patterns, to other issues server, wiki or others
198 198 ## below an example how to create a wiki pattern
199 199 # wiki-some-id -> https://wiki.example.com/some-id
200 200
201 201 #issue_pat_wiki = wiki-(\S+)
202 202 #issue_server_link_wiki = https://wiki.example.com/\1
203 203 #issue_sub_wiki = WIKI-\1
204 204
205 205 ## alternative return HTTP header for failed authentication. Default HTTP
206 206 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
207 207 ## handling that. Set this variable to 403 to return HTTPForbidden
208 208 auth_ret_code =
209 209
210 210 ## allows to change the repository location in settings page
211 211 allow_repo_location_change = True
212 212
213 213 ## allows to setup custom hooks in settings page
214 214 allow_custom_hooks_settings = True
215 215
216 216 ## extra extensions for indexing, space separated and without the leading '.'.
217 217 # index.extensions =
218 218 # gemfile
219 219 # lock
220 220
221 221 ## extra filenames for indexing, space separated
222 222 # index.filenames =
223 223 # .dockerignore
224 224 # .editorconfig
225 225 # INSTALL
226 226 # CHANGELOG
227 227
228 228 ####################################
229 ### SSH CONFIG ####
230 ####################################
231
232 ## SSH is disabled by default, until an Administrator decides to enable it.
233 ssh_enabled = false
234
235 ####################################
229 236 ### CELERY CONFIG ####
230 237 ####################################
231 238
232 239 use_celery = false
233 240
234 241 ## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq:
235 242 broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost
236 243
237 244 celery.imports = kallithea.lib.celerylib.tasks
238 245 celery.accept.content = pickle
239 246 celery.result.backend = amqp
240 247 celery.result.dburi = amqp://
241 248 celery.result.serialier = json
242 249
243 250 #celery.send.task.error.emails = true
244 251 #celery.amqp.task.result.expires = 18000
245 252
246 253 celeryd.concurrency = 2
247 254 celeryd.max.tasks.per.child = 1
248 255
249 256 ## If true, tasks will never be sent to the queue, but executed locally instead.
250 257 celery.always.eager = false
251 258
252 259 ####################################
253 260 ### BEAKER CACHE ####
254 261 ####################################
255 262
256 263 beaker.cache.data_dir = %(here)s/data/cache/data
257 264 beaker.cache.lock_dir = %(here)s/data/cache/lock
258 265
259 266 beaker.cache.regions = short_term,long_term,sql_cache_short
260 267
261 268 beaker.cache.short_term.type = memory
262 269 beaker.cache.short_term.expire = 60
263 270 beaker.cache.short_term.key_length = 256
264 271
265 272 beaker.cache.long_term.type = memory
266 273 beaker.cache.long_term.expire = 36000
267 274 beaker.cache.long_term.key_length = 256
268 275
269 276 beaker.cache.sql_cache_short.type = memory
270 277 beaker.cache.sql_cache_short.expire = 10
271 278 beaker.cache.sql_cache_short.key_length = 256
272 279
273 280 ####################################
274 281 ### BEAKER SESSION ####
275 282 ####################################
276 283
277 284 ## Name of session cookie. Should be unique for a given host and path, even when running
278 285 ## on different ports. Otherwise, cookie sessions will be shared and messed up.
279 286 session.key = kallithea
280 287 ## Sessions should always only be accessible by the browser, not directly by JavaScript.
281 288 session.httponly = true
282 289 ## Session lifetime. 2592000 seconds is 30 days.
283 290 session.timeout = 2592000
284 291
285 292 ## Server secret used with HMAC to ensure integrity of cookies.
286 293 #session.secret = VERY-SECRET
287 294 session.secret = development-not-secret
288 295 ## Further, encrypt the data with AES.
289 296 #session.encrypt_key = <key_for_encryption>
290 297 #session.validate_key = <validation_key>
291 298
292 299 ## Type of storage used for the session, current types are
293 300 ## dbm, file, memcached, database, and memory.
294 301
295 302 ## File system storage of session data. (default)
296 303 #session.type = file
297 304
298 305 ## Cookie only, store all session data inside the cookie. Requires secure secrets.
299 306 #session.type = cookie
300 307
301 308 ## Database storage of session data.
302 309 #session.type = ext:database
303 310 #session.sa.url = postgresql://postgres:qwe@localhost/kallithea
304 311 #session.table_name = db_session
305 312
306 313 ############################
307 314 ## ERROR HANDLING SYSTEMS ##
308 315 ############################
309 316
310 317 # Propagate email settings to ErrorReporter of TurboGears2
311 318 # You do not normally need to change these lines
312 319 get trace_errors.error_email = email_to
313 320 get trace_errors.smtp_server = smtp_server
314 321 get trace_errors.smtp_port = smtp_port
315 322 get trace_errors.from_address = error_email_from
316 323
317 324 ################################################################################
318 325 ## WARNING: *DEBUG MODE MUST BE OFF IN A PRODUCTION ENVIRONMENT* ##
319 326 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
320 327 ## execute malicious code after an exception is raised. ##
321 328 ################################################################################
322 329 #debug = false
323 330 debug = true
324 331
325 332 ##################################
326 333 ### LOGVIEW CONFIG ###
327 334 ##################################
328 335
329 336 logview.sqlalchemy = #faa
330 337 logview.pylons.templating = #bfb
331 338 logview.pylons.util = #eee
332 339
333 340 #########################################################
334 341 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
335 342 #########################################################
336 343
337 344 # SQLITE [default]
338 345 sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
339 346
340 347 # see sqlalchemy docs for others
341 348
342 349 sqlalchemy.pool_recycle = 3600
343 350
344 351 ################################
345 352 ### ALEMBIC CONFIGURATION ####
346 353 ################################
347 354
348 355 [alembic]
349 356 script_location = kallithea:alembic
350 357
351 358 ################################
352 359 ### LOGGING CONFIGURATION ####
353 360 ################################
354 361
355 362 [loggers]
356 363 keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer, werkzeug, backlash
357 364
358 365 [handlers]
359 366 keys = console, console_color, console_color_sql, null
360 367
361 368 [formatters]
362 369 keys = generic, color_formatter, color_formatter_sql
363 370
364 371 #############
365 372 ## LOGGERS ##
366 373 #############
367 374
368 375 [logger_root]
369 376 level = NOTSET
370 377 #handlers = console
371 378 handlers = console_color
372 379 # For coloring based on log level:
373 380 # handlers = console_color
374 381
375 382 [logger_routes]
376 383 #level = WARN
377 384 level = DEBUG
378 385 handlers =
379 386 qualname = routes.middleware
380 387 ## "level = DEBUG" logs the route matched and routing variables.
381 388
382 389 [logger_beaker]
383 390 #level = WARN
384 391 level = DEBUG
385 392 handlers =
386 393 qualname = beaker.container
387 394
388 395 [logger_templates]
389 396 #level = WARN
390 397 level = INFO
391 398 handlers =
392 399 qualname = pylons.templating
393 400
394 401 [logger_kallithea]
395 402 #level = WARN
396 403 level = DEBUG
397 404 handlers =
398 405 qualname = kallithea
399 406
400 407 [logger_tg]
401 408 #level = WARN
402 409 level = DEBUG
403 410 handlers =
404 411 qualname = tg
405 412
406 413 [logger_gearbox]
407 414 #level = WARN
408 415 level = DEBUG
409 416 handlers =
410 417 qualname = gearbox
411 418
412 419 [logger_sqlalchemy]
413 420 level = WARN
414 421 handlers =
415 422 qualname = sqlalchemy.engine
416 423 # For coloring based on log level and pretty printing of SQL:
417 424 # level = INFO
418 425 # handlers = console_color_sql
419 426 # propagate = 0
420 427
421 428 [logger_whoosh_indexer]
422 429 #level = WARN
423 430 level = DEBUG
424 431 handlers =
425 432 qualname = whoosh_indexer
426 433
427 434 [logger_werkzeug]
428 435 level = WARN
429 436 handlers =
430 437 qualname = werkzeug
431 438
432 439 [logger_backlash]
433 440 level = WARN
434 441 handlers =
435 442 qualname = backlash
436 443
437 444 ##############
438 445 ## HANDLERS ##
439 446 ##############
440 447
441 448 [handler_console]
442 449 class = StreamHandler
443 450 args = (sys.stderr,)
444 451 formatter = generic
445 452
446 453 [handler_console_color]
447 454 # ANSI color coding based on log level
448 455 class = StreamHandler
449 456 args = (sys.stderr,)
450 457 formatter = color_formatter
451 458
452 459 [handler_console_color_sql]
453 460 # ANSI color coding and pretty printing of SQL statements
454 461 class = StreamHandler
455 462 args = (sys.stderr,)
456 463 formatter = color_formatter_sql
457 464
458 465 [handler_null]
459 466 class = NullHandler
460 467 args = ()
461 468
462 469 ################
463 470 ## FORMATTERS ##
464 471 ################
465 472
466 473 [formatter_generic]
467 474 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
468 475 datefmt = %Y-%m-%d %H:%M:%S
469 476
470 477 [formatter_color_formatter]
471 478 class = kallithea.lib.colored_formatter.ColorFormatter
472 479 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
473 480 datefmt = %Y-%m-%d %H:%M:%S
474 481
475 482 [formatter_color_formatter_sql]
476 483 class = kallithea.lib.colored_formatter.ColorFormatterSql
477 484 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
478 485 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,638 +1,651 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 """
16 16 kallithea.lib.base
17 17 ~~~~~~~~~~~~~~~~~~
18 18
19 19 The base Controller API
20 20 Provides the BaseController class for subclassing. And usage in different
21 21 controllers
22 22
23 23 This file was forked by the Kallithea project in July 2014.
24 24 Original author and date, and relevant copyright and licensing information is below:
25 25 :created_on: Oct 06, 2010
26 26 :author: marcink
27 27 :copyright: (c) 2013 RhodeCode GmbH, and others.
28 28 :license: GPLv3, see LICENSE.md for more details.
29 29 """
30 30
31 31 import datetime
32 32 import decorator
33 33 import logging
34 34 import time
35 35 import traceback
36 36 import warnings
37 37
38 38 import webob.exc
39 39 import paste.httpexceptions
40 40 import paste.auth.basic
41 41 import paste.httpheaders
42 42 from webhelpers.pylonslib import secure_form
43 43
44 44 from tg import config, tmpl_context as c, request, response, session, render_template
45 45 from tg import TGController
46 46 from tg.i18n import ugettext as _
47 47
48 48 from kallithea import __version__, BACKENDS
49 49
50 50 from kallithea.config.routing import url
51 51 from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict, \
52 52 safe_str, safe_int, set_hook_environment
53 53 from kallithea.lib import auth_modules
54 54 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware
55 55 from kallithea.lib.compat import json
56 56 from kallithea.lib.utils import get_repo_slug, is_valid_repo
57 57 from kallithea.lib.exceptions import UserCreationError
58 58 from kallithea.lib.vcs.exceptions import RepositoryError, EmptyRepositoryError, ChangesetDoesNotExistError
59 59 from kallithea.model import meta
60 60
61 61 from kallithea.model.db import PullRequest, Repository, User, Setting
62 62 from kallithea.model.scm import ScmModel
63 63
64 64 log = logging.getLogger(__name__)
65 65
66 66
67 67 def render(template_path):
68 68 return render_template({'url': url}, 'mako', template_path)
69 69
70 70
71 71 def _filter_proxy(ip):
72 72 """
73 73 HEADERS can have multiple ips inside the left-most being the original
74 74 client, and each successive proxy that passed the request adding the IP
75 75 address where it received the request from.
76 76
77 77 :param ip:
78 78 """
79 79 if ',' in ip:
80 80 _ips = ip.split(',')
81 81 _first_ip = _ips[0].strip()
82 82 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
83 83 return _first_ip
84 84 return ip
85 85
86 86
87 87 def _get_ip_addr(environ):
88 88 proxy_key = 'HTTP_X_REAL_IP'
89 89 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
90 90 def_key = 'REMOTE_ADDR'
91 91
92 92 ip = environ.get(proxy_key)
93 93 if ip:
94 94 return _filter_proxy(ip)
95 95
96 96 ip = environ.get(proxy_key2)
97 97 if ip:
98 98 return _filter_proxy(ip)
99 99
100 100 ip = environ.get(def_key, '0.0.0.0')
101 101 return _filter_proxy(ip)
102 102
103 103
104 104 def _get_access_path(environ):
105 105 """Return PATH_INFO from environ ... using tg.original_request if available."""
106 106 org_req = environ.get('tg.original_request')
107 107 if org_req is not None:
108 108 environ = org_req.environ
109 109 return environ.get('PATH_INFO')
110 110
111 111
112 112 def log_in_user(user, remember, is_external_auth, ip_addr):
113 113 """
114 114 Log a `User` in and update session and cookies. If `remember` is True,
115 115 the session cookie is set to expire in a year; otherwise, it expires at
116 116 the end of the browser session.
117 117
118 118 Returns populated `AuthUser` object.
119 119 """
120 120 # It should not be possible to explicitly log in as the default user.
121 121 assert not user.is_default_user, user
122 122
123 123 auth_user = AuthUser.make(dbuser=user, is_external_auth=is_external_auth, ip_addr=ip_addr)
124 124 if auth_user is None:
125 125 return None
126 126
127 127 user.update_lastlogin()
128 128 meta.Session().commit()
129 129
130 130 # Start new session to prevent session fixation attacks.
131 131 session.invalidate()
132 132 session['authuser'] = cookie = auth_user.to_cookie()
133 133
134 134 # If they want to be remembered, update the cookie.
135 135 # NOTE: Assumes that beaker defaults to browser session cookie.
136 136 if remember:
137 137 t = datetime.datetime.now() + datetime.timedelta(days=365)
138 138 session._set_cookie_expires(t)
139 139
140 140 session.save()
141 141
142 142 log.info('user %s is now authenticated and stored in '
143 143 'session, session attrs %s', user.username, cookie)
144 144
145 145 # dumps session attrs back to cookie
146 146 session._update_cookie_out()
147 147
148 148 return auth_user
149 149
150 150
151 151 class BasicAuth(paste.auth.basic.AuthBasicAuthenticator):
152 152
153 153 def __init__(self, realm, authfunc, auth_http_code=None):
154 154 self.realm = realm
155 155 self.authfunc = authfunc
156 156 self._rc_auth_http_code = auth_http_code
157 157
158 158 def build_authentication(self, environ):
159 159 head = paste.httpheaders.WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
160 160 # Consume the whole body before sending a response
161 161 try:
162 162 request_body_size = int(environ.get('CONTENT_LENGTH', 0))
163 163 except (ValueError):
164 164 request_body_size = 0
165 165 environ['wsgi.input'].read(request_body_size)
166 166 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
167 167 # return 403 if alternative http return code is specified in
168 168 # Kallithea config
169 169 return paste.httpexceptions.HTTPForbidden(headers=head)
170 170 return paste.httpexceptions.HTTPUnauthorized(headers=head)
171 171
172 172 def authenticate(self, environ):
173 173 authorization = paste.httpheaders.AUTHORIZATION(environ)
174 174 if not authorization:
175 175 return self.build_authentication(environ)
176 176 (authmeth, auth) = authorization.split(' ', 1)
177 177 if 'basic' != authmeth.lower():
178 178 return self.build_authentication(environ)
179 179 auth = auth.strip().decode('base64')
180 180 _parts = auth.split(':', 1)
181 181 if len(_parts) == 2:
182 182 username, password = _parts
183 183 if self.authfunc(username, password, environ) is not None:
184 184 return username
185 185 return self.build_authentication(environ)
186 186
187 187 __call__ = authenticate
188 188
189 189
190 190 class BaseVCSController(object):
191 191 """Base controller for handling Mercurial/Git protocol requests
192 192 (coming from a VCS client, and not a browser).
193 193 """
194 194
195 195 scm_alias = None # 'hg' / 'git'
196 196
197 197 def __init__(self, application, config):
198 198 self.application = application
199 199 self.config = config
200 200 # base path of repo locations
201 201 self.basepath = self.config['base_path']
202 202 # authenticate this VCS request using the authentication modules
203 203 self.authenticate = BasicAuth('', auth_modules.authenticate,
204 204 config.get('auth_ret_code'))
205 205
206 206 @classmethod
207 207 def parse_request(cls, environ):
208 208 """If request is parsed as a request for this VCS, return a namespace with the parsed request.
209 209 If the request is unknown, return None.
210 210 """
211 211 raise NotImplementedError()
212 212
213 213 def _authorize(self, environ, action, repo_name, ip_addr):
214 214 """Authenticate and authorize user.
215 215
216 216 Since we're dealing with a VCS client and not a browser, we only
217 217 support HTTP basic authentication, either directly via raw header
218 218 inspection, or by using container authentication to delegate the
219 219 authentication to the web server.
220 220
221 221 Returns (user, None) on successful authentication and authorization.
222 222 Returns (None, wsgi_app) to send the wsgi_app response to the client.
223 223 """
224 224 # Use anonymous access if allowed for action on repo.
225 225 default_user = User.get_default_user(cache=True)
226 226 default_authuser = AuthUser.make(dbuser=default_user, ip_addr=ip_addr)
227 227 if default_authuser is None:
228 228 log.debug('No anonymous access at all') # move on to proper user auth
229 229 else:
230 230 if self._check_permission(action, default_authuser, repo_name):
231 231 return default_authuser, None
232 232 log.debug('Not authorized to access this repository as anonymous user')
233 233
234 234 username = None
235 235 #==============================================================
236 236 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
237 237 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
238 238 #==============================================================
239 239
240 240 # try to auth based on environ, container auth methods
241 241 log.debug('Running PRE-AUTH for container based authentication')
242 242 pre_auth = auth_modules.authenticate('', '', environ)
243 243 if pre_auth is not None and pre_auth.get('username'):
244 244 username = pre_auth['username']
245 245 log.debug('PRE-AUTH got %s as username', username)
246 246
247 247 # If not authenticated by the container, running basic auth
248 248 if not username:
249 249 self.authenticate.realm = safe_str(self.config['realm'])
250 250 result = self.authenticate(environ)
251 251 if isinstance(result, str):
252 252 paste.httpheaders.AUTH_TYPE.update(environ, 'basic')
253 253 paste.httpheaders.REMOTE_USER.update(environ, result)
254 254 username = result
255 255 else:
256 256 return None, result.wsgi_application
257 257
258 258 #==============================================================
259 259 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
260 260 #==============================================================
261 261 try:
262 262 user = User.get_by_username_or_email(username)
263 263 except Exception:
264 264 log.error(traceback.format_exc())
265 265 return None, webob.exc.HTTPInternalServerError()
266 266
267 267 authuser = AuthUser.make(dbuser=user, ip_addr=ip_addr)
268 268 if authuser is None:
269 269 return None, webob.exc.HTTPForbidden()
270 270 if not self._check_permission(action, authuser, repo_name):
271 271 return None, webob.exc.HTTPForbidden()
272 272
273 273 return user, None
274 274
275 275 def _handle_request(self, environ, start_response):
276 276 raise NotImplementedError()
277 277
278 278 def _check_permission(self, action, authuser, repo_name):
279 279 """
280 280 Checks permissions using action (push/pull) user and repository
281 281 name
282 282
283 283 :param action: 'push' or 'pull' action
284 284 :param user: `User` instance
285 285 :param repo_name: repository name
286 286 """
287 287 if action == 'push':
288 288 if not HasPermissionAnyMiddleware('repository.write',
289 289 'repository.admin')(authuser,
290 290 repo_name):
291 291 return False
292 292
293 293 else:
294 294 #any other action need at least read permission
295 295 if not HasPermissionAnyMiddleware('repository.read',
296 296 'repository.write',
297 297 'repository.admin')(authuser,
298 298 repo_name):
299 299 return False
300 300
301 301 return True
302 302
303 303 def _get_ip_addr(self, environ):
304 304 return _get_ip_addr(environ)
305 305
306 306 def __call__(self, environ, start_response):
307 307 start = time.time()
308 308 try:
309 309 # try parsing a request for this VCS - if it fails, call the wrapped app
310 310 parsed_request = self.parse_request(environ)
311 311 if parsed_request is None:
312 312 return self.application(environ, start_response)
313 313
314 314 # skip passing error to error controller
315 315 environ['pylons.status_code_redirect'] = True
316 316
317 317 # quick check if repo exists...
318 318 if not is_valid_repo(parsed_request.repo_name, self.basepath, self.scm_alias):
319 319 raise webob.exc.HTTPNotFound()
320 320
321 321 if parsed_request.action is None:
322 322 # Note: the client doesn't get the helpful error message
323 323 raise webob.exc.HTTPBadRequest('Unable to detect pull/push action for %r! Are you using a nonstandard command or client?' % parsed_request.repo_name)
324 324
325 325 #======================================================================
326 326 # CHECK PERMISSIONS
327 327 #======================================================================
328 328 ip_addr = self._get_ip_addr(environ)
329 329 user, response_app = self._authorize(environ, parsed_request.action, parsed_request.repo_name, ip_addr)
330 330 if response_app is not None:
331 331 return response_app(environ, start_response)
332 332
333 333 #======================================================================
334 334 # REQUEST HANDLING
335 335 #======================================================================
336 336 set_hook_environment(user.username, ip_addr,
337 337 parsed_request.repo_name, self.scm_alias, parsed_request.action)
338 338
339 339 try:
340 340 log.info('%s action on %s repo "%s" by "%s" from %s',
341 341 parsed_request.action, self.scm_alias, parsed_request.repo_name, safe_str(user.username), ip_addr)
342 342 app = self._make_app(parsed_request)
343 343 return app(environ, start_response)
344 344 except Exception:
345 345 log.error(traceback.format_exc())
346 346 raise webob.exc.HTTPInternalServerError()
347 347
348 348 except webob.exc.HTTPException as e:
349 349 return e(environ, start_response)
350 350 finally:
351 351 log_ = logging.getLogger('kallithea.' + self.__class__.__name__)
352 352 log_.debug('Request time: %.3fs', time.time() - start)
353 353 meta.Session.remove()
354 354
355 355
356 356 class BaseController(TGController):
357 357
358 358 def _before(self, *args, **kwargs):
359 359 """
360 360 _before is called before controller methods and after __call__
361 361 """
362 362 if request.needs_csrf_check:
363 363 # CSRF protection: Whenever a request has ambient authority (whether
364 364 # through a session cookie or its origin IP address), it must include
365 365 # the correct token, unless the HTTP method is GET or HEAD (and thus
366 366 # guaranteed to be side effect free. In practice, the only situation
367 367 # where we allow side effects without ambient authority is when the
368 368 # authority comes from an API key; and that is handled above.
369 369 token = request.POST.get(secure_form.token_key)
370 370 if not token or token != secure_form.authentication_token():
371 371 log.error('CSRF check failed')
372 372 raise webob.exc.HTTPForbidden()
373 373
374 374 c.kallithea_version = __version__
375 375 rc_config = Setting.get_app_settings()
376 376
377 377 # Visual options
378 378 c.visual = AttributeDict({})
379 379
380 380 ## DB stored
381 381 c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
382 382 c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon'))
383 383 c.visual.stylify_metalabels = str2bool(rc_config.get('stylify_metalabels'))
384 384 c.visual.page_size = safe_int(rc_config.get('dashboard_items', 100))
385 385 c.visual.admin_grid_items = safe_int(rc_config.get('admin_grid_items', 100))
386 386 c.visual.repository_fields = str2bool(rc_config.get('repository_fields'))
387 387 c.visual.show_version = str2bool(rc_config.get('show_version'))
388 388 c.visual.use_gravatar = str2bool(rc_config.get('use_gravatar'))
389 389 c.visual.gravatar_url = rc_config.get('gravatar_url')
390 390
391 391 c.ga_code = rc_config.get('ga_code')
392 392 # TODO: replace undocumented backwards compatibility hack with db upgrade and rename ga_code
393 393 if c.ga_code and '<' not in c.ga_code:
394 394 c.ga_code = '''<script type="text/javascript">
395 395 var _gaq = _gaq || [];
396 396 _gaq.push(['_setAccount', '%s']);
397 397 _gaq.push(['_trackPageview']);
398 398
399 399 (function() {
400 400 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
401 401 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
402 402 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
403 403 })();
404 404 </script>''' % c.ga_code
405 405 c.site_name = rc_config.get('title')
406 406 c.clone_uri_tmpl = rc_config.get('clone_uri_tmpl') or Repository.DEFAULT_CLONE_URI
407 407
408 408 ## INI stored
409 409 c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True))
410 410 c.visual.allow_custom_hooks_settings = str2bool(config.get('allow_custom_hooks_settings', True))
411 c.ssh_enabled = str2bool(config.get('ssh_enabled', False))
411 412
412 413 c.instance_id = config.get('instance_id')
413 414 c.issues_url = config.get('bugtracker', url('issues_url'))
414 415 # END CONFIG VARS
415 416
416 417 c.repo_name = get_repo_slug(request) # can be empty
417 418 c.backends = BACKENDS.keys()
418 419
419 420 self.cut_off_limit = safe_int(config.get('cut_off_limit'))
420 421
421 422 c.my_pr_count = PullRequest.query(reviewer_id=request.authuser.user_id, include_closed=False).count()
422 423
423 424 self.scm_model = ScmModel()
424 425
425 426 @staticmethod
426 427 def _determine_auth_user(session_authuser, ip_addr):
427 428 """
428 429 Create an `AuthUser` object given the API key/bearer token
429 430 (if any) and the value of the authuser session cookie.
430 431 Returns None if no valid user is found (like not active or no access for IP).
431 432 """
432 433
433 434 # Authenticate by session cookie
434 435 # In ancient login sessions, 'authuser' may not be a dict.
435 436 # In that case, the user will have to log in again.
436 437 # v0.3 and earlier included an 'is_authenticated' key; if present,
437 438 # this must be True.
438 439 if isinstance(session_authuser, dict) and session_authuser.get('is_authenticated', True):
439 440 return AuthUser.from_cookie(session_authuser, ip_addr=ip_addr)
440 441
441 442 # Authenticate by auth_container plugin (if enabled)
442 443 if any(
443 444 plugin.is_container_auth
444 445 for plugin in auth_modules.get_auth_plugins()
445 446 ):
446 447 try:
447 448 user_info = auth_modules.authenticate('', '', request.environ)
448 449 except UserCreationError as e:
449 450 from kallithea.lib import helpers as h
450 451 h.flash(e, 'error', logf=log.error)
451 452 else:
452 453 if user_info is not None:
453 454 username = user_info['username']
454 455 user = User.get_by_username(username, case_insensitive=True)
455 456 return log_in_user(user, remember=False, is_external_auth=True, ip_addr=ip_addr)
456 457
457 458 # User is default user (if active) or anonymous
458 459 default_user = User.get_default_user(cache=True)
459 460 authuser = AuthUser.make(dbuser=default_user, ip_addr=ip_addr)
460 461 if authuser is None: # fall back to anonymous
461 462 authuser = AuthUser(dbuser=default_user) # TODO: somehow use .make?
462 463 return authuser
463 464
464 465 @staticmethod
465 466 def _basic_security_checks():
466 467 """Perform basic security/sanity checks before processing the request."""
467 468
468 469 # Only allow the following HTTP request methods.
469 470 if request.method not in ['GET', 'HEAD', 'POST']:
470 471 raise webob.exc.HTTPMethodNotAllowed()
471 472
472 473 # Also verify the _method override - no longer allowed.
473 474 if request.params.get('_method') is None:
474 475 pass # no override, no problem
475 476 else:
476 477 raise webob.exc.HTTPMethodNotAllowed()
477 478
478 479 # Make sure CSRF token never appears in the URL. If so, invalidate it.
479 480 if secure_form.token_key in request.GET:
480 481 log.error('CSRF key leak detected')
481 482 session.pop(secure_form.token_key, None)
482 483 session.save()
483 484 from kallithea.lib import helpers as h
484 485 h.flash(_('CSRF token leak has been detected - all form tokens have been expired'),
485 486 category='error')
486 487
487 488 # WebOb already ignores request payload parameters for anything other
488 489 # than POST/PUT, but double-check since other Kallithea code relies on
489 490 # this assumption.
490 491 if request.method not in ['POST', 'PUT'] and request.POST:
491 492 log.error('%r request with payload parameters; WebOb should have stopped this', request.method)
492 493 raise webob.exc.HTTPBadRequest()
493 494
494 495 def __call__(self, environ, context):
495 496 try:
496 497 ip_addr = _get_ip_addr(environ)
497 498 self._basic_security_checks()
498 499
499 500 api_key = request.GET.get('api_key')
500 501 try:
501 502 # Request.authorization may raise ValueError on invalid input
502 503 type, params = request.authorization
503 504 except (ValueError, TypeError):
504 505 pass
505 506 else:
506 507 if type.lower() == 'bearer':
507 508 api_key = params # bearer token is an api key too
508 509
509 510 if api_key is None:
510 511 authuser = self._determine_auth_user(
511 512 session.get('authuser'),
512 513 ip_addr=ip_addr,
513 514 )
514 515 needs_csrf_check = request.method not in ['GET', 'HEAD']
515 516
516 517 else:
517 518 dbuser = User.get_by_api_key(api_key)
518 519 if dbuser is None:
519 520 log.info('No db user found for authentication with API key ****%s from %s',
520 521 api_key[-4:], ip_addr)
521 522 authuser = AuthUser.make(dbuser=dbuser, is_external_auth=True, ip_addr=ip_addr)
522 523 needs_csrf_check = False # API key provides CSRF protection
523 524
524 525 if authuser is None:
525 526 log.info('No valid user found')
526 527 raise webob.exc.HTTPForbidden()
527 528
528 529 # set globals for auth user
529 530 request.authuser = authuser
530 531 request.ip_addr = ip_addr
531 532 request.needs_csrf_check = needs_csrf_check
532 533
533 534 log.info('IP: %s User: %s accessed %s',
534 535 request.ip_addr, request.authuser,
535 536 safe_unicode(_get_access_path(environ)),
536 537 )
537 538 return super(BaseController, self).__call__(environ, context)
538 539 except webob.exc.HTTPException as e:
539 540 return e
540 541
541 542
542 543 class BaseRepoController(BaseController):
543 544 """
544 545 Base class for controllers responsible for loading all needed data for
545 546 repository loaded items are
546 547
547 548 c.db_repo_scm_instance: instance of scm repository
548 549 c.db_repo: instance of db
549 550 c.repository_followers: number of followers
550 551 c.repository_forks: number of forks
551 552 c.repository_following: weather the current user is following the current repo
552 553 """
553 554
554 555 def _before(self, *args, **kwargs):
555 556 super(BaseRepoController, self)._before(*args, **kwargs)
556 557 if c.repo_name: # extracted from routes
557 558 _dbr = Repository.get_by_repo_name(c.repo_name)
558 559 if not _dbr:
559 560 return
560 561
561 562 log.debug('Found repository in database %s with state `%s`',
562 563 safe_unicode(_dbr), safe_unicode(_dbr.repo_state))
563 564 route = getattr(request.environ.get('routes.route'), 'name', '')
564 565
565 566 # allow to delete repos that are somehow damages in filesystem
566 567 if route in ['delete_repo']:
567 568 return
568 569
569 570 if _dbr.repo_state in [Repository.STATE_PENDING]:
570 571 if route in ['repo_creating_home']:
571 572 return
572 573 check_url = url('repo_creating_home', repo_name=c.repo_name)
573 574 raise webob.exc.HTTPFound(location=check_url)
574 575
575 576 dbr = c.db_repo = _dbr
576 577 c.db_repo_scm_instance = c.db_repo.scm_instance
577 578 if c.db_repo_scm_instance is None:
578 579 log.error('%s this repository is present in database but it '
579 580 'cannot be created as an scm instance', c.repo_name)
580 581 from kallithea.lib import helpers as h
581 582 h.flash(_('Repository not found in the filesystem'),
582 583 category='error')
583 584 raise webob.exc.HTTPNotFound()
584 585
585 586 # some globals counter for menu
586 587 c.repository_followers = self.scm_model.get_followers(dbr)
587 588 c.repository_forks = self.scm_model.get_forks(dbr)
588 589 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
589 590 c.repository_following = self.scm_model.is_following_repo(
590 591 c.repo_name, request.authuser.user_id)
591 592
592 593 @staticmethod
593 594 def _get_ref_rev(repo, ref_type, ref_name, returnempty=False):
594 595 """
595 596 Safe way to get changeset. If error occurs show error.
596 597 """
597 598 from kallithea.lib import helpers as h
598 599 try:
599 600 return repo.scm_instance.get_ref_revision(ref_type, ref_name)
600 601 except EmptyRepositoryError as e:
601 602 if returnempty:
602 603 return repo.scm_instance.EMPTY_CHANGESET
603 604 h.flash(_('There are no changesets yet'), category='error')
604 605 raise webob.exc.HTTPNotFound()
605 606 except ChangesetDoesNotExistError as e:
606 607 h.flash(_('Changeset for %s %s not found in %s') %
607 608 (ref_type, ref_name, repo.repo_name),
608 609 category='error')
609 610 raise webob.exc.HTTPNotFound()
610 611 except RepositoryError as e:
611 612 log.error(traceback.format_exc())
612 613 h.flash(safe_str(e), category='error')
613 614 raise webob.exc.HTTPBadRequest()
614 615
615 616
616 617 @decorator.decorator
617 618 def jsonify(func, *args, **kwargs):
618 619 """Action decorator that formats output for JSON
619 620
620 621 Given a function that will return content, this decorator will turn
621 622 the result into JSON, with a content-type of 'application/json' and
622 623 output it.
623 624 """
624 625 response.headers['Content-Type'] = 'application/json; charset=utf-8'
625 626 data = func(*args, **kwargs)
626 627 if isinstance(data, (list, tuple)):
627 628 # A JSON list response is syntactically valid JavaScript and can be
628 629 # loaded and executed as JavaScript by a malicious third-party site
629 630 # using <script>, which can lead to cross-site data leaks.
630 631 # JSON responses should therefore be scalars or objects (i.e. Python
631 632 # dicts), because a JSON object is a syntax error if intepreted as JS.
632 633 msg = "JSON responses with Array envelopes are susceptible to " \
633 634 "cross-site data leak attacks, see " \
634 635 "https://web.archive.org/web/20120519231904/http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
635 636 warnings.warn(msg, Warning, 2)
636 637 log.warning(msg)
637 638 log.debug("Returning JSON wrapped action output")
638 639 return json.dumps(data, encoding='utf-8')
640
641 @decorator.decorator
642 def IfSshEnabled(func, *args, **kwargs):
643 """Decorator for functions that can only be called if SSH access is enabled.
644
645 If SSH access is disabled in the configuration file, HTTPNotFound is raised.
646 """
647 if not c.ssh_enabled:
648 from kallithea.lib import helpers as h
649 h.flash(_("SSH access is disabled."), category='warning')
650 raise webob.exc.HTTPNotFound()
651 return func(*args, **kwargs)
@@ -1,651 +1,658 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%text>################################################################################</%text>
3 3 <%text>################################################################################</%text>
4 4 # Kallithea - config file generated with kallithea-config #
5 5 # #
6 6 # The %(here)s variable will be replaced with the parent directory of this file#
7 7 <%text>################################################################################</%text>
8 8 <%text>################################################################################</%text>
9 9
10 10 [DEFAULT]
11 11
12 12 <%text>################################################################################</%text>
13 13 <%text>## Email settings ##</%text>
14 14 <%text>## ##</%text>
15 15 <%text>## Refer to the documentation ("Email settings") for more details. ##</%text>
16 16 <%text>## ##</%text>
17 17 <%text>## It is recommended to use a valid sender address that passes access ##</%text>
18 18 <%text>## validation and spam filtering in mail servers. ##</%text>
19 19 <%text>################################################################################</%text>
20 20
21 21 <%text>## 'From' header for application emails. You can optionally add a name.</%text>
22 22 <%text>## Default:</%text>
23 23 #app_email_from = Kallithea
24 24 <%text>## Examples:</%text>
25 25 #app_email_from = Kallithea <kallithea-noreply@example.com>
26 26 #app_email_from = kallithea-noreply@example.com
27 27
28 28 <%text>## Subject prefix for application emails.</%text>
29 29 <%text>## A space between this prefix and the real subject is automatically added.</%text>
30 30 <%text>## Default:</%text>
31 31 #email_prefix =
32 32 <%text>## Example:</%text>
33 33 #email_prefix = [Kallithea]
34 34
35 35 <%text>## Recipients for error emails and fallback recipients of application mails.</%text>
36 36 <%text>## Multiple addresses can be specified, comma-separated.</%text>
37 37 <%text>## Only addresses are allowed, do not add any name part.</%text>
38 38 <%text>## Default:</%text>
39 39 #email_to =
40 40 <%text>## Examples:</%text>
41 41 #email_to = admin@example.com
42 42 #email_to = admin@example.com,another_admin@example.com
43 43 email_to =
44 44
45 45 <%text>## 'From' header for error emails. You can optionally add a name.</%text>
46 46 <%text>## Default: (none)</%text>
47 47 <%text>## Examples:</%text>
48 48 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
49 49 #error_email_from = kallithea_errors@example.com
50 50 error_email_from =
51 51
52 52 <%text>## SMTP server settings</%text>
53 53 <%text>## If specifying credentials, make sure to use secure connections.</%text>
54 54 <%text>## Default: Send unencrypted unauthenticated mails to the specified smtp_server.</%text>
55 55 <%text>## For "SSL", use smtp_use_ssl = true and smtp_port = 465.</%text>
56 56 <%text>## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587.</%text>
57 57 smtp_server =
58 58 #smtp_username =
59 59 #smtp_password =
60 60 smtp_port =
61 61 #smtp_use_ssl = false
62 62 #smtp_use_tls = false
63 63
64 64 %if http_server != 'uwsgi':
65 65 <%text>## Entry point for 'gearbox serve'</%text>
66 66 [server:main]
67 67 host = ${host}
68 68 port = ${port}
69 69
70 70 %if http_server == 'gearbox':
71 71 <%text>## Gearbox default web server ##</%text>
72 72 use = egg:gearbox#wsgiref
73 73 <%text>## nr of worker threads to spawn</%text>
74 74 threadpool_workers = 1
75 75 <%text>## max request before thread respawn</%text>
76 76 threadpool_max_requests = 100
77 77 <%text>## option to use threads of process</%text>
78 78 use_threadpool = true
79 79
80 80 %elif http_server == 'gevent':
81 81 <%text>## Gearbox gevent web server ##</%text>
82 82 use = egg:gearbox#gevent
83 83
84 84 %elif http_server == 'waitress':
85 85 <%text>## WAITRESS ##</%text>
86 86 use = egg:waitress#main
87 87 <%text>## number of worker threads</%text>
88 88 threads = 1
89 89 <%text>## MAX BODY SIZE 100GB</%text>
90 90 max_request_body_size = 107374182400
91 91 <%text>## use poll instead of select, fixes fd limits, may not work on old</%text>
92 92 <%text>## windows systems.</%text>
93 93 #asyncore_use_poll = True
94 94
95 95 %elif http_server == 'gunicorn':
96 96 <%text>## GUNICORN ##</%text>
97 97 use = egg:gunicorn#main
98 98 <%text>## number of process workers. You must set `instance_id = *` when this option</%text>
99 99 <%text>## is set to more than one worker</%text>
100 100 workers = 4
101 101 <%text>## process name</%text>
102 102 proc_name = kallithea
103 103 <%text>## type of worker class, one of sync, eventlet, gevent, tornado</%text>
104 104 <%text>## recommended for bigger setup is using of of other than sync one</%text>
105 105 worker_class = sync
106 106 max_requests = 1000
107 107 <%text>## amount of time a worker can handle request before it gets killed and</%text>
108 108 <%text>## restarted</%text>
109 109 timeout = 3600
110 110
111 111 %endif
112 112 %else:
113 113 <%text>## UWSGI ##</%text>
114 114 <%text>## run with uwsgi --ini-paste-logged <inifile.ini></%text>
115 115 [uwsgi]
116 116 socket = /tmp/uwsgi.sock
117 117 master = true
118 118 http = ${host}:${port}
119 119
120 120 <%text>## set as daemon and redirect all output to file</%text>
121 121 #daemonize = ./uwsgi_kallithea.log
122 122
123 123 <%text>## master process PID</%text>
124 124 pidfile = ./uwsgi_kallithea.pid
125 125
126 126 <%text>## stats server with workers statistics, use uwsgitop</%text>
127 127 <%text>## for monitoring, `uwsgitop 127.0.0.1:1717`</%text>
128 128 stats = 127.0.0.1:1717
129 129 memory-report = true
130 130
131 131 <%text>## log 5XX errors</%text>
132 132 log-5xx = true
133 133
134 134 <%text>## Set the socket listen queue size.</%text>
135 135 listen = 128
136 136
137 137 <%text>## Gracefully Reload workers after the specified amount of managed requests</%text>
138 138 <%text>## (avoid memory leaks).</%text>
139 139 max-requests = 1000
140 140
141 141 <%text>## enable large buffers</%text>
142 142 buffer-size = 65535
143 143
144 144 <%text>## socket and http timeouts ##</%text>
145 145 http-timeout = 3600
146 146 socket-timeout = 3600
147 147
148 148 <%text>## Log requests slower than the specified number of milliseconds.</%text>
149 149 log-slow = 10
150 150
151 151 <%text>## Exit if no app can be loaded.</%text>
152 152 need-app = true
153 153
154 154 <%text>## Set lazy mode (load apps in workers instead of master).</%text>
155 155 lazy = true
156 156
157 157 <%text>## scaling ##</%text>
158 158 <%text>## set cheaper algorithm to use, if not set default will be used</%text>
159 159 cheaper-algo = spare
160 160
161 161 <%text>## minimum number of workers to keep at all times</%text>
162 162 cheaper = 1
163 163
164 164 <%text>## number of workers to spawn at startup</%text>
165 165 cheaper-initial = 1
166 166
167 167 <%text>## maximum number of workers that can be spawned</%text>
168 168 workers = 4
169 169
170 170 <%text>## how many workers should be spawned at a time</%text>
171 171 cheaper-step = 1
172 172
173 173 %endif
174 174 <%text>## middleware for hosting the WSGI application under a URL prefix</%text>
175 175 #[filter:proxy-prefix]
176 176 #use = egg:PasteDeploy#prefix
177 177 #prefix = /<your-prefix>
178 178
179 179 [app:main]
180 180 use = egg:kallithea
181 181 <%text>## enable proxy prefix middleware</%text>
182 182 #filter-with = proxy-prefix
183 183
184 184 full_stack = true
185 185 static_files = true
186 186
187 187 <%text>## Internationalization (see setup documentation for details)</%text>
188 188 <%text>## By default, the language requested by the browser is used if available.</%text>
189 189 #i18n.enabled = false
190 190 <%text>## Fallback language, empty for English (valid values are the names of subdirectories in kallithea/i18n):</%text>
191 191 i18n.lang =
192 192
193 193 cache_dir = %(here)s/data
194 194 index_dir = %(here)s/data/index
195 195
196 196 <%text>## uncomment and set this path to use archive download cache</%text>
197 197 archive_cache_dir = %(here)s/tarballcache
198 198
199 199 <%text>## change this to unique ID for security</%text>
200 200 app_instance_uuid = ${uuid()}
201 201
202 202 <%text>## cut off limit for large diffs (size in bytes)</%text>
203 203 cut_off_limit = 256000
204 204
205 205 <%text>## force https in Kallithea, fixes https redirects, assumes it's always https</%text>
206 206 force_https = false
207 207
208 208 <%text>## use Strict-Transport-Security headers</%text>
209 209 use_htsts = false
210 210
211 211 <%text>## number of commits stats will parse on each iteration</%text>
212 212 commit_parse_limit = 25
213 213
214 214 <%text>## Path to Python executable to be used for git hooks.</%text>
215 215 <%text>## This value will be written inside the git hook scripts as the text</%text>
216 216 <%text>## after '#!' (shebang). When empty or not defined, the value of</%text>
217 217 <%text>## 'sys.executable' at the time of installation of the git hooks is</%text>
218 218 <%text>## used, which is correct in many cases but for example not when using uwsgi.</%text>
219 219 <%text>## If you change this setting, you should reinstall the Git hooks via</%text>
220 220 <%text>## Admin > Settings > Remap and Rescan.</%text>
221 221 # git_hook_interpreter = /srv/kallithea/venv/bin/python2
222 222 %if git_hook_interpreter:
223 223 git_hook_interpreter = ${git_hook_interpreter}
224 224 %endif
225 225
226 226 <%text>## path to git executable</%text>
227 227 git_path = git
228 228
229 229 <%text>## git rev filter option, --all is the default filter, if you need to</%text>
230 230 <%text>## hide all refs in changelog switch this to --branches --tags</%text>
231 231 #git_rev_filter = --branches --tags
232 232
233 233 <%text>## RSS feed options</%text>
234 234 rss_cut_off_limit = 256000
235 235 rss_items_per_page = 10
236 236 rss_include_diff = false
237 237
238 238 <%text>## options for showing and identifying changesets</%text>
239 239 show_sha_length = 12
240 240 show_revision_number = false
241 241
242 242 <%text>## Canonical URL to use when creating full URLs in UI and texts.</%text>
243 243 <%text>## Useful when the site is available under different names or protocols.</%text>
244 244 <%text>## Defaults to what is provided in the WSGI environment.</%text>
245 245 #canonical_url = https://kallithea.example.com/repos
246 246
247 247 <%text>## gist URL alias, used to create nicer urls for gist. This should be an</%text>
248 248 <%text>## url that does rewrites to _admin/gists/<gistid>.</%text>
249 249 <%text>## example: http://gist.example.com/{gistid}. Empty means use the internal</%text>
250 250 <%text>## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid></%text>
251 251 gist_alias_url =
252 252
253 253 <%text>## default encoding used to convert from and to unicode</%text>
254 254 <%text>## can be also a comma separated list of encoding in case of mixed encodings</%text>
255 255 default_encoding = utf-8
256 256
257 257 <%text>## Set Mercurial encoding, similar to setting HGENCODING before launching Kallithea</%text>
258 258 hgencoding = utf-8
259 259
260 260 <%text>## issue tracker for Kallithea (leave blank to disable, absent for default)</%text>
261 261 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
262 262
263 263 <%text>## issue tracking mapping for commit messages, comments, PR descriptions, ...</%text>
264 264 <%text>## Refer to the documentation ("Integration with issue trackers") for more details.</%text>
265 265
266 266 <%text>## regular expression to match issue references</%text>
267 267 <%text>## This pattern may/should contain parenthesized groups, that can</%text>
268 268 <%text>## be referred to in issue_server_link or issue_sub using Python backreferences</%text>
269 269 <%text>## (e.g. \1, \2, ...). You can also create named groups with '(?P<groupname>)'.</%text>
270 270 <%text>## To require mandatory whitespace before the issue pattern, use:</%text>
271 271 <%text>## (?:^|(?<=\s)) before the actual pattern, and for mandatory whitespace</%text>
272 272 <%text>## behind the issue pattern, use (?:$|(?=\s)) after the actual pattern.</%text>
273 273
274 274 issue_pat = #(\d+)
275 275
276 276 <%text>## server url to the issue</%text>
277 277 <%text>## This pattern may/should contain backreferences to parenthesized groups in issue_pat.</%text>
278 278 <%text>## A backreference can be \1, \2, ... or \g<groupname> if you specified a named group</%text>
279 279 <%text>## called 'groupname' in issue_pat.</%text>
280 280 <%text>## The special token {repo} is replaced with the full repository name</%text>
281 281 <%text>## including repository groups, while {repo_name} is replaced with just</%text>
282 282 <%text>## the name of the repository.</%text>
283 283
284 284 issue_server_link = https://issues.example.com/{repo}/issue/\1
285 285
286 286 <%text>## substitution pattern to use as the link text</%text>
287 287 <%text>## If issue_sub is empty, the text matched by issue_pat is retained verbatim</%text>
288 288 <%text>## for the link text. Otherwise, the link text is that of issue_sub, with any</%text>
289 289 <%text>## backreferences to groups in issue_pat replaced.</%text>
290 290
291 291 issue_sub =
292 292
293 293 <%text>## issue_pat, issue_server_link and issue_sub can have suffixes to specify</%text>
294 294 <%text>## multiple patterns, to other issues server, wiki or others</%text>
295 295 <%text>## below an example how to create a wiki pattern</%text>
296 296 # wiki-some-id -> https://wiki.example.com/some-id
297 297
298 298 #issue_pat_wiki = wiki-(\S+)
299 299 #issue_server_link_wiki = https://wiki.example.com/\1
300 300 #issue_sub_wiki = WIKI-\1
301 301
302 302 <%text>## alternative return HTTP header for failed authentication. Default HTTP</%text>
303 303 <%text>## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with</%text>
304 304 <%text>## handling that. Set this variable to 403 to return HTTPForbidden</%text>
305 305 auth_ret_code =
306 306
307 307 <%text>## allows to change the repository location in settings page</%text>
308 308 allow_repo_location_change = True
309 309
310 310 <%text>## allows to setup custom hooks in settings page</%text>
311 311 allow_custom_hooks_settings = True
312 312
313 313 <%text>## extra extensions for indexing, space separated and without the leading '.'.</%text>
314 314 # index.extensions =
315 315 # gemfile
316 316 # lock
317 317
318 318 <%text>## extra filenames for indexing, space separated</%text>
319 319 # index.filenames =
320 320 # .dockerignore
321 321 # .editorconfig
322 322 # INSTALL
323 323 # CHANGELOG
324 324
325 325 <%text>####################################</%text>
326 <%text>### SSH CONFIG ####</%text>
327 <%text>####################################</%text>
328
329 <%text>## SSH is disabled by default, until an Administrator decides to enable it.</%text>
330 ssh_enabled = false
331
332 <%text>####################################</%text>
326 333 <%text>### CELERY CONFIG ####</%text>
327 334 <%text>####################################</%text>
328 335
329 336 use_celery = false
330 337
331 338 <%text>## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq:</%text>
332 339 broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost
333 340
334 341 celery.imports = kallithea.lib.celerylib.tasks
335 342 celery.accept.content = pickle
336 343 celery.result.backend = amqp
337 344 celery.result.dburi = amqp://
338 345 celery.result.serialier = json
339 346
340 347 #celery.send.task.error.emails = true
341 348 #celery.amqp.task.result.expires = 18000
342 349
343 350 celeryd.concurrency = 2
344 351 celeryd.max.tasks.per.child = 1
345 352
346 353 <%text>## If true, tasks will never be sent to the queue, but executed locally instead.</%text>
347 354 celery.always.eager = false
348 355
349 356 <%text>####################################</%text>
350 357 <%text>### BEAKER CACHE ####</%text>
351 358 <%text>####################################</%text>
352 359
353 360 beaker.cache.data_dir = %(here)s/data/cache/data
354 361 beaker.cache.lock_dir = %(here)s/data/cache/lock
355 362
356 363 beaker.cache.regions = short_term,long_term,sql_cache_short
357 364
358 365 beaker.cache.short_term.type = memory
359 366 beaker.cache.short_term.expire = 60
360 367 beaker.cache.short_term.key_length = 256
361 368
362 369 beaker.cache.long_term.type = memory
363 370 beaker.cache.long_term.expire = 36000
364 371 beaker.cache.long_term.key_length = 256
365 372
366 373 beaker.cache.sql_cache_short.type = memory
367 374 beaker.cache.sql_cache_short.expire = 10
368 375 beaker.cache.sql_cache_short.key_length = 256
369 376
370 377 <%text>####################################</%text>
371 378 <%text>### BEAKER SESSION ####</%text>
372 379 <%text>####################################</%text>
373 380
374 381 <%text>## Name of session cookie. Should be unique for a given host and path, even when running</%text>
375 382 <%text>## on different ports. Otherwise, cookie sessions will be shared and messed up.</%text>
376 383 session.key = kallithea
377 384 <%text>## Sessions should always only be accessible by the browser, not directly by JavaScript.</%text>
378 385 session.httponly = true
379 386 <%text>## Session lifetime. 2592000 seconds is 30 days.</%text>
380 387 session.timeout = 2592000
381 388
382 389 <%text>## Server secret used with HMAC to ensure integrity of cookies.</%text>
383 390 session.secret = ${uuid()}
384 391 <%text>## Further, encrypt the data with AES.</%text>
385 392 #session.encrypt_key = <key_for_encryption>
386 393 #session.validate_key = <validation_key>
387 394
388 395 <%text>## Type of storage used for the session, current types are</%text>
389 396 <%text>## dbm, file, memcached, database, and memory.</%text>
390 397
391 398 <%text>## File system storage of session data. (default)</%text>
392 399 #session.type = file
393 400
394 401 <%text>## Cookie only, store all session data inside the cookie. Requires secure secrets.</%text>
395 402 #session.type = cookie
396 403
397 404 <%text>## Database storage of session data.</%text>
398 405 #session.type = ext:database
399 406 #session.sa.url = postgresql://postgres:qwe@localhost/kallithea
400 407 #session.table_name = db_session
401 408
402 409 <%text>############################</%text>
403 410 <%text>## ERROR HANDLING SYSTEMS ##</%text>
404 411 <%text>############################</%text>
405 412
406 413 # Propagate email settings to ErrorReporter of TurboGears2
407 414 # You do not normally need to change these lines
408 415 get trace_errors.error_email = email_to
409 416 get trace_errors.smtp_server = smtp_server
410 417 get trace_errors.smtp_port = smtp_port
411 418 get trace_errors.from_address = error_email_from
412 419
413 420 %if error_aggregation_service == 'appenlight':
414 421 <%text>####################</%text>
415 422 <%text>### [appenlight] ###</%text>
416 423 <%text>####################</%text>
417 424
418 425 <%text>## AppEnlight is tailored to work with Kallithea, see</%text>
419 426 <%text>## http://appenlight.com for details how to obtain an account</%text>
420 427 <%text>## you must install python package `appenlight_client` to make it work</%text>
421 428
422 429 <%text>## appenlight enabled</%text>
423 430 appenlight = false
424 431
425 432 appenlight.server_url = https://api.appenlight.com
426 433 appenlight.api_key = YOUR_API_KEY
427 434
428 435 <%text>## TWEAK AMOUNT OF INFO SENT HERE</%text>
429 436
430 437 <%text>## enables 404 error logging (default False)</%text>
431 438 appenlight.report_404 = false
432 439
433 440 <%text>## time in seconds after request is considered being slow (default 1)</%text>
434 441 appenlight.slow_request_time = 1
435 442
436 443 <%text>## record slow requests in application</%text>
437 444 <%text>## (needs to be enabled for slow datastore recording and time tracking)</%text>
438 445 appenlight.slow_requests = true
439 446
440 447 <%text>## enable hooking to application loggers</%text>
441 448 #appenlight.logging = true
442 449
443 450 <%text>## minimum log level for log capture</%text>
444 451 #appenlight.logging.level = WARNING
445 452
446 453 <%text>## send logs only from erroneous/slow requests</%text>
447 454 <%text>## (saves API quota for intensive logging)</%text>
448 455 appenlight.logging_on_error = false
449 456
450 457 <%text>## list of additional keywords that should be grabbed from environ object</%text>
451 458 <%text>## can be string with comma separated list of words in lowercase</%text>
452 459 <%text>## (by default client will always send following info:</%text>
453 460 <%text>## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that</%text>
454 461 <%text>## start with HTTP* this list be extended with additional keywords here</%text>
455 462 appenlight.environ_keys_whitelist =
456 463
457 464 <%text>## list of keywords that should be blanked from request object</%text>
458 465 <%text>## can be string with comma separated list of words in lowercase</%text>
459 466 <%text>## (by default client will always blank keys that contain following words</%text>
460 467 <%text>## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'</%text>
461 468 <%text>## this list be extended with additional keywords set here</%text>
462 469 appenlight.request_keys_blacklist =
463 470
464 471 <%text>## list of namespaces that should be ignores when gathering log entries</%text>
465 472 <%text>## can be string with comma separated list of namespaces</%text>
466 473 <%text>## (by default the client ignores own entries: appenlight_client.client)</%text>
467 474 appenlight.log_namespace_blacklist =
468 475
469 476 %elif error_aggregation_service == 'sentry':
470 477 <%text>################</%text>
471 478 <%text>### [sentry] ###</%text>
472 479 <%text>################</%text>
473 480
474 481 <%text>## sentry is a alternative open source error aggregator</%text>
475 482 <%text>## you must install python packages `sentry` and `raven` to enable</%text>
476 483
477 484 sentry.dsn = YOUR_DNS
478 485 sentry.servers =
479 486 sentry.name =
480 487 sentry.key =
481 488 sentry.public_key =
482 489 sentry.secret_key =
483 490 sentry.project =
484 491 sentry.site =
485 492 sentry.include_paths =
486 493 sentry.exclude_paths =
487 494
488 495 %endif
489 496 <%text>################################################################################</%text>
490 497 <%text>## WARNING: *DEBUG MODE MUST BE OFF IN A PRODUCTION ENVIRONMENT* ##</%text>
491 498 <%text>## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##</%text>
492 499 <%text>## execute malicious code after an exception is raised. ##</%text>
493 500 <%text>################################################################################</%text>
494 501 debug = false
495 502
496 503 <%text>##################################</%text>
497 504 <%text>### LOGVIEW CONFIG ###</%text>
498 505 <%text>##################################</%text>
499 506
500 507 logview.sqlalchemy = #faa
501 508 logview.pylons.templating = #bfb
502 509 logview.pylons.util = #eee
503 510
504 511 <%text>#########################################################</%text>
505 512 <%text>### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###</%text>
506 513 <%text>#########################################################</%text>
507 514
508 515 %if database_engine == 'sqlite':
509 516 # SQLITE [default]
510 517 sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
511 518
512 519 %elif database_engine == 'postgres':
513 520 # POSTGRESQL
514 521 sqlalchemy.url = postgresql://user:pass@localhost/kallithea
515 522
516 523 %elif database_engine == 'mysql':
517 524 # MySQL
518 525 sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8
519 526
520 527 %endif
521 528 # see sqlalchemy docs for others
522 529
523 530 sqlalchemy.pool_recycle = 3600
524 531
525 532 <%text>################################</%text>
526 533 <%text>### ALEMBIC CONFIGURATION ####</%text>
527 534 <%text>################################</%text>
528 535
529 536 [alembic]
530 537 script_location = kallithea:alembic
531 538
532 539 <%text>################################</%text>
533 540 <%text>### LOGGING CONFIGURATION ####</%text>
534 541 <%text>################################</%text>
535 542
536 543 [loggers]
537 544 keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer, werkzeug, backlash
538 545
539 546 [handlers]
540 547 keys = console, console_color, console_color_sql, null
541 548
542 549 [formatters]
543 550 keys = generic, color_formatter, color_formatter_sql
544 551
545 552 <%text>#############</%text>
546 553 <%text>## LOGGERS ##</%text>
547 554 <%text>#############</%text>
548 555
549 556 [logger_root]
550 557 level = NOTSET
551 558 handlers = console
552 559 # For coloring based on log level:
553 560 # handlers = console_color
554 561
555 562 [logger_routes]
556 563 level = WARN
557 564 handlers =
558 565 qualname = routes.middleware
559 566 <%text>## "level = DEBUG" logs the route matched and routing variables.</%text>
560 567
561 568 [logger_beaker]
562 569 level = WARN
563 570 handlers =
564 571 qualname = beaker.container
565 572
566 573 [logger_templates]
567 574 level = WARN
568 575 handlers =
569 576 qualname = pylons.templating
570 577
571 578 [logger_kallithea]
572 579 level = WARN
573 580 handlers =
574 581 qualname = kallithea
575 582
576 583 [logger_tg]
577 584 level = WARN
578 585 handlers =
579 586 qualname = tg
580 587
581 588 [logger_gearbox]
582 589 level = WARN
583 590 handlers =
584 591 qualname = gearbox
585 592
586 593 [logger_sqlalchemy]
587 594 level = WARN
588 595 handlers =
589 596 qualname = sqlalchemy.engine
590 597 # For coloring based on log level and pretty printing of SQL:
591 598 # level = INFO
592 599 # handlers = console_color_sql
593 600 # propagate = 0
594 601
595 602 [logger_whoosh_indexer]
596 603 level = WARN
597 604 handlers =
598 605 qualname = whoosh_indexer
599 606
600 607 [logger_werkzeug]
601 608 level = WARN
602 609 handlers =
603 610 qualname = werkzeug
604 611
605 612 [logger_backlash]
606 613 level = WARN
607 614 handlers =
608 615 qualname = backlash
609 616
610 617 <%text>##############</%text>
611 618 <%text>## HANDLERS ##</%text>
612 619 <%text>##############</%text>
613 620
614 621 [handler_console]
615 622 class = StreamHandler
616 623 args = (sys.stderr,)
617 624 formatter = generic
618 625
619 626 [handler_console_color]
620 627 # ANSI color coding based on log level
621 628 class = StreamHandler
622 629 args = (sys.stderr,)
623 630 formatter = color_formatter
624 631
625 632 [handler_console_color_sql]
626 633 # ANSI color coding and pretty printing of SQL statements
627 634 class = StreamHandler
628 635 args = (sys.stderr,)
629 636 formatter = color_formatter_sql
630 637
631 638 [handler_null]
632 639 class = NullHandler
633 640 args = ()
634 641
635 642 <%text>################</%text>
636 643 <%text>## FORMATTERS ##</%text>
637 644 <%text>################</%text>
638 645
639 646 [formatter_generic]
640 647 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
641 648 datefmt = %Y-%m-%d %H:%M:%S
642 649
643 650 [formatter_color_formatter]
644 651 class = kallithea.lib.colored_formatter.ColorFormatter
645 652 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
646 653 datefmt = %Y-%m-%d %H:%M:%S
647 654
648 655 [formatter_color_formatter_sql]
649 656 class = kallithea.lib.colored_formatter.ColorFormatterSql
650 657 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
651 658 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,213 +1,214 b''
1 1 import os
2 2 import re
3 3 import sys
4 4 import logging
5 5 import pkg_resources
6 6 import time
7 7
8 8 import formencode
9 9 from paste.deploy import loadwsgi
10 10 from routes.util import URLGenerator
11 11 import pytest
12 12 from pytest_localserver.http import WSGIServer
13 13
14 14 from kallithea.controllers.root import RootController
15 15 from kallithea.lib import inifile
16 16 from kallithea.lib.utils import repo2db_mapper
17 17 from kallithea.model.user import UserModel
18 18 from kallithea.model.meta import Session
19 19 from kallithea.model.db import Setting, User, UserIpMap
20 20 from kallithea.model.scm import ScmModel
21 21 from kallithea.tests.base import invalidate_all_caches, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH, \
22 22 TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS
23 23 import kallithea.tests.base # FIXME: needed for setting testapp instance!!!
24 24
25 25 from tg.util.webtest import test_context
26 26
27 27
28 28 def pytest_configure():
29 29 os.environ['TZ'] = 'UTC'
30 30 if not kallithea.is_windows:
31 31 time.tzset() # only available on Unix
32 32
33 33 path = os.getcwd()
34 34 sys.path.insert(0, path)
35 35 pkg_resources.working_set.add_entry(path)
36 36
37 37 # Disable INFO logging of test database creation, restore with NOTSET
38 38 logging.disable(logging.INFO)
39 39
40 40 ini_settings = {
41 41 '[server:main]': {
42 42 'port': '4999',
43 43 },
44 44 '[app:main]': {
45 'ssh_enabled': 'true',
45 46 'app_instance_uuid': 'test',
46 47 'show_revision_number': 'true',
47 48 'beaker.cache.sql_cache_short.expire': '1',
48 49 'session.secret': '{74e0cd75-b339-478b-b129-07dd221def1f}',
49 50 #'i18n.lang': '',
50 51 },
51 52 '[handler_console]': {
52 53 'formatter': 'color_formatter',
53 54 },
54 55 # The 'handler_console_sql' block is very similar to the one in
55 56 # development.ini, but without the explicit 'level=DEBUG' setting:
56 57 # it causes duplicate sqlalchemy debug logs, one through
57 58 # handler_console_sql and another through another path.
58 59 '[handler_console_sql]': {
59 60 'formatter': 'color_formatter_sql',
60 61 },
61 62 }
62 63 if os.environ.get('TEST_DB'):
63 64 ini_settings['[app:main]']['sqlalchemy.url'] = os.environ.get('TEST_DB')
64 65
65 66 test_ini_file = os.path.join(TESTS_TMP_PATH, 'test.ini')
66 67 inifile.create(test_ini_file, None, ini_settings)
67 68
68 69 context = loadwsgi.loadcontext(loadwsgi.APP, 'config:%s' % test_ini_file)
69 70 from kallithea.tests.fixture import create_test_env, create_test_index
70 71
71 72 # set KALLITHEA_NO_TMP_PATH=1 to disable re-creating the database and test repos
72 73 if not int(os.environ.get('KALLITHEA_NO_TMP_PATH', 0)):
73 74 create_test_env(TESTS_TMP_PATH, context.config())
74 75
75 76 # set KALLITHEA_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests
76 77 if not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0)):
77 78 create_test_index(TESTS_TMP_PATH, context.config(), True)
78 79
79 80 kallithea.tests.base.testapp = context.create()
80 81 # do initial repo scan
81 82 repo2db_mapper(ScmModel().repo_scan(TESTS_TMP_PATH))
82 83
83 84 logging.disable(logging.NOTSET)
84 85
85 86 kallithea.tests.base.url = URLGenerator(RootController().mapper, {'HTTP_HOST': 'example.com'})
86 87
87 88 # set fixed language for form messages, regardless of environment settings
88 89 formencode.api.set_stdtranslation(languages=[])
89 90
90 91
91 92 @pytest.fixture
92 93 def create_test_user():
93 94 """Provide users that automatically disappear after test is over."""
94 95 test_user_ids = []
95 96
96 97 def _create_test_user(user_form):
97 98 user = UserModel().create(user_form)
98 99 test_user_ids.append(user.user_id)
99 100 return user
100 101 yield _create_test_user
101 102 for user_id in test_user_ids:
102 103 UserModel().delete(user_id)
103 104 Session().commit()
104 105
105 106
106 107 def _set_settings(*kvtseq):
107 108 session = Session()
108 109 for kvt in kvtseq:
109 110 assert len(kvt) in (2, 3)
110 111 k = kvt[0]
111 112 v = kvt[1]
112 113 t = kvt[2] if len(kvt) == 3 else 'unicode'
113 114 Setting.create_or_update(k, v, t)
114 115 session.commit()
115 116
116 117
117 118 @pytest.fixture
118 119 def set_test_settings():
119 120 """Restore settings after test is over."""
120 121 # Save settings.
121 122 settings_snapshot = [
122 123 (s.app_settings_name, s.app_settings_value, s.app_settings_type)
123 124 for s in Setting.query().all()]
124 125 yield _set_settings
125 126 # Restore settings.
126 127 session = Session()
127 128 keys = frozenset(k for (k, v, t) in settings_snapshot)
128 129 for s in Setting.query().all():
129 130 if s.app_settings_name not in keys:
130 131 session.delete(s)
131 132 for k, v, t in settings_snapshot:
132 133 if t == 'list' and hasattr(v, '__iter__'):
133 134 v = ','.join(v) # Quirk: must format list value manually.
134 135 Setting.create_or_update(k, v, t)
135 136 session.commit()
136 137
137 138
138 139 @pytest.fixture
139 140 def auto_clear_ip_permissions():
140 141 """Fixture that provides nothing but clearing IP permissions upon test
141 142 exit. This clearing is needed to avoid other test failing to make fake http
142 143 accesses."""
143 144 yield
144 145 # cleanup
145 146 user_model = UserModel()
146 147
147 148 user_ids = []
148 149 user_ids.append(User.get_default_user().user_id)
149 150 user_ids.append(User.get_by_username(TEST_USER_REGULAR_LOGIN).user_id)
150 151
151 152 for user_id in user_ids:
152 153 for ip in UserIpMap.query().filter(UserIpMap.user_id == user_id):
153 154 user_model.delete_extra_ip(user_id, ip.ip_id)
154 155
155 156 # IP permissions are cached, need to invalidate this cache explicitly
156 157 invalidate_all_caches()
157 158 session = Session()
158 159 session.commit()
159 160
160 161
161 162 @pytest.fixture
162 163 def test_context_fixture(app_fixture):
163 164 """
164 165 Encompass the entire test using this fixture in a test_context,
165 166 making sure that certain functionality still works even if no call to
166 167 self.app.get/post has been made.
167 168 The typical error message indicating you need a test_context is:
168 169 TypeError: No object (name: context) has been registered for this thread
169 170
170 171 The standard way to fix this is simply using the test_context context
171 172 manager directly inside your test:
172 173 with test_context(self.app):
173 174 <actions>
174 175 but if test setup code (xUnit-style or pytest fixtures) also needs to be
175 176 executed inside the test context, that method is not possible.
176 177 Even if there is no such setup code, the fixture may reduce code complexity
177 178 if the entire test needs to run inside a test context.
178 179
179 180 To apply this fixture (like any other fixture) to all test methods of a
180 181 class, use the following class decorator:
181 182 @pytest.mark.usefixtures("test_context_fixture")
182 183 class TestFoo(TestController):
183 184 ...
184 185 """
185 186 with test_context(app_fixture):
186 187 yield
187 188
188 189
189 190 class MyWSGIServer(WSGIServer):
190 191 def repo_url(self, repo_name, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
191 192 """Return URL to repo on this web server."""
192 193 host, port = self.server_address
193 194 proto = 'http' if self._server.ssl_context is None else 'https'
194 195 auth = ''
195 196 if username is not None:
196 197 auth = username
197 198 if password is not None:
198 199 auth += ':' + password
199 200 if auth:
200 201 auth += '@'
201 202 return '%s://%s%s:%s/%s' % (proto, auth, host, port, repo_name)
202 203
203 204
204 205 @pytest.yield_fixture(scope="session")
205 206 def webserver():
206 207 """Start web server while tests are running.
207 208 Useful for debugging and necessary for vcs operation tests."""
208 209 server = MyWSGIServer(application=kallithea.tests.base.testapp)
209 210 server.start()
210 211
211 212 yield server
212 213
213 214 server.stop()
General Comments 0
You need to be logged in to leave comments. Login now