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