##// END OF EJS Templates
file-uploads: created simple upload capabilities....
ergo -
r3432:834ca581 default
parent child Browse files
Show More
@@ -0,0 +1,49 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import os
21 from rhodecode.apps.upload_store import config_keys
22 from rhodecode.config.middleware import _bool_setting, _string_setting
23
24
25 def _sanitize_settings_and_apply_defaults(settings):
26 """
27 Set defaults, convert to python types and validate settings.
28 """
29 _bool_setting(settings, config_keys.enabled, 'true')
30
31 _string_setting(settings, config_keys.backend, 'local')
32
33 default_store = os.path.join(os.path.dirname(settings['__file__']), 'upload_store')
34 _string_setting(settings, config_keys.store_path, default_store)
35
36
37 def includeme(config):
38 settings = config.registry.settings
39 _sanitize_settings_and_apply_defaults(settings)
40
41 config.add_route(
42 name='upload_file',
43 pattern='/_file_store/upload')
44 config.add_route(
45 name='download_file',
46 pattern='/_file_store/download/{fid}')
47
48 # Scan module for configuration decorators.
49 config.scan('.views', ignore='.tests')
@@ -0,0 +1,27 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 # Definition of setting keys used to configure this module. Defined here to
23 # avoid repetition of keys throughout the module.
24
25 enabled = 'file_store.enabled'
26 backend = 'file_store.backend'
27 store_path = 'file_store.storage_path'
@@ -0,0 +1,31 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 class FileNotAllowedException(Exception):
23 """
24 Thrown if file does not have an allowed extension.
25 """
26
27
28 class FileOverSizeException(Exception):
29 """
30 Thrown if file is over the set limit.
31 """
@@ -0,0 +1,66 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 ANY = []
23 TEXT_EXT = ['txt', 'md', 'rst', 'log']
24 DOCUMENTS_EXT = ['pdf', 'rtf', 'odf', 'ods', 'gnumeric', 'abw', 'doc', 'docx', 'xls', 'xlsx']
25 IMAGES_EXT = ['jpg', 'jpe', 'jpeg', 'png', 'gif', 'svg', 'bmp', 'tiff']
26 AUDIO_EXT = ['wav', 'mp3', 'aac', 'ogg', 'oga', 'flac']
27 VIDEO_EXT = ['mpeg', '3gp', 'avi', 'divx', 'dvr', 'flv', 'mp4', 'wmv']
28 DATA_EXT = ['csv', 'ini', 'json', 'plist', 'xml', 'yaml', 'yml']
29 SCRIPTS_EXT = ['js', 'php', 'pl', 'py', 'rb', 'sh', 'go', 'c', 'h']
30 ARCHIVES_EXT = ['gz', 'bz2', 'zip', 'tar', 'tgz', 'txz', '7z']
31 EXECUTABLES_EXT = ['so', 'exe', 'dll']
32
33
34 DEFAULT = DOCUMENTS_EXT + TEXT_EXT + IMAGES_EXT + DATA_EXT
35
36 GROUPS = dict((
37 ('any', ANY),
38 ('text', TEXT_EXT),
39 ('documents', DOCUMENTS_EXT),
40 ('images', IMAGES_EXT),
41 ('audio', AUDIO_EXT),
42 ('video', VIDEO_EXT),
43 ('data', DATA_EXT),
44 ('scripts', SCRIPTS_EXT),
45 ('archives', ARCHIVES_EXT),
46 ('executables', EXECUTABLES_EXT),
47 ('default', DEFAULT),
48 ))
49
50
51 def resolve_extensions(extensions, groups=None):
52 """
53 Calculate allowed extensions based on a list of extensions provided, and optional
54 groups of extensions from the available lists.
55
56 :param extensions: a list of extensions e.g ['py', 'txt']
57 :param groups: additionally groups to extend the extensions.
58 """
59 groups = groups or []
60 valid_exts = set([x.lower() for x in extensions])
61
62 for group in groups:
63 if group in GROUPS:
64 valid_exts.update(GROUPS[group])
65
66 return valid_exts
@@ -0,0 +1,167 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22 import shutil
23
24 from rhodecode.lib.ext_json import json
25 from rhodecode.apps.upload_store import utils
26 from rhodecode.apps.upload_store.extensions import resolve_extensions
27 from rhodecode.apps.upload_store.exceptions import FileNotAllowedException
28
29
30 class LocalFileStorage(object):
31
32 @classmethod
33 def resolve_name(cls, name, directory):
34 """
35 Resolves a unique name and the correct path. If a filename
36 for that path already exists then a numeric prefix with values > 0 will be
37 added, for example test.jpg -> test-1.jpg etc. initially file would have 0 prefix.
38
39 :param name: base name of file
40 :param directory: absolute directory path
41 """
42
43 basename, ext = os.path.splitext(name)
44 counter = 0
45 while True:
46 name = '%s-%d%s' % (basename, counter, ext)
47 path = os.path.join(directory, name)
48 if not os.path.exists(path):
49 return name, path
50 counter += 1
51
52 def __init__(self, base_path, extension_groups=None):
53
54 """
55 Local file storage
56
57 :param base_path: the absolute base path where uploads are stored
58 :param extension_groups: extensions string
59 """
60
61 extension_groups = extension_groups or ['any']
62 self.base_path = base_path
63 self.extensions = resolve_extensions([], groups=extension_groups)
64
65 def store_path(self, filename):
66 """
67 Returns absolute file path of the filename, joined to the
68 base_path.
69
70 :param filename: base name of file
71 """
72 return os.path.join(self.base_path, filename)
73
74 def delete(self, filename):
75 """
76 Deletes the filename. Filename is resolved with the
77 absolute path based on base_path. If file does not exist,
78 returns **False**, otherwise **True**
79
80 :param filename: base name of file
81 """
82 if self.exists(filename):
83 os.remove(self.store_path(filename))
84 return True
85 return False
86
87 def exists(self, filename):
88 """
89 Checks if file exists. Resolves filename's absolute
90 path based on base_path.
91
92 :param filename: base name of file
93 """
94 return os.path.exists(self.store_path(filename))
95
96 def filename_allowed(self, filename, extensions=None):
97 """Checks if a filename has an allowed extension
98
99 :param filename: base name of file
100 :param extensions: iterable of extensions (or self.extensions)
101 """
102 _, ext = os.path.splitext(filename)
103 return self.extension_allowed(ext, extensions)
104
105 def extension_allowed(self, ext, extensions=None):
106 """
107 Checks if an extension is permitted. Both e.g. ".jpg" and
108 "jpg" can be passed in. Extension lookup is case-insensitive.
109
110 :param extensions: iterable of extensions (or self.extensions)
111 """
112
113 extensions = extensions or self.extensions
114 if not extensions:
115 return True
116 if ext.startswith('.'):
117 ext = ext[1:]
118 return ext.lower() in extensions
119
120 def save_file(self, file_obj, filename, directory=None, extensions=None,
121 metadata=None, **kwargs):
122 """
123 Saves a file object to the uploads location.
124 Returns the resolved filename, i.e. the directory +
125 the (randomized/incremented) base name.
126
127 :param file_obj: **cgi.FieldStorage** object (or similar)
128 :param filename: original filename
129 :param directory: relative path of sub-directory
130 :param extensions: iterable of allowed extensions, if not default
131 :param metadata: JSON metadata to store next to the file with .meta suffix
132 :returns: modified filename
133 """
134
135 extensions = extensions or self.extensions
136
137 if not self.filename_allowed(filename, extensions):
138 raise FileNotAllowedException()
139
140 if directory:
141 dest_directory = os.path.join(self.base_path, directory)
142 else:
143 dest_directory = self.base_path
144
145 if not os.path.exists(dest_directory):
146 os.makedirs(dest_directory)
147
148 filename = utils.uid_filename(filename)
149
150 filename, path = self.resolve_name(filename, dest_directory)
151 filename_meta = filename + '.meta'
152
153 file_obj.seek(0)
154
155 with open(path, "wb") as dest:
156 shutil.copyfileobj(file_obj, dest)
157
158 if metadata:
159 size = os.stat(path).st_size
160 metadata.update({'size': size})
161 with open(os.path.join(dest_directory, filename_meta), "wb") as dest_meta:
162 dest_meta.write(json.dumps(metadata))
163
164 if directory:
165 filename = os.path.join(directory, filename)
166
167 return filename
@@ -0,0 +1,20 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
@@ -0,0 +1,110 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import os
21 import pytest
22
23 from rhodecode.lib.ext_json import json
24 from rhodecode.tests import TestController
25 from rhodecode.apps.upload_store import utils, config_keys
26
27
28 def route_path(name, params=None, **kwargs):
29 import urllib
30
31 base_url = {
32 'upload_file': '/_file_store/upload',
33 'download_file': '/_file_store/download/{fid}',
34
35 }[name].format(**kwargs)
36
37 if params:
38 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
39 return base_url
40
41
42 class TestFileStoreViews(TestController):
43
44 @pytest.mark.parametrize("fid, content, exists", [
45 ('abcde-0.jpg', "xxxxx", True),
46 ('abcde-0.exe', "1234567", True),
47 ('abcde-0.jpg', "xxxxx", False),
48 ])
49 def test_get_files_from_store(self, fid, content, exists, tmpdir):
50 self.log_user()
51 store_path = self.app._pyramid_settings[config_keys.store_path]
52
53 if exists:
54 status = 200
55 store = utils.get_file_storage({config_keys.store_path: store_path})
56 filesystem_file = os.path.join(str(tmpdir), fid)
57 with open(filesystem_file, 'wb') as f:
58 f.write(content)
59
60 with open(filesystem_file, 'rb') as f:
61 fid = store.save_file(f, fid, metadata={'filename': fid})
62
63 else:
64 status = 404
65
66 response = self.app.get(route_path('download_file', fid=fid), status=status)
67
68 if exists:
69 assert response.text == content
70 metadata = os.path.join(store_path, fid + '.meta')
71 assert os.path.exists(metadata)
72 with open(metadata, 'rb') as f:
73 json_data = json.loads(f.read())
74
75 assert json_data
76 assert 'size' in json_data
77
78 def test_upload_files_without_content_to_store(self):
79 self.log_user()
80 response = self.app.post(
81 route_path('upload_file'),
82 params={'csrf_token': self.csrf_token},
83 status=200)
84
85 assert response.json == {
86 u'error': u'store_file data field is missing',
87 u'access_path': None,
88 u'store_fid': None}
89
90 def test_upload_files_bogus_content_to_store(self):
91 self.log_user()
92 response = self.app.post(
93 route_path('upload_file'),
94 params={'csrf_token': self.csrf_token, 'store_file': 'bogus'},
95 status=200)
96
97 assert response.json == {
98 u'error': u'filename cannot be read from the data field',
99 u'access_path': None,
100 u'store_fid': None}
101
102 def test_upload_content_to_store(self):
103 self.log_user()
104 response = self.app.post(
105 route_path('upload_file'),
106 upload_files=[('store_file', 'myfile.txt', 'SOME CONTENT')],
107 params={'csrf_token': self.csrf_token},
108 status=200)
109
110 assert response.json['store_fid']
@@ -0,0 +1,47 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import os
23 import uuid
24
25
26 def get_file_storage(settings):
27 from rhodecode.apps.upload_store.local_store import LocalFileStorage
28 from rhodecode.apps.upload_store import config_keys
29 store_path = settings.get(config_keys.store_path)
30 return LocalFileStorage(base_path=store_path)
31
32
33 def uid_filename(filename, randomized=True):
34 """
35 Generates a randomized or stable (uuid) filename,
36 preserving the original extension.
37
38 :param filename: the original filename
39 :param randomized: define if filename should be stable (sha1 based) or randomized
40 """
41 _, ext = os.path.splitext(filename)
42 if randomized:
43 uid = uuid.uuid4()
44 else:
45 hash_key = '{}.{}'.format(filename, 'store')
46 uid = uuid.uuid5(uuid.NAMESPACE_URL, hash_key)
47 return str(uid) + ext.lower()
@@ -0,0 +1,97 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import logging
21
22 from pyramid.view import view_config
23 from pyramid.response import FileResponse
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps.upload_store import utils
28 from rhodecode.apps.upload_store.exceptions import (
29 FileNotAllowedException,FileOverSizeException)
30
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib.auth import (CSRFRequired, NotAnonymous)
34
35 log = logging.getLogger(__name__)
36
37
38 class FileStoreView(BaseAppView):
39 upload_key = 'store_file'
40
41 def load_default_context(self):
42 c = self._get_local_tmpl_context()
43 self.storage = utils.get_file_storage(self.request.registry.settings)
44 return c
45
46 @NotAnonymous()
47 @CSRFRequired()
48 @view_config(route_name='upload_file', request_method='POST', renderer='json_ext')
49 def upload_file(self):
50 self.load_default_context()
51 file_obj = self.request.POST.get(self.upload_key)
52
53 if file_obj is None:
54 return {'store_fid': None,
55 'access_path': None,
56 'error': '{} data field is missing'.format(self.upload_key)}
57
58 if not hasattr(file_obj, 'filename'):
59 return {'store_fid': None,
60 'access_path': None,
61 'error': 'filename cannot be read from the data field'}
62
63 filename = file_obj.filename
64
65 metadata = {
66 'filename': filename,
67 'size': '', # filled by save_file
68 'user_uploaded': {'username': self._rhodecode_user.username,
69 'user_id': self._rhodecode_user.user_id,
70 'ip': self._rhodecode_user.ip_addr}}
71 try:
72 store_fid = self.storage.save_file(file_obj.file, filename,
73 metadata=metadata)
74 except FileNotAllowedException:
75 return {'store_fid': None,
76 'access_path': None,
77 'error': 'File {} is not allowed.'.format(filename)}
78
79 except FileOverSizeException:
80 return {'store_fid': None,
81 'access_path': None,
82 'error': 'File {} is exceeding allowed limit.'.format(filename)}
83
84 return {'store_fid': store_fid,
85 'access_path': h.route_path('download_file', fid=store_fid)}
86
87 @view_config(route_name='download_file')
88 def download_file(self):
89 self.load_default_context()
90 file_uid = self.request.matchdict['fid']
91 log.debug('Requesting FID:%s from store %s', file_uid, self.storage)
92 if not self.storage.exists(file_uid):
93 log.debug('File with FID:%s not found in the store', file_uid)
94 raise HTTPNotFound()
95
96 file_path = self.storage.store_path(file_uid)
97 return FileResponse(file_path)
@@ -1,718 +1,725 b''
1 1
2 2
3 3 ################################################################################
4 4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 5 ################################################################################
6 6
7 7 [DEFAULT]
8 8 ## Debug flag sets all loggers to debug, and enables request tracking
9 9 debug = true
10 10
11 11 ################################################################################
12 12 ## EMAIL CONFIGURATION ##
13 13 ## Uncomment and replace with the email address which should receive ##
14 14 ## any error reports after an application crash ##
15 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 16 ################################################################################
17 17
18 18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 19 #email_prefix = [RhodeCode]
20 20
21 21 ## email FROM address all mails will be sent
22 22 #app_email_from = rhodecode-noreply@localhost
23 23
24 24 #smtp_server = mail.server.com
25 25 #smtp_username =
26 26 #smtp_password =
27 27 #smtp_port =
28 28 #smtp_use_tls = false
29 29 #smtp_use_ssl = true
30 30
31 31 [server:main]
32 32 ## COMMON ##
33 33 host = 127.0.0.1
34 34 port = 5000
35 35
36 36 ###########################################################
37 37 ## WAITRESS WSGI SERVER - Recommended for Development ####
38 38 ###########################################################
39 39
40 40 use = egg:waitress#main
41 41 ## number of worker threads
42 42 threads = 5
43 43 ## MAX BODY SIZE 100GB
44 44 max_request_body_size = 107374182400
45 45 ## Use poll instead of select, fixes file descriptors limits problems.
46 46 ## May not work on old windows systems.
47 47 asyncore_use_poll = true
48 48
49 49
50 50 ##########################
51 51 ## GUNICORN WSGI SERVER ##
52 52 ##########################
53 53 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
54 54
55 55 #use = egg:gunicorn#main
56 56 ## Sets the number of process workers. More workers means more concurent connections
57 57 ## RhodeCode can handle at the same time. Each additional worker also it increases
58 58 ## memory usage as each has it's own set of caches.
59 59 ## Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
60 60 ## than 8-10 unless for really big deployments .e.g 700-1000 users.
61 61 ## `instance_id = *` must be set in the [app:main] section below (which is the default)
62 62 ## when using more than 1 worker.
63 63 #workers = 2
64 64 ## process name visible in process list
65 65 #proc_name = rhodecode
66 66 ## type of worker class, one of sync, gevent
67 67 ## recommended for bigger setup is using of of other than sync one
68 68 #worker_class = gevent
69 69 ## The maximum number of simultaneous clients. Valid only for Gevent
70 70 #worker_connections = 10
71 71 ## max number of requests that worker will handle before being gracefully
72 72 ## restarted, could prevent memory leaks
73 73 #max_requests = 1000
74 74 #max_requests_jitter = 30
75 75 ## amount of time a worker can spend with handling a request before it
76 76 ## gets killed and restarted. Set to 6hrs
77 77 #timeout = 21600
78 78
79 79
80 80 ## prefix middleware for RhodeCode.
81 81 ## recommended when using proxy setup.
82 82 ## allows to set RhodeCode under a prefix in server.
83 83 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
84 84 ## And set your prefix like: `prefix = /custom_prefix`
85 85 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
86 86 ## to make your cookies only work on prefix url
87 87 [filter:proxy-prefix]
88 88 use = egg:PasteDeploy#prefix
89 89 prefix = /
90 90
91 91 [app:main]
92 92 ## The %(here)s variable will be replaced with the absolute path of parent directory
93 93 ## of this file
94 94 ## In addition ENVIRONMENT variables usage is possible, e.g
95 95 ## sqlalchemy.db1.url = {ENV_RC_DB_URL}
96 96
97 97 use = egg:rhodecode-enterprise-ce
98 98
99 99 ## enable proxy prefix middleware, defined above
100 100 #filter-with = proxy-prefix
101 101
102 102 # During development the we want to have the debug toolbar enabled
103 103 pyramid.includes =
104 104 pyramid_debugtoolbar
105 105 rhodecode.lib.middleware.request_wrapper
106 106
107 107 pyramid.reload_templates = true
108 108
109 109 debugtoolbar.hosts = 0.0.0.0/0
110 110 debugtoolbar.exclude_prefixes =
111 111 /css
112 112 /fonts
113 113 /images
114 114 /js
115 115
116 116 ## RHODECODE PLUGINS ##
117 117 rhodecode.includes =
118 118 rhodecode.api
119 119
120 120
121 121 # api prefix url
122 122 rhodecode.api.url = /_admin/api
123 123
124 124
125 125 ## END RHODECODE PLUGINS ##
126 126
127 127 ## encryption key used to encrypt social plugin tokens,
128 128 ## remote_urls with credentials etc, if not set it defaults to
129 129 ## `beaker.session.secret`
130 130 #rhodecode.encrypted_values.secret =
131 131
132 132 ## decryption strict mode (enabled by default). It controls if decryption raises
133 133 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
134 134 #rhodecode.encrypted_values.strict = false
135 135
136 136 ## return gzipped responses from Rhodecode (static files/application)
137 137 gzip_responses = false
138 138
139 139 ## autogenerate javascript routes file on startup
140 140 generate_js_files = false
141 141
142 142 ## System global default language.
143 143 ## All available languages: en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
144 144 lang = en
145 145
146 146 ## Perform a full repository scan and import on each server start.
147 147 ## Settings this to true could lead to very long startup time.
148 148 startup.import_repos = false
149 149
150 150 ## Uncomment and set this path to use archive download cache.
151 151 ## Once enabled, generated archives will be cached at this location
152 152 ## and served from the cache during subsequent requests for the same archive of
153 153 ## the repository.
154 154 #archive_cache_dir = /tmp/tarballcache
155 155
156 156 ## URL at which the application is running. This is used for bootstraping
157 157 ## requests in context when no web request is available. Used in ishell, or
158 158 ## SSH calls. Set this for events to receive proper url for SSH calls.
159 159 app.base_url = http://rhodecode.local
160 160
161 161 ## Unique application ID. Should be a random unique string for security.
162 162 app_instance_uuid = rc-production
163 163
164 164 ## Cut off limit for large diffs (size in bytes). If overall diff size on
165 165 ## commit, or pull request exceeds this limit this diff will be displayed
166 166 ## partially. E.g 512000 == 512Kb
167 167 cut_off_limit_diff = 512000
168 168
169 169 ## Cut off limit for large files inside diffs (size in bytes). Each individual
170 170 ## file inside diff which exceeds this limit will be displayed partially.
171 171 ## E.g 128000 == 128Kb
172 172 cut_off_limit_file = 128000
173 173
174 174 ## use cached version of vcs repositories everywhere. Recommended to be `true`
175 175 vcs_full_cache = true
176 176
177 177 ## Force https in RhodeCode, fixes https redirects, assumes it's always https.
178 178 ## Normally this is controlled by proper http flags sent from http server
179 179 force_https = false
180 180
181 181 ## use Strict-Transport-Security headers
182 182 use_htsts = false
183 183
184 184 ## git rev filter option, --all is the default filter, if you need to
185 185 ## hide all refs in changelog switch this to --branches --tags
186 186 git_rev_filter = --branches --tags
187 187
188 188 # Set to true if your repos are exposed using the dumb protocol
189 189 git_update_server_info = false
190 190
191 191 ## RSS/ATOM feed options
192 192 rss_cut_off_limit = 256000
193 193 rss_items_per_page = 10
194 194 rss_include_diff = false
195 195
196 196 ## gist URL alias, used to create nicer urls for gist. This should be an
197 197 ## url that does rewrites to _admin/gists/{gistid}.
198 198 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
199 199 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
200 200 gist_alias_url =
201 201
202 202 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
203 203 ## used for access.
204 204 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
205 205 ## came from the the logged in user who own this authentication token.
206 206 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
207 207 ## authentication token. Such view would be only accessible when used together
208 208 ## with this authentication token
209 209 ##
210 210 ## list of all views can be found under `/_admin/permissions/auth_token_access`
211 211 ## The list should be "," separated and on a single line.
212 212 ##
213 213 ## Most common views to enable:
214 214 # RepoCommitsView:repo_commit_download
215 215 # RepoCommitsView:repo_commit_patch
216 216 # RepoCommitsView:repo_commit_raw
217 217 # RepoCommitsView:repo_commit_raw@TOKEN
218 218 # RepoFilesView:repo_files_diff
219 219 # RepoFilesView:repo_archivefile
220 220 # RepoFilesView:repo_file_raw
221 221 # GistView:*
222 222 api_access_controllers_whitelist =
223 223
224 224 ## Default encoding used to convert from and to unicode
225 225 ## can be also a comma separated list of encoding in case of mixed encodings
226 226 default_encoding = UTF-8
227 227
228 228 ## instance-id prefix
229 229 ## a prefix key for this instance used for cache invalidation when running
230 230 ## multiple instances of rhodecode, make sure it's globally unique for
231 231 ## all running rhodecode instances. Leave empty if you don't use it
232 232 instance_id =
233 233
234 234 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
235 235 ## of an authentication plugin also if it is disabled by it's settings.
236 236 ## This could be useful if you are unable to log in to the system due to broken
237 237 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
238 238 ## module to log in again and fix the settings.
239 239 ##
240 240 ## Available builtin plugin IDs (hash is part of the ID):
241 241 ## egg:rhodecode-enterprise-ce#rhodecode
242 242 ## egg:rhodecode-enterprise-ce#pam
243 243 ## egg:rhodecode-enterprise-ce#ldap
244 244 ## egg:rhodecode-enterprise-ce#jasig_cas
245 245 ## egg:rhodecode-enterprise-ce#headers
246 246 ## egg:rhodecode-enterprise-ce#crowd
247 247 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
248 248
249 249 ## alternative return HTTP header for failed authentication. Default HTTP
250 250 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
251 251 ## handling that causing a series of failed authentication calls.
252 252 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
253 253 ## This will be served instead of default 401 on bad authnetication
254 254 auth_ret_code =
255 255
256 256 ## use special detection method when serving auth_ret_code, instead of serving
257 257 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
258 258 ## and then serve auth_ret_code to clients
259 259 auth_ret_code_detection = false
260 260
261 261 ## locking return code. When repository is locked return this HTTP code. 2XX
262 262 ## codes don't break the transactions while 4XX codes do
263 263 lock_ret_code = 423
264 264
265 265 ## allows to change the repository location in settings page
266 266 allow_repo_location_change = true
267 267
268 268 ## allows to setup custom hooks in settings page
269 269 allow_custom_hooks_settings = true
270 270
271 271 ## Generated license token required for EE edition license.
272 272 ## New generated token value can be found in Admin > settings > license page.
273 273 license_token =
274 274
275 275 ## supervisor connection uri, for managing supervisor and logs.
276 276 supervisor.uri =
277 277 ## supervisord group name/id we only want this RC instance to handle
278 278 supervisor.group_id = dev
279 279
280 280 ## Display extended labs settings
281 281 labs_settings_active = true
282 282
283 283 ## Custom exception store path, defaults to TMPDIR
284 284 ## This is used to store exception from RhodeCode in shared directory
285 285 #exception_tracker.store_path =
286 286
287 ## File store configuration. This is used to store and serve uploaded files
288 file_store.enabled = true
289 ## backend, only available one is local
290 file_store.backend = local
291 ## path to store the uploaded binaries
292 file_store.storage_path = %(here)s/data/file_store
293
287 294
288 295 ####################################
289 296 ### CELERY CONFIG ####
290 297 ####################################
291 298 ## run: /path/to/celery worker \
292 299 ## -E --beat --app rhodecode.lib.celerylib.loader \
293 300 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
294 301 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
295 302
296 303 use_celery = false
297 304
298 305 ## connection url to the message broker (default rabbitmq)
299 306 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
300 307
301 308 ## maximum tasks to execute before worker restart
302 309 celery.max_tasks_per_child = 100
303 310
304 311 ## tasks will never be sent to the queue, but executed locally instead.
305 312 celery.task_always_eager = false
306 313
307 314 #####################################
308 315 ### DOGPILE CACHE ####
309 316 #####################################
310 317 ## Default cache dir for caches. Putting this into a ramdisk
311 318 ## can boost performance, eg. /tmpfs/data_ramdisk, however this directory might require
312 319 ## large amount of space
313 320 cache_dir = %(here)s/data
314 321
315 322 ## `cache_perms` cache settings for permission tree, auth TTL.
316 323 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
317 324 rc_cache.cache_perms.expiration_time = 300
318 325
319 326 ## alternative `cache_perms` redis backend with distributed lock
320 327 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
321 328 #rc_cache.cache_perms.expiration_time = 300
322 329 ## redis_expiration_time needs to be greater then expiration_time
323 330 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
324 331 #rc_cache.cache_perms.arguments.socket_timeout = 30
325 332 #rc_cache.cache_perms.arguments.host = localhost
326 333 #rc_cache.cache_perms.arguments.port = 6379
327 334 #rc_cache.cache_perms.arguments.db = 0
328 335 #rc_cache.cache_perms.arguments.distributed_lock = true
329 336
330 337 ## `cache_repo` cache settings for FileTree, Readme, RSS FEEDS
331 338 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
332 339 rc_cache.cache_repo.expiration_time = 2592000
333 340
334 341 ## alternative `cache_repo` redis backend with distributed lock
335 342 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
336 343 #rc_cache.cache_repo.expiration_time = 2592000
337 344 ## redis_expiration_time needs to be greater then expiration_time
338 345 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
339 346 #rc_cache.cache_repo.arguments.socket_timeout = 30
340 347 #rc_cache.cache_repo.arguments.host = localhost
341 348 #rc_cache.cache_repo.arguments.port = 6379
342 349 #rc_cache.cache_repo.arguments.db = 1
343 350 #rc_cache.cache_repo.arguments.distributed_lock = true
344 351
345 352 ## cache settings for SQL queries, this needs to use memory type backend
346 353 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
347 354 rc_cache.sql_cache_short.expiration_time = 30
348 355
349 356 ## `cache_repo_longterm` cache for repo object instances, this needs to use memory
350 357 ## type backend as the objects kept are not pickle serializable
351 358 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
352 359 ## by default we use 96H, this is using invalidation on push anyway
353 360 rc_cache.cache_repo_longterm.expiration_time = 345600
354 361 ## max items in LRU cache, reduce this number to save memory, and expire last used
355 362 ## cached objects
356 363 rc_cache.cache_repo_longterm.max_size = 10000
357 364
358 365
359 366 ####################################
360 367 ### BEAKER SESSION ####
361 368 ####################################
362 369
363 370 ## .session.type is type of storage options for the session, current allowed
364 371 ## types are file, ext:memcached, ext:redis, ext:database, and memory (default).
365 372 beaker.session.type = file
366 373 beaker.session.data_dir = %(here)s/data/sessions
367 374
368 375 ## db based session, fast, and allows easy management over logged in users
369 376 #beaker.session.type = ext:database
370 377 #beaker.session.table_name = db_session
371 378 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
372 379 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
373 380 #beaker.session.sa.pool_recycle = 3600
374 381 #beaker.session.sa.echo = false
375 382
376 383 beaker.session.key = rhodecode
377 384 beaker.session.secret = develop-rc-uytcxaz
378 385 beaker.session.lock_dir = %(here)s/data/sessions/lock
379 386
380 387 ## Secure encrypted cookie. Requires AES and AES python libraries
381 388 ## you must disable beaker.session.secret to use this
382 389 #beaker.session.encrypt_key = key_for_encryption
383 390 #beaker.session.validate_key = validation_key
384 391
385 392 ## sets session as invalid(also logging out user) if it haven not been
386 393 ## accessed for given amount of time in seconds
387 394 beaker.session.timeout = 2592000
388 395 beaker.session.httponly = true
389 396 ## Path to use for the cookie. Set to prefix if you use prefix middleware
390 397 #beaker.session.cookie_path = /custom_prefix
391 398
392 399 ## uncomment for https secure cookie
393 400 beaker.session.secure = false
394 401
395 402 ## auto save the session to not to use .save()
396 403 beaker.session.auto = false
397 404
398 405 ## default cookie expiration time in seconds, set to `true` to set expire
399 406 ## at browser close
400 407 #beaker.session.cookie_expires = 3600
401 408
402 409 ###################################
403 410 ## SEARCH INDEXING CONFIGURATION ##
404 411 ###################################
405 412 ## Full text search indexer is available in rhodecode-tools under
406 413 ## `rhodecode-tools index` command
407 414
408 415 ## WHOOSH Backend, doesn't require additional services to run
409 416 ## it works good with few dozen repos
410 417 search.module = rhodecode.lib.index.whoosh
411 418 search.location = %(here)s/data/index
412 419
413 420 ########################################
414 421 ### CHANNELSTREAM CONFIG ####
415 422 ########################################
416 423 ## channelstream enables persistent connections and live notification
417 424 ## in the system. It's also used by the chat system
418 425
419 426 channelstream.enabled = false
420 427
421 428 ## server address for channelstream server on the backend
422 429 channelstream.server = 127.0.0.1:9800
423 430
424 431 ## location of the channelstream server from outside world
425 432 ## use ws:// for http or wss:// for https. This address needs to be handled
426 433 ## by external HTTP server such as Nginx or Apache
427 434 ## see nginx/apache configuration examples in our docs
428 435 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
429 436 channelstream.secret = secret
430 437 channelstream.history.location = %(here)s/channelstream_history
431 438
432 439 ## Internal application path that Javascript uses to connect into.
433 440 ## If you use proxy-prefix the prefix should be added before /_channelstream
434 441 channelstream.proxy_path = /_channelstream
435 442
436 443
437 444 ###################################
438 445 ## APPENLIGHT CONFIG ##
439 446 ###################################
440 447
441 448 ## Appenlight is tailored to work with RhodeCode, see
442 449 ## http://appenlight.com for details how to obtain an account
443 450
444 451 ## appenlight integration enabled
445 452 appenlight = false
446 453
447 454 appenlight.server_url = https://api.appenlight.com
448 455 appenlight.api_key = YOUR_API_KEY
449 456 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
450 457
451 458 # used for JS client
452 459 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
453 460
454 461 ## TWEAK AMOUNT OF INFO SENT HERE
455 462
456 463 ## enables 404 error logging (default False)
457 464 appenlight.report_404 = false
458 465
459 466 ## time in seconds after request is considered being slow (default 1)
460 467 appenlight.slow_request_time = 1
461 468
462 469 ## record slow requests in application
463 470 ## (needs to be enabled for slow datastore recording and time tracking)
464 471 appenlight.slow_requests = true
465 472
466 473 ## enable hooking to application loggers
467 474 appenlight.logging = true
468 475
469 476 ## minimum log level for log capture
470 477 appenlight.logging.level = WARNING
471 478
472 479 ## send logs only from erroneous/slow requests
473 480 ## (saves API quota for intensive logging)
474 481 appenlight.logging_on_error = false
475 482
476 483 ## list of additonal keywords that should be grabbed from environ object
477 484 ## can be string with comma separated list of words in lowercase
478 485 ## (by default client will always send following info:
479 486 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
480 487 ## start with HTTP* this list be extended with additional keywords here
481 488 appenlight.environ_keys_whitelist =
482 489
483 490 ## list of keywords that should be blanked from request object
484 491 ## can be string with comma separated list of words in lowercase
485 492 ## (by default client will always blank keys that contain following words
486 493 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
487 494 ## this list be extended with additional keywords set here
488 495 appenlight.request_keys_blacklist =
489 496
490 497 ## list of namespaces that should be ignores when gathering log entries
491 498 ## can be string with comma separated list of namespaces
492 499 ## (by default the client ignores own entries: appenlight_client.client)
493 500 appenlight.log_namespace_blacklist =
494 501
495 502 # enable debug style page
496 503 debug_style = true
497 504
498 505 ###########################################
499 506 ### MAIN RHODECODE DATABASE CONFIG ###
500 507 ###########################################
501 508 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
502 509 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
503 510 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
504 511 # pymysql is an alternative driver for MySQL, use in case of problems with default one
505 512 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
506 513
507 514 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
508 515
509 516 # see sqlalchemy docs for other advanced settings
510 517
511 518 ## print the sql statements to output
512 519 sqlalchemy.db1.echo = false
513 520 ## recycle the connections after this amount of seconds
514 521 sqlalchemy.db1.pool_recycle = 3600
515 522 sqlalchemy.db1.convert_unicode = true
516 523
517 524 ## the number of connections to keep open inside the connection pool.
518 525 ## 0 indicates no limit
519 526 #sqlalchemy.db1.pool_size = 5
520 527
521 528 ## the number of connections to allow in connection pool "overflow", that is
522 529 ## connections that can be opened above and beyond the pool_size setting,
523 530 ## which defaults to five.
524 531 #sqlalchemy.db1.max_overflow = 10
525 532
526 533 ## Connection check ping, used to detect broken database connections
527 534 ## could be enabled to better handle cases if MySQL has gone away errors
528 535 #sqlalchemy.db1.ping_connection = true
529 536
530 537 ##################
531 538 ### VCS CONFIG ###
532 539 ##################
533 540 vcs.server.enable = true
534 541 vcs.server = localhost:9900
535 542
536 543 ## Web server connectivity protocol, responsible for web based VCS operatations
537 544 ## Available protocols are:
538 545 ## `http` - use http-rpc backend (default)
539 546 vcs.server.protocol = http
540 547
541 548 ## Push/Pull operations protocol, available options are:
542 549 ## `http` - use http-rpc backend (default)
543 550 vcs.scm_app_implementation = http
544 551
545 552 ## Push/Pull operations hooks protocol, available options are:
546 553 ## `http` - use http-rpc backend (default)
547 554 vcs.hooks.protocol = http
548 555
549 556 ## Host on which this instance is listening for hooks. If vcsserver is in other location
550 557 ## this should be adjusted.
551 558 vcs.hooks.host = 127.0.0.1
552 559
553 560 vcs.server.log_level = debug
554 561 ## Start VCSServer with this instance as a subprocess, useful for development
555 562 vcs.start_server = false
556 563
557 564 ## List of enabled VCS backends, available options are:
558 565 ## `hg` - mercurial
559 566 ## `git` - git
560 567 ## `svn` - subversion
561 568 vcs.backends = hg, git, svn
562 569
563 570 vcs.connection_timeout = 3600
564 571 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
565 572 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
566 573 #vcs.svn.compatible_version = pre-1.8-compatible
567 574
568 575
569 576 ############################################################
570 577 ### Subversion proxy support (mod_dav_svn) ###
571 578 ### Maps RhodeCode repo groups into SVN paths for Apache ###
572 579 ############################################################
573 580 ## Enable or disable the config file generation.
574 581 svn.proxy.generate_config = false
575 582 ## Generate config file with `SVNListParentPath` set to `On`.
576 583 svn.proxy.list_parent_path = true
577 584 ## Set location and file name of generated config file.
578 585 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
579 586 ## alternative mod_dav config template. This needs to be a mako template
580 587 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
581 588 ## Used as a prefix to the `Location` block in the generated config file.
582 589 ## In most cases it should be set to `/`.
583 590 svn.proxy.location_root = /
584 591 ## Command to reload the mod dav svn configuration on change.
585 592 ## Example: `/etc/init.d/apache2 reload`
586 593 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
587 594 ## If the timeout expires before the reload command finishes, the command will
588 595 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
589 596 #svn.proxy.reload_timeout = 10
590 597
591 598 ############################################################
592 599 ### SSH Support Settings ###
593 600 ############################################################
594 601
595 602 ## Defines if a custom authorized_keys file should be created and written on
596 603 ## any change user ssh keys. Setting this to false also disables posibility
597 604 ## of adding SSH keys by users from web interface. Super admins can still
598 605 ## manage SSH Keys.
599 606 ssh.generate_authorized_keyfile = false
600 607
601 608 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
602 609 # ssh.authorized_keys_ssh_opts =
603 610
604 611 ## Path to the authrozied_keys file where the generate entries are placed.
605 612 ## It is possible to have multiple key files specified in `sshd_config` e.g.
606 613 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
607 614 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
608 615
609 616 ## Command to execute the SSH wrapper. The binary is available in the
610 617 ## rhodecode installation directory.
611 618 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
612 619 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
613 620
614 621 ## Allow shell when executing the ssh-wrapper command
615 622 ssh.wrapper_cmd_allow_shell = false
616 623
617 624 ## Enables logging, and detailed output send back to the client during SSH
618 625 ## operations. Usefull for debugging, shouldn't be used in production.
619 626 ssh.enable_debug_logging = true
620 627
621 628 ## Paths to binary executable, by default they are the names, but we can
622 629 ## override them if we want to use a custom one
623 630 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
624 631 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
625 632 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
626 633
627 634
628 635 ## Dummy marker to add new entries after.
629 636 ## Add any custom entries below. Please don't remove.
630 637 custom.conf = 1
631 638
632 639
633 640 ################################
634 641 ### LOGGING CONFIGURATION ####
635 642 ################################
636 643 [loggers]
637 644 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
638 645
639 646 [handlers]
640 647 keys = console, console_sql
641 648
642 649 [formatters]
643 650 keys = generic, color_formatter, color_formatter_sql
644 651
645 652 #############
646 653 ## LOGGERS ##
647 654 #############
648 655 [logger_root]
649 656 level = NOTSET
650 657 handlers = console
651 658
652 659 [logger_sqlalchemy]
653 660 level = INFO
654 661 handlers = console_sql
655 662 qualname = sqlalchemy.engine
656 663 propagate = 0
657 664
658 665 [logger_beaker]
659 666 level = DEBUG
660 667 handlers =
661 668 qualname = beaker.container
662 669 propagate = 1
663 670
664 671 [logger_rhodecode]
665 672 level = DEBUG
666 673 handlers =
667 674 qualname = rhodecode
668 675 propagate = 1
669 676
670 677 [logger_ssh_wrapper]
671 678 level = DEBUG
672 679 handlers =
673 680 qualname = ssh_wrapper
674 681 propagate = 1
675 682
676 683 [logger_celery]
677 684 level = DEBUG
678 685 handlers =
679 686 qualname = celery
680 687
681 688
682 689 ##############
683 690 ## HANDLERS ##
684 691 ##############
685 692
686 693 [handler_console]
687 694 class = StreamHandler
688 695 args = (sys.stderr, )
689 696 level = DEBUG
690 697 formatter = color_formatter
691 698
692 699 [handler_console_sql]
693 700 # "level = DEBUG" logs SQL queries and results.
694 701 # "level = INFO" logs SQL queries.
695 702 # "level = WARN" logs neither. (Recommended for production systems.)
696 703 class = StreamHandler
697 704 args = (sys.stderr, )
698 705 level = WARN
699 706 formatter = color_formatter_sql
700 707
701 708 ################
702 709 ## FORMATTERS ##
703 710 ################
704 711
705 712 [formatter_generic]
706 713 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
707 714 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
708 715 datefmt = %Y-%m-%d %H:%M:%S
709 716
710 717 [formatter_color_formatter]
711 718 class = rhodecode.lib.logging_formatter.ColorFormatter
712 719 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
713 720 datefmt = %Y-%m-%d %H:%M:%S
714 721
715 722 [formatter_color_formatter_sql]
716 723 class = rhodecode.lib.logging_formatter.ColorFormatterSql
717 724 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
718 725 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,691 +1,698 b''
1 1
2 2
3 3 ################################################################################
4 4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 5 ################################################################################
6 6
7 7 [DEFAULT]
8 8 ## Debug flag sets all loggers to debug, and enables request tracking
9 9 debug = false
10 10
11 11 ################################################################################
12 12 ## EMAIL CONFIGURATION ##
13 13 ## Uncomment and replace with the email address which should receive ##
14 14 ## any error reports after an application crash ##
15 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 16 ################################################################################
17 17
18 18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 19 #email_prefix = [RhodeCode]
20 20
21 21 ## email FROM address all mails will be sent
22 22 #app_email_from = rhodecode-noreply@localhost
23 23
24 24 #smtp_server = mail.server.com
25 25 #smtp_username =
26 26 #smtp_password =
27 27 #smtp_port =
28 28 #smtp_use_tls = false
29 29 #smtp_use_ssl = true
30 30
31 31 [server:main]
32 32 ## COMMON ##
33 33 host = 127.0.0.1
34 34 port = 5000
35 35
36 36 ###########################################################
37 37 ## WAITRESS WSGI SERVER - Recommended for Development ####
38 38 ###########################################################
39 39
40 40 #use = egg:waitress#main
41 41 ## number of worker threads
42 42 #threads = 5
43 43 ## MAX BODY SIZE 100GB
44 44 #max_request_body_size = 107374182400
45 45 ## Use poll instead of select, fixes file descriptors limits problems.
46 46 ## May not work on old windows systems.
47 47 #asyncore_use_poll = true
48 48
49 49
50 50 ##########################
51 51 ## GUNICORN WSGI SERVER ##
52 52 ##########################
53 53 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
54 54
55 55 use = egg:gunicorn#main
56 56 ## Sets the number of process workers. More workers means more concurent connections
57 57 ## RhodeCode can handle at the same time. Each additional worker also it increases
58 58 ## memory usage as each has it's own set of caches.
59 59 ## Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
60 60 ## than 8-10 unless for really big deployments .e.g 700-1000 users.
61 61 ## `instance_id = *` must be set in the [app:main] section below (which is the default)
62 62 ## when using more than 1 worker.
63 63 workers = 2
64 64 ## process name visible in process list
65 65 proc_name = rhodecode
66 66 ## type of worker class, one of sync, gevent
67 67 ## recommended for bigger setup is using of of other than sync one
68 68 worker_class = gevent
69 69 ## The maximum number of simultaneous clients. Valid only for Gevent
70 70 worker_connections = 10
71 71 ## max number of requests that worker will handle before being gracefully
72 72 ## restarted, could prevent memory leaks
73 73 max_requests = 1000
74 74 max_requests_jitter = 30
75 75 ## amount of time a worker can spend with handling a request before it
76 76 ## gets killed and restarted. Set to 6hrs
77 77 timeout = 21600
78 78
79 79
80 80 ## prefix middleware for RhodeCode.
81 81 ## recommended when using proxy setup.
82 82 ## allows to set RhodeCode under a prefix in server.
83 83 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
84 84 ## And set your prefix like: `prefix = /custom_prefix`
85 85 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
86 86 ## to make your cookies only work on prefix url
87 87 [filter:proxy-prefix]
88 88 use = egg:PasteDeploy#prefix
89 89 prefix = /
90 90
91 91 [app:main]
92 92 ## The %(here)s variable will be replaced with the absolute path of parent directory
93 93 ## of this file
94 94 ## In addition ENVIRONMENT variables usage is possible, e.g
95 95 ## sqlalchemy.db1.url = {ENV_RC_DB_URL}
96 96
97 97 use = egg:rhodecode-enterprise-ce
98 98
99 99 ## enable proxy prefix middleware, defined above
100 100 #filter-with = proxy-prefix
101 101
102 102 ## encryption key used to encrypt social plugin tokens,
103 103 ## remote_urls with credentials etc, if not set it defaults to
104 104 ## `beaker.session.secret`
105 105 #rhodecode.encrypted_values.secret =
106 106
107 107 ## decryption strict mode (enabled by default). It controls if decryption raises
108 108 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
109 109 #rhodecode.encrypted_values.strict = false
110 110
111 111 ## return gzipped responses from Rhodecode (static files/application)
112 112 gzip_responses = false
113 113
114 114 ## autogenerate javascript routes file on startup
115 115 generate_js_files = false
116 116
117 117 ## System global default language.
118 118 ## All available languages: en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
119 119 lang = en
120 120
121 121 ## Perform a full repository scan and import on each server start.
122 122 ## Settings this to true could lead to very long startup time.
123 123 startup.import_repos = false
124 124
125 125 ## Uncomment and set this path to use archive download cache.
126 126 ## Once enabled, generated archives will be cached at this location
127 127 ## and served from the cache during subsequent requests for the same archive of
128 128 ## the repository.
129 129 #archive_cache_dir = /tmp/tarballcache
130 130
131 131 ## URL at which the application is running. This is used for bootstraping
132 132 ## requests in context when no web request is available. Used in ishell, or
133 133 ## SSH calls. Set this for events to receive proper url for SSH calls.
134 134 app.base_url = http://rhodecode.local
135 135
136 136 ## Unique application ID. Should be a random unique string for security.
137 137 app_instance_uuid = rc-production
138 138
139 139 ## Cut off limit for large diffs (size in bytes). If overall diff size on
140 140 ## commit, or pull request exceeds this limit this diff will be displayed
141 141 ## partially. E.g 512000 == 512Kb
142 142 cut_off_limit_diff = 512000
143 143
144 144 ## Cut off limit for large files inside diffs (size in bytes). Each individual
145 145 ## file inside diff which exceeds this limit will be displayed partially.
146 146 ## E.g 128000 == 128Kb
147 147 cut_off_limit_file = 128000
148 148
149 149 ## use cached version of vcs repositories everywhere. Recommended to be `true`
150 150 vcs_full_cache = true
151 151
152 152 ## Force https in RhodeCode, fixes https redirects, assumes it's always https.
153 153 ## Normally this is controlled by proper http flags sent from http server
154 154 force_https = false
155 155
156 156 ## use Strict-Transport-Security headers
157 157 use_htsts = false
158 158
159 159 ## git rev filter option, --all is the default filter, if you need to
160 160 ## hide all refs in changelog switch this to --branches --tags
161 161 git_rev_filter = --branches --tags
162 162
163 163 # Set to true if your repos are exposed using the dumb protocol
164 164 git_update_server_info = false
165 165
166 166 ## RSS/ATOM feed options
167 167 rss_cut_off_limit = 256000
168 168 rss_items_per_page = 10
169 169 rss_include_diff = false
170 170
171 171 ## gist URL alias, used to create nicer urls for gist. This should be an
172 172 ## url that does rewrites to _admin/gists/{gistid}.
173 173 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
174 174 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
175 175 gist_alias_url =
176 176
177 177 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
178 178 ## used for access.
179 179 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
180 180 ## came from the the logged in user who own this authentication token.
181 181 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
182 182 ## authentication token. Such view would be only accessible when used together
183 183 ## with this authentication token
184 184 ##
185 185 ## list of all views can be found under `/_admin/permissions/auth_token_access`
186 186 ## The list should be "," separated and on a single line.
187 187 ##
188 188 ## Most common views to enable:
189 189 # RepoCommitsView:repo_commit_download
190 190 # RepoCommitsView:repo_commit_patch
191 191 # RepoCommitsView:repo_commit_raw
192 192 # RepoCommitsView:repo_commit_raw@TOKEN
193 193 # RepoFilesView:repo_files_diff
194 194 # RepoFilesView:repo_archivefile
195 195 # RepoFilesView:repo_file_raw
196 196 # GistView:*
197 197 api_access_controllers_whitelist =
198 198
199 199 ## Default encoding used to convert from and to unicode
200 200 ## can be also a comma separated list of encoding in case of mixed encodings
201 201 default_encoding = UTF-8
202 202
203 203 ## instance-id prefix
204 204 ## a prefix key for this instance used for cache invalidation when running
205 205 ## multiple instances of rhodecode, make sure it's globally unique for
206 206 ## all running rhodecode instances. Leave empty if you don't use it
207 207 instance_id =
208 208
209 209 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
210 210 ## of an authentication plugin also if it is disabled by it's settings.
211 211 ## This could be useful if you are unable to log in to the system due to broken
212 212 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
213 213 ## module to log in again and fix the settings.
214 214 ##
215 215 ## Available builtin plugin IDs (hash is part of the ID):
216 216 ## egg:rhodecode-enterprise-ce#rhodecode
217 217 ## egg:rhodecode-enterprise-ce#pam
218 218 ## egg:rhodecode-enterprise-ce#ldap
219 219 ## egg:rhodecode-enterprise-ce#jasig_cas
220 220 ## egg:rhodecode-enterprise-ce#headers
221 221 ## egg:rhodecode-enterprise-ce#crowd
222 222 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
223 223
224 224 ## alternative return HTTP header for failed authentication. Default HTTP
225 225 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
226 226 ## handling that causing a series of failed authentication calls.
227 227 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
228 228 ## This will be served instead of default 401 on bad authnetication
229 229 auth_ret_code =
230 230
231 231 ## use special detection method when serving auth_ret_code, instead of serving
232 232 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
233 233 ## and then serve auth_ret_code to clients
234 234 auth_ret_code_detection = false
235 235
236 236 ## locking return code. When repository is locked return this HTTP code. 2XX
237 237 ## codes don't break the transactions while 4XX codes do
238 238 lock_ret_code = 423
239 239
240 240 ## allows to change the repository location in settings page
241 241 allow_repo_location_change = true
242 242
243 243 ## allows to setup custom hooks in settings page
244 244 allow_custom_hooks_settings = true
245 245
246 246 ## Generated license token required for EE edition license.
247 247 ## New generated token value can be found in Admin > settings > license page.
248 248 license_token =
249 249
250 250 ## supervisor connection uri, for managing supervisor and logs.
251 251 supervisor.uri =
252 252 ## supervisord group name/id we only want this RC instance to handle
253 253 supervisor.group_id = prod
254 254
255 255 ## Display extended labs settings
256 256 labs_settings_active = true
257 257
258 258 ## Custom exception store path, defaults to TMPDIR
259 259 ## This is used to store exception from RhodeCode in shared directory
260 260 #exception_tracker.store_path =
261 261
262 ## File store configuration. This is used to store and serve uploaded files
263 file_store.enabled = true
264 ## backend, only available one is local
265 file_store.backend = local
266 ## path to store the uploaded binaries
267 file_store.storage_path = %(here)s/data/file_store
268
262 269
263 270 ####################################
264 271 ### CELERY CONFIG ####
265 272 ####################################
266 273 ## run: /path/to/celery worker \
267 274 ## -E --beat --app rhodecode.lib.celerylib.loader \
268 275 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
269 276 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
270 277
271 278 use_celery = false
272 279
273 280 ## connection url to the message broker (default rabbitmq)
274 281 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
275 282
276 283 ## maximum tasks to execute before worker restart
277 284 celery.max_tasks_per_child = 100
278 285
279 286 ## tasks will never be sent to the queue, but executed locally instead.
280 287 celery.task_always_eager = false
281 288
282 289 #####################################
283 290 ### DOGPILE CACHE ####
284 291 #####################################
285 292 ## Default cache dir for caches. Putting this into a ramdisk
286 293 ## can boost performance, eg. /tmpfs/data_ramdisk, however this directory might require
287 294 ## large amount of space
288 295 cache_dir = %(here)s/data
289 296
290 297 ## `cache_perms` cache settings for permission tree, auth TTL.
291 298 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
292 299 rc_cache.cache_perms.expiration_time = 300
293 300
294 301 ## alternative `cache_perms` redis backend with distributed lock
295 302 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
296 303 #rc_cache.cache_perms.expiration_time = 300
297 304 ## redis_expiration_time needs to be greater then expiration_time
298 305 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
299 306 #rc_cache.cache_perms.arguments.socket_timeout = 30
300 307 #rc_cache.cache_perms.arguments.host = localhost
301 308 #rc_cache.cache_perms.arguments.port = 6379
302 309 #rc_cache.cache_perms.arguments.db = 0
303 310 #rc_cache.cache_perms.arguments.distributed_lock = true
304 311
305 312 ## `cache_repo` cache settings for FileTree, Readme, RSS FEEDS
306 313 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
307 314 rc_cache.cache_repo.expiration_time = 2592000
308 315
309 316 ## alternative `cache_repo` redis backend with distributed lock
310 317 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
311 318 #rc_cache.cache_repo.expiration_time = 2592000
312 319 ## redis_expiration_time needs to be greater then expiration_time
313 320 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
314 321 #rc_cache.cache_repo.arguments.socket_timeout = 30
315 322 #rc_cache.cache_repo.arguments.host = localhost
316 323 #rc_cache.cache_repo.arguments.port = 6379
317 324 #rc_cache.cache_repo.arguments.db = 1
318 325 #rc_cache.cache_repo.arguments.distributed_lock = true
319 326
320 327 ## cache settings for SQL queries, this needs to use memory type backend
321 328 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
322 329 rc_cache.sql_cache_short.expiration_time = 30
323 330
324 331 ## `cache_repo_longterm` cache for repo object instances, this needs to use memory
325 332 ## type backend as the objects kept are not pickle serializable
326 333 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
327 334 ## by default we use 96H, this is using invalidation on push anyway
328 335 rc_cache.cache_repo_longterm.expiration_time = 345600
329 336 ## max items in LRU cache, reduce this number to save memory, and expire last used
330 337 ## cached objects
331 338 rc_cache.cache_repo_longterm.max_size = 10000
332 339
333 340
334 341 ####################################
335 342 ### BEAKER SESSION ####
336 343 ####################################
337 344
338 345 ## .session.type is type of storage options for the session, current allowed
339 346 ## types are file, ext:memcached, ext:redis, ext:database, and memory (default).
340 347 beaker.session.type = file
341 348 beaker.session.data_dir = %(here)s/data/sessions
342 349
343 350 ## db based session, fast, and allows easy management over logged in users
344 351 #beaker.session.type = ext:database
345 352 #beaker.session.table_name = db_session
346 353 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
347 354 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
348 355 #beaker.session.sa.pool_recycle = 3600
349 356 #beaker.session.sa.echo = false
350 357
351 358 beaker.session.key = rhodecode
352 359 beaker.session.secret = production-rc-uytcxaz
353 360 beaker.session.lock_dir = %(here)s/data/sessions/lock
354 361
355 362 ## Secure encrypted cookie. Requires AES and AES python libraries
356 363 ## you must disable beaker.session.secret to use this
357 364 #beaker.session.encrypt_key = key_for_encryption
358 365 #beaker.session.validate_key = validation_key
359 366
360 367 ## sets session as invalid(also logging out user) if it haven not been
361 368 ## accessed for given amount of time in seconds
362 369 beaker.session.timeout = 2592000
363 370 beaker.session.httponly = true
364 371 ## Path to use for the cookie. Set to prefix if you use prefix middleware
365 372 #beaker.session.cookie_path = /custom_prefix
366 373
367 374 ## uncomment for https secure cookie
368 375 beaker.session.secure = false
369 376
370 377 ## auto save the session to not to use .save()
371 378 beaker.session.auto = false
372 379
373 380 ## default cookie expiration time in seconds, set to `true` to set expire
374 381 ## at browser close
375 382 #beaker.session.cookie_expires = 3600
376 383
377 384 ###################################
378 385 ## SEARCH INDEXING CONFIGURATION ##
379 386 ###################################
380 387 ## Full text search indexer is available in rhodecode-tools under
381 388 ## `rhodecode-tools index` command
382 389
383 390 ## WHOOSH Backend, doesn't require additional services to run
384 391 ## it works good with few dozen repos
385 392 search.module = rhodecode.lib.index.whoosh
386 393 search.location = %(here)s/data/index
387 394
388 395 ########################################
389 396 ### CHANNELSTREAM CONFIG ####
390 397 ########################################
391 398 ## channelstream enables persistent connections and live notification
392 399 ## in the system. It's also used by the chat system
393 400
394 401 channelstream.enabled = false
395 402
396 403 ## server address for channelstream server on the backend
397 404 channelstream.server = 127.0.0.1:9800
398 405
399 406 ## location of the channelstream server from outside world
400 407 ## use ws:// for http or wss:// for https. This address needs to be handled
401 408 ## by external HTTP server such as Nginx or Apache
402 409 ## see nginx/apache configuration examples in our docs
403 410 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
404 411 channelstream.secret = secret
405 412 channelstream.history.location = %(here)s/channelstream_history
406 413
407 414 ## Internal application path that Javascript uses to connect into.
408 415 ## If you use proxy-prefix the prefix should be added before /_channelstream
409 416 channelstream.proxy_path = /_channelstream
410 417
411 418
412 419 ###################################
413 420 ## APPENLIGHT CONFIG ##
414 421 ###################################
415 422
416 423 ## Appenlight is tailored to work with RhodeCode, see
417 424 ## http://appenlight.com for details how to obtain an account
418 425
419 426 ## appenlight integration enabled
420 427 appenlight = false
421 428
422 429 appenlight.server_url = https://api.appenlight.com
423 430 appenlight.api_key = YOUR_API_KEY
424 431 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
425 432
426 433 # used for JS client
427 434 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
428 435
429 436 ## TWEAK AMOUNT OF INFO SENT HERE
430 437
431 438 ## enables 404 error logging (default False)
432 439 appenlight.report_404 = false
433 440
434 441 ## time in seconds after request is considered being slow (default 1)
435 442 appenlight.slow_request_time = 1
436 443
437 444 ## record slow requests in application
438 445 ## (needs to be enabled for slow datastore recording and time tracking)
439 446 appenlight.slow_requests = true
440 447
441 448 ## enable hooking to application loggers
442 449 appenlight.logging = true
443 450
444 451 ## minimum log level for log capture
445 452 appenlight.logging.level = WARNING
446 453
447 454 ## send logs only from erroneous/slow requests
448 455 ## (saves API quota for intensive logging)
449 456 appenlight.logging_on_error = false
450 457
451 458 ## list of additonal keywords that should be grabbed from environ object
452 459 ## can be string with comma separated list of words in lowercase
453 460 ## (by default client will always send following info:
454 461 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
455 462 ## start with HTTP* this list be extended with additional keywords here
456 463 appenlight.environ_keys_whitelist =
457 464
458 465 ## list of keywords that should be blanked from request object
459 466 ## can be string with comma separated list of words in lowercase
460 467 ## (by default client will always blank keys that contain following words
461 468 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
462 469 ## this list be extended with additional keywords set here
463 470 appenlight.request_keys_blacklist =
464 471
465 472 ## list of namespaces that should be ignores when gathering log entries
466 473 ## can be string with comma separated list of namespaces
467 474 ## (by default the client ignores own entries: appenlight_client.client)
468 475 appenlight.log_namespace_blacklist =
469 476
470 477
471 478 ###########################################
472 479 ### MAIN RHODECODE DATABASE CONFIG ###
473 480 ###########################################
474 481 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
475 482 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
476 483 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
477 484 # pymysql is an alternative driver for MySQL, use in case of problems with default one
478 485 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
479 486
480 487 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
481 488
482 489 # see sqlalchemy docs for other advanced settings
483 490
484 491 ## print the sql statements to output
485 492 sqlalchemy.db1.echo = false
486 493 ## recycle the connections after this amount of seconds
487 494 sqlalchemy.db1.pool_recycle = 3600
488 495 sqlalchemy.db1.convert_unicode = true
489 496
490 497 ## the number of connections to keep open inside the connection pool.
491 498 ## 0 indicates no limit
492 499 #sqlalchemy.db1.pool_size = 5
493 500
494 501 ## the number of connections to allow in connection pool "overflow", that is
495 502 ## connections that can be opened above and beyond the pool_size setting,
496 503 ## which defaults to five.
497 504 #sqlalchemy.db1.max_overflow = 10
498 505
499 506 ## Connection check ping, used to detect broken database connections
500 507 ## could be enabled to better handle cases if MySQL has gone away errors
501 508 #sqlalchemy.db1.ping_connection = true
502 509
503 510 ##################
504 511 ### VCS CONFIG ###
505 512 ##################
506 513 vcs.server.enable = true
507 514 vcs.server = localhost:9900
508 515
509 516 ## Web server connectivity protocol, responsible for web based VCS operatations
510 517 ## Available protocols are:
511 518 ## `http` - use http-rpc backend (default)
512 519 vcs.server.protocol = http
513 520
514 521 ## Push/Pull operations protocol, available options are:
515 522 ## `http` - use http-rpc backend (default)
516 523 vcs.scm_app_implementation = http
517 524
518 525 ## Push/Pull operations hooks protocol, available options are:
519 526 ## `http` - use http-rpc backend (default)
520 527 vcs.hooks.protocol = http
521 528
522 529 ## Host on which this instance is listening for hooks. If vcsserver is in other location
523 530 ## this should be adjusted.
524 531 vcs.hooks.host = 127.0.0.1
525 532
526 533 vcs.server.log_level = info
527 534 ## Start VCSServer with this instance as a subprocess, useful for development
528 535 vcs.start_server = false
529 536
530 537 ## List of enabled VCS backends, available options are:
531 538 ## `hg` - mercurial
532 539 ## `git` - git
533 540 ## `svn` - subversion
534 541 vcs.backends = hg, git, svn
535 542
536 543 vcs.connection_timeout = 3600
537 544 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
538 545 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
539 546 #vcs.svn.compatible_version = pre-1.8-compatible
540 547
541 548
542 549 ############################################################
543 550 ### Subversion proxy support (mod_dav_svn) ###
544 551 ### Maps RhodeCode repo groups into SVN paths for Apache ###
545 552 ############################################################
546 553 ## Enable or disable the config file generation.
547 554 svn.proxy.generate_config = false
548 555 ## Generate config file with `SVNListParentPath` set to `On`.
549 556 svn.proxy.list_parent_path = true
550 557 ## Set location and file name of generated config file.
551 558 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
552 559 ## alternative mod_dav config template. This needs to be a mako template
553 560 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
554 561 ## Used as a prefix to the `Location` block in the generated config file.
555 562 ## In most cases it should be set to `/`.
556 563 svn.proxy.location_root = /
557 564 ## Command to reload the mod dav svn configuration on change.
558 565 ## Example: `/etc/init.d/apache2 reload`
559 566 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
560 567 ## If the timeout expires before the reload command finishes, the command will
561 568 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
562 569 #svn.proxy.reload_timeout = 10
563 570
564 571 ############################################################
565 572 ### SSH Support Settings ###
566 573 ############################################################
567 574
568 575 ## Defines if a custom authorized_keys file should be created and written on
569 576 ## any change user ssh keys. Setting this to false also disables posibility
570 577 ## of adding SSH keys by users from web interface. Super admins can still
571 578 ## manage SSH Keys.
572 579 ssh.generate_authorized_keyfile = false
573 580
574 581 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
575 582 # ssh.authorized_keys_ssh_opts =
576 583
577 584 ## Path to the authrozied_keys file where the generate entries are placed.
578 585 ## It is possible to have multiple key files specified in `sshd_config` e.g.
579 586 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
580 587 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
581 588
582 589 ## Command to execute the SSH wrapper. The binary is available in the
583 590 ## rhodecode installation directory.
584 591 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
585 592 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
586 593
587 594 ## Allow shell when executing the ssh-wrapper command
588 595 ssh.wrapper_cmd_allow_shell = false
589 596
590 597 ## Enables logging, and detailed output send back to the client during SSH
591 598 ## operations. Usefull for debugging, shouldn't be used in production.
592 599 ssh.enable_debug_logging = false
593 600
594 601 ## Paths to binary executable, by default they are the names, but we can
595 602 ## override them if we want to use a custom one
596 603 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
597 604 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
598 605 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
599 606
600 607
601 608 ## Dummy marker to add new entries after.
602 609 ## Add any custom entries below. Please don't remove.
603 610 custom.conf = 1
604 611
605 612
606 613 ################################
607 614 ### LOGGING CONFIGURATION ####
608 615 ################################
609 616 [loggers]
610 617 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
611 618
612 619 [handlers]
613 620 keys = console, console_sql
614 621
615 622 [formatters]
616 623 keys = generic, color_formatter, color_formatter_sql
617 624
618 625 #############
619 626 ## LOGGERS ##
620 627 #############
621 628 [logger_root]
622 629 level = NOTSET
623 630 handlers = console
624 631
625 632 [logger_sqlalchemy]
626 633 level = INFO
627 634 handlers = console_sql
628 635 qualname = sqlalchemy.engine
629 636 propagate = 0
630 637
631 638 [logger_beaker]
632 639 level = DEBUG
633 640 handlers =
634 641 qualname = beaker.container
635 642 propagate = 1
636 643
637 644 [logger_rhodecode]
638 645 level = DEBUG
639 646 handlers =
640 647 qualname = rhodecode
641 648 propagate = 1
642 649
643 650 [logger_ssh_wrapper]
644 651 level = DEBUG
645 652 handlers =
646 653 qualname = ssh_wrapper
647 654 propagate = 1
648 655
649 656 [logger_celery]
650 657 level = DEBUG
651 658 handlers =
652 659 qualname = celery
653 660
654 661
655 662 ##############
656 663 ## HANDLERS ##
657 664 ##############
658 665
659 666 [handler_console]
660 667 class = StreamHandler
661 668 args = (sys.stderr, )
662 669 level = INFO
663 670 formatter = generic
664 671
665 672 [handler_console_sql]
666 673 # "level = DEBUG" logs SQL queries and results.
667 674 # "level = INFO" logs SQL queries.
668 675 # "level = WARN" logs neither. (Recommended for production systems.)
669 676 class = StreamHandler
670 677 args = (sys.stderr, )
671 678 level = WARN
672 679 formatter = generic
673 680
674 681 ################
675 682 ## FORMATTERS ##
676 683 ################
677 684
678 685 [formatter_generic]
679 686 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
680 687 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
681 688 datefmt = %Y-%m-%d %H:%M:%S
682 689
683 690 [formatter_color_formatter]
684 691 class = rhodecode.lib.logging_formatter.ColorFormatter
685 692 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
686 693 datefmt = %Y-%m-%d %H:%M:%S
687 694
688 695 [formatter_color_formatter_sql]
689 696 class = rhodecode.lib.logging_formatter.ColorFormatterSql
690 697 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
691 698 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,739 +1,740 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import sys
23 23 import logging
24 24 import collections
25 25 import tempfile
26 26 import time
27 27
28 28 from paste.gzipper import make_gzip_middleware
29 29 import pyramid.events
30 30 from pyramid.wsgi import wsgiapp
31 31 from pyramid.authorization import ACLAuthorizationPolicy
32 32 from pyramid.config import Configurator
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.httpexceptions import (
35 35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 36 from pyramid.renderers import render_to_response
37 37
38 38 from rhodecode.model import meta
39 39 from rhodecode.config import patches
40 40 from rhodecode.config import utils as config_utils
41 41 from rhodecode.config.environment import load_pyramid_environment
42 42
43 43 import rhodecode.events
44 44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 45 from rhodecode.lib.request import Request
46 46 from rhodecode.lib.vcs import VCSCommunicationError
47 47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 50 from rhodecode.lib.celerylib.loader import configure_celery
51 51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
53 53 from rhodecode.lib.exc_tracking import store_exception
54 54 from rhodecode.subscribers import (
55 55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 56 write_metadata_if_needed, inject_app_settings)
57 57
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 def is_http_error(response):
63 63 # error which should have traceback
64 64 return response.status_code > 499
65 65
66 66
67 67 def should_load_all():
68 68 """
69 69 Returns if all application components should be loaded. In some cases it's
70 70 desired to skip apps loading for faster shell script execution
71 71 """
72 72 return True
73 73
74 74
75 75 def make_pyramid_app(global_config, **settings):
76 76 """
77 77 Constructs the WSGI application based on Pyramid.
78 78
79 79 Specials:
80 80
81 81 * The application can also be integrated like a plugin via the call to
82 82 `includeme`. This is accompanied with the other utility functions which
83 83 are called. Changing this should be done with great care to not break
84 84 cases when these fragments are assembled from another place.
85 85
86 86 """
87 87
88 88 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
89 89 # will be replaced by the value of the environment variable "NAME" in this case.
90 90 start_time = time.time()
91 91
92 92 debug = asbool(global_config.get('debug'))
93 93 if debug:
94 94 enable_debug()
95 95
96 96 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
97 97
98 98 global_config = _substitute_values(global_config, environ)
99 99 settings = _substitute_values(settings, environ)
100 100
101 101 sanitize_settings_and_apply_defaults(global_config, settings)
102 102
103 103 config = Configurator(settings=settings)
104 104
105 105 # Apply compatibility patches
106 106 patches.inspect_getargspec()
107 107
108 108 load_pyramid_environment(global_config, settings)
109 109
110 110 # Static file view comes first
111 111 includeme_first(config)
112 112
113 113 includeme(config)
114 114
115 115 pyramid_app = config.make_wsgi_app()
116 116 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
117 117 pyramid_app.config = config
118 118
119 119 config.configure_celery(global_config['__file__'])
120 120 # creating the app uses a connection - return it after we are done
121 121 meta.Session.remove()
122 122 total_time = time.time() - start_time
123 123 log.info('Pyramid app `%s` created and configured in %.2fs',
124 124 pyramid_app.func_name, total_time)
125 125
126 126 return pyramid_app
127 127
128 128
129 129 def not_found_view(request):
130 130 """
131 131 This creates the view which should be registered as not-found-view to
132 132 pyramid.
133 133 """
134 134
135 135 if not getattr(request, 'vcs_call', None):
136 136 # handle like regular case with our error_handler
137 137 return error_handler(HTTPNotFound(), request)
138 138
139 139 # handle not found view as a vcs call
140 140 settings = request.registry.settings
141 141 ae_client = getattr(request, 'ae_client', None)
142 142 vcs_app = VCSMiddleware(
143 143 HTTPNotFound(), request.registry, settings,
144 144 appenlight_client=ae_client)
145 145
146 146 return wsgiapp(vcs_app)(None, request)
147 147
148 148
149 149 def error_handler(exception, request):
150 150 import rhodecode
151 151 from rhodecode.lib import helpers
152 152
153 153 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
154 154
155 155 base_response = HTTPInternalServerError()
156 156 # prefer original exception for the response since it may have headers set
157 157 if isinstance(exception, HTTPException):
158 158 base_response = exception
159 159 elif isinstance(exception, VCSCommunicationError):
160 160 base_response = VCSServerUnavailable()
161 161
162 162 if is_http_error(base_response):
163 163 log.exception(
164 164 'error occurred handling this request for path: %s', request.path)
165 165
166 166 error_explanation = base_response.explanation or str(base_response)
167 167 if base_response.status_code == 404:
168 168 error_explanation += " Optionally you don't have permission to access this page."
169 169 c = AttributeDict()
170 170 c.error_message = base_response.status
171 171 c.error_explanation = error_explanation
172 172 c.visual = AttributeDict()
173 173
174 174 c.visual.rhodecode_support_url = (
175 175 request.registry.settings.get('rhodecode_support_url') or
176 176 request.route_url('rhodecode_support')
177 177 )
178 178 c.redirect_time = 0
179 179 c.rhodecode_name = rhodecode_title
180 180 if not c.rhodecode_name:
181 181 c.rhodecode_name = 'Rhodecode'
182 182
183 183 c.causes = []
184 184 if is_http_error(base_response):
185 185 c.causes.append('Server is overloaded.')
186 186 c.causes.append('Server database connection is lost.')
187 187 c.causes.append('Server expected unhandled error.')
188 188
189 189 if hasattr(base_response, 'causes'):
190 190 c.causes = base_response.causes
191 191
192 192 c.messages = helpers.flash.pop_messages(request=request)
193 193
194 194 exc_info = sys.exc_info()
195 195 c.exception_id = id(exc_info)
196 196 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
197 197 or base_response.status_code > 499
198 198 c.exception_id_url = request.route_url(
199 199 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
200 200
201 201 if c.show_exception_id:
202 202 store_exception(c.exception_id, exc_info)
203 203
204 204 response = render_to_response(
205 205 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
206 206 response=base_response)
207 207
208 208 return response
209 209
210 210
211 211 def includeme_first(config):
212 212 # redirect automatic browser favicon.ico requests to correct place
213 213 def favicon_redirect(context, request):
214 214 return HTTPFound(
215 215 request.static_path('rhodecode:public/images/favicon.ico'))
216 216
217 217 config.add_view(favicon_redirect, route_name='favicon')
218 218 config.add_route('favicon', '/favicon.ico')
219 219
220 220 def robots_redirect(context, request):
221 221 return HTTPFound(
222 222 request.static_path('rhodecode:public/robots.txt'))
223 223
224 224 config.add_view(robots_redirect, route_name='robots')
225 225 config.add_route('robots', '/robots.txt')
226 226
227 227 config.add_static_view(
228 228 '_static/deform', 'deform:static')
229 229 config.add_static_view(
230 230 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
231 231
232 232
233 233 def includeme(config):
234 234 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
235 235 settings = config.registry.settings
236 236 config.set_request_factory(Request)
237 237
238 238 # plugin information
239 239 config.registry.rhodecode_plugins = collections.OrderedDict()
240 240
241 241 config.add_directive(
242 242 'register_rhodecode_plugin', register_rhodecode_plugin)
243 243
244 244 config.add_directive('configure_celery', configure_celery)
245 245
246 246 if asbool(settings.get('appenlight', 'false')):
247 247 config.include('appenlight_client.ext.pyramid_tween')
248 248
249 249 load_all = should_load_all()
250 250
251 251 # Includes which are required. The application would fail without them.
252 252 config.include('pyramid_mako')
253 253 config.include('pyramid_beaker')
254 254 config.include('rhodecode.lib.rc_cache')
255 255
256 256 config.include('rhodecode.apps._base.navigation')
257 257 config.include('rhodecode.apps._base.subscribers')
258 258 config.include('rhodecode.tweens')
259 259
260 260 config.include('rhodecode.integrations')
261 261 config.include('rhodecode.authentication')
262 262
263 263 if load_all:
264 264 from rhodecode.authentication import discover_legacy_plugins
265 265 # load CE authentication plugins
266 266 config.include('rhodecode.authentication.plugins.auth_crowd')
267 267 config.include('rhodecode.authentication.plugins.auth_headers')
268 268 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
269 269 config.include('rhodecode.authentication.plugins.auth_ldap')
270 270 config.include('rhodecode.authentication.plugins.auth_pam')
271 271 config.include('rhodecode.authentication.plugins.auth_rhodecode')
272 272 config.include('rhodecode.authentication.plugins.auth_token')
273 273
274 274 # Auto discover authentication plugins and include their configuration.
275 275 discover_legacy_plugins(config)
276 276
277 277 # apps
278 278 config.include('rhodecode.apps._base')
279 279
280 280 if load_all:
281 281 config.include('rhodecode.apps.ops')
282 282 config.include('rhodecode.apps.admin')
283 283 config.include('rhodecode.apps.channelstream')
284 config.include('rhodecode.apps.upload_store')
284 285 config.include('rhodecode.apps.login')
285 286 config.include('rhodecode.apps.home')
286 287 config.include('rhodecode.apps.journal')
287 288 config.include('rhodecode.apps.repository')
288 289 config.include('rhodecode.apps.repo_group')
289 290 config.include('rhodecode.apps.user_group')
290 291 config.include('rhodecode.apps.search')
291 292 config.include('rhodecode.apps.user_profile')
292 293 config.include('rhodecode.apps.user_group_profile')
293 294 config.include('rhodecode.apps.my_account')
294 295 config.include('rhodecode.apps.svn_support')
295 296 config.include('rhodecode.apps.ssh_support')
296 297 config.include('rhodecode.apps.gist')
297 298 config.include('rhodecode.apps.debug_style')
298 299 config.include('rhodecode.api')
299 300
300 301 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
301 302 config.add_translation_dirs('rhodecode:i18n/')
302 303 settings['default_locale_name'] = settings.get('lang', 'en')
303 304
304 305 # Add subscribers.
305 306 config.add_subscriber(inject_app_settings,
306 307 pyramid.events.ApplicationCreated)
307 308 config.add_subscriber(scan_repositories_if_enabled,
308 309 pyramid.events.ApplicationCreated)
309 310 config.add_subscriber(write_metadata_if_needed,
310 311 pyramid.events.ApplicationCreated)
311 312 config.add_subscriber(write_js_routes_if_enabled,
312 313 pyramid.events.ApplicationCreated)
313 314
314 315 # request custom methods
315 316 config.add_request_method(
316 317 'rhodecode.lib.partial_renderer.get_partial_renderer',
317 318 'get_partial_renderer')
318 319
319 320 # Set the authorization policy.
320 321 authz_policy = ACLAuthorizationPolicy()
321 322 config.set_authorization_policy(authz_policy)
322 323
323 324 # Set the default renderer for HTML templates to mako.
324 325 config.add_mako_renderer('.html')
325 326
326 327 config.add_renderer(
327 328 name='json_ext',
328 329 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
329 330
330 331 # include RhodeCode plugins
331 332 includes = aslist(settings.get('rhodecode.includes', []))
332 333 for inc in includes:
333 334 config.include(inc)
334 335
335 336 # custom not found view, if our pyramid app doesn't know how to handle
336 337 # the request pass it to potential VCS handling ap
337 338 config.add_notfound_view(not_found_view)
338 339 if not settings.get('debugtoolbar.enabled', False):
339 340 # disabled debugtoolbar handle all exceptions via the error_handlers
340 341 config.add_view(error_handler, context=Exception)
341 342
342 343 # all errors including 403/404/50X
343 344 config.add_view(error_handler, context=HTTPError)
344 345
345 346
346 347 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
347 348 """
348 349 Apply outer WSGI middlewares around the application.
349 350 """
350 351 registry = config.registry
351 352 settings = registry.settings
352 353
353 354 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
354 355 pyramid_app = HttpsFixup(pyramid_app, settings)
355 356
356 357 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
357 358 pyramid_app, settings)
358 359 registry.ae_client = _ae_client
359 360
360 361 if settings['gzip_responses']:
361 362 pyramid_app = make_gzip_middleware(
362 363 pyramid_app, settings, compress_level=1)
363 364
364 365 # this should be the outer most middleware in the wsgi stack since
365 366 # middleware like Routes make database calls
366 367 def pyramid_app_with_cleanup(environ, start_response):
367 368 try:
368 369 return pyramid_app(environ, start_response)
369 370 finally:
370 371 # Dispose current database session and rollback uncommitted
371 372 # transactions.
372 373 meta.Session.remove()
373 374
374 375 # In a single threaded mode server, on non sqlite db we should have
375 376 # '0 Current Checked out connections' at the end of a request,
376 377 # if not, then something, somewhere is leaving a connection open
377 378 pool = meta.Base.metadata.bind.engine.pool
378 379 log.debug('sa pool status: %s', pool.status())
379 380 log.debug('Request processing finalized')
380 381
381 382 return pyramid_app_with_cleanup
382 383
383 384
384 385 def sanitize_settings_and_apply_defaults(global_config, settings):
385 386 """
386 387 Applies settings defaults and does all type conversion.
387 388
388 389 We would move all settings parsing and preparation into this place, so that
389 390 we have only one place left which deals with this part. The remaining parts
390 391 of the application would start to rely fully on well prepared settings.
391 392
392 393 This piece would later be split up per topic to avoid a big fat monster
393 394 function.
394 395 """
395 396
396 397 settings.setdefault('rhodecode.edition', 'Community Edition')
397 398
398 399 if 'mako.default_filters' not in settings:
399 400 # set custom default filters if we don't have it defined
400 401 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
401 402 settings['mako.default_filters'] = 'h_filter'
402 403
403 404 if 'mako.directories' not in settings:
404 405 mako_directories = settings.setdefault('mako.directories', [
405 406 # Base templates of the original application
406 407 'rhodecode:templates',
407 408 ])
408 409 log.debug(
409 410 "Using the following Mako template directories: %s",
410 411 mako_directories)
411 412
412 413 # Default includes, possible to change as a user
413 414 pyramid_includes = settings.setdefault('pyramid.includes', [
414 415 'rhodecode.lib.middleware.request_wrapper',
415 416 ])
416 417 log.debug(
417 418 "Using the following pyramid.includes: %s",
418 419 pyramid_includes)
419 420
420 421 # TODO: johbo: Re-think this, usually the call to config.include
421 422 # should allow to pass in a prefix.
422 423 settings.setdefault('rhodecode.api.url', '/_admin/api')
423 424 settings.setdefault('__file__', global_config.get('__file__'))
424 425
425 426 # Sanitize generic settings.
426 427 _list_setting(settings, 'default_encoding', 'UTF-8')
427 428 _bool_setting(settings, 'is_test', 'false')
428 429 _bool_setting(settings, 'gzip_responses', 'false')
429 430
430 431 # Call split out functions that sanitize settings for each topic.
431 432 _sanitize_appenlight_settings(settings)
432 433 _sanitize_vcs_settings(settings)
433 434 _sanitize_cache_settings(settings)
434 435
435 436 # configure instance id
436 437 config_utils.set_instance_id(settings)
437 438
438 439 return settings
439 440
440 441
441 442 def enable_debug():
442 443 """
443 444 Helper to enable debug on running instance
444 445 :return:
445 446 """
446 447 import tempfile
447 448 import textwrap
448 449 import logging.config
449 450
450 451 ini_template = textwrap.dedent("""
451 452 #####################################
452 453 ### DEBUG LOGGING CONFIGURATION ####
453 454 #####################################
454 455 [loggers]
455 456 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
456 457
457 458 [handlers]
458 459 keys = console, console_sql
459 460
460 461 [formatters]
461 462 keys = generic, color_formatter, color_formatter_sql
462 463
463 464 #############
464 465 ## LOGGERS ##
465 466 #############
466 467 [logger_root]
467 468 level = NOTSET
468 469 handlers = console
469 470
470 471 [logger_sqlalchemy]
471 472 level = INFO
472 473 handlers = console_sql
473 474 qualname = sqlalchemy.engine
474 475 propagate = 0
475 476
476 477 [logger_beaker]
477 478 level = DEBUG
478 479 handlers =
479 480 qualname = beaker.container
480 481 propagate = 1
481 482
482 483 [logger_rhodecode]
483 484 level = DEBUG
484 485 handlers =
485 486 qualname = rhodecode
486 487 propagate = 1
487 488
488 489 [logger_ssh_wrapper]
489 490 level = DEBUG
490 491 handlers =
491 492 qualname = ssh_wrapper
492 493 propagate = 1
493 494
494 495 [logger_celery]
495 496 level = DEBUG
496 497 handlers =
497 498 qualname = celery
498 499
499 500
500 501 ##############
501 502 ## HANDLERS ##
502 503 ##############
503 504
504 505 [handler_console]
505 506 class = StreamHandler
506 507 args = (sys.stderr, )
507 508 level = DEBUG
508 509 formatter = color_formatter
509 510
510 511 [handler_console_sql]
511 512 # "level = DEBUG" logs SQL queries and results.
512 513 # "level = INFO" logs SQL queries.
513 514 # "level = WARN" logs neither. (Recommended for production systems.)
514 515 class = StreamHandler
515 516 args = (sys.stderr, )
516 517 level = WARN
517 518 formatter = color_formatter_sql
518 519
519 520 ################
520 521 ## FORMATTERS ##
521 522 ################
522 523
523 524 [formatter_generic]
524 525 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
525 526 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
526 527 datefmt = %Y-%m-%d %H:%M:%S
527 528
528 529 [formatter_color_formatter]
529 530 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
530 531 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
531 532 datefmt = %Y-%m-%d %H:%M:%S
532 533
533 534 [formatter_color_formatter_sql]
534 535 class = rhodecode.lib.logging_formatter.ColorFormatterSql
535 536 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
536 537 datefmt = %Y-%m-%d %H:%M:%S
537 538 """)
538 539
539 540 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
540 541 delete=False) as f:
541 542 log.info('Saved Temporary DEBUG config at %s', f.name)
542 543 f.write(ini_template)
543 544
544 545 logging.config.fileConfig(f.name)
545 546 log.debug('DEBUG MODE ON')
546 547 os.remove(f.name)
547 548
548 549
549 550 def _sanitize_appenlight_settings(settings):
550 551 _bool_setting(settings, 'appenlight', 'false')
551 552
552 553
553 554 def _sanitize_vcs_settings(settings):
554 555 """
555 556 Applies settings defaults and does type conversion for all VCS related
556 557 settings.
557 558 """
558 559 _string_setting(settings, 'vcs.svn.compatible_version', '')
559 560 _string_setting(settings, 'git_rev_filter', '--all')
560 561 _string_setting(settings, 'vcs.hooks.protocol', 'http')
561 562 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
562 563 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
563 564 _string_setting(settings, 'vcs.server', '')
564 565 _string_setting(settings, 'vcs.server.log_level', 'debug')
565 566 _string_setting(settings, 'vcs.server.protocol', 'http')
566 567 _bool_setting(settings, 'startup.import_repos', 'false')
567 568 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
568 569 _bool_setting(settings, 'vcs.server.enable', 'true')
569 570 _bool_setting(settings, 'vcs.start_server', 'false')
570 571 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
571 572 _int_setting(settings, 'vcs.connection_timeout', 3600)
572 573
573 574 # Support legacy values of vcs.scm_app_implementation. Legacy
574 575 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
575 576 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
576 577 scm_app_impl = settings['vcs.scm_app_implementation']
577 578 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
578 579 settings['vcs.scm_app_implementation'] = 'http'
579 580
580 581
581 582 def _sanitize_cache_settings(settings):
582 583 temp_store = tempfile.gettempdir()
583 584 default_cache_dir = os.path.join(temp_store, 'rc_cache')
584 585
585 586 # save default, cache dir, and use it for all backends later.
586 587 default_cache_dir = _string_setting(
587 588 settings,
588 589 'cache_dir',
589 590 default_cache_dir, lower=False, default_when_empty=True)
590 591
591 592 # ensure we have our dir created
592 593 if not os.path.isdir(default_cache_dir):
593 594 os.makedirs(default_cache_dir, mode=0o755)
594 595
595 596 # exception store cache
596 597 _string_setting(
597 598 settings,
598 599 'exception_tracker.store_path',
599 600 temp_store, lower=False, default_when_empty=True)
600 601
601 602 # cache_perms
602 603 _string_setting(
603 604 settings,
604 605 'rc_cache.cache_perms.backend',
605 606 'dogpile.cache.rc.file_namespace', lower=False)
606 607 _int_setting(
607 608 settings,
608 609 'rc_cache.cache_perms.expiration_time',
609 610 60)
610 611 _string_setting(
611 612 settings,
612 613 'rc_cache.cache_perms.arguments.filename',
613 614 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
614 615
615 616 # cache_repo
616 617 _string_setting(
617 618 settings,
618 619 'rc_cache.cache_repo.backend',
619 620 'dogpile.cache.rc.file_namespace', lower=False)
620 621 _int_setting(
621 622 settings,
622 623 'rc_cache.cache_repo.expiration_time',
623 624 60)
624 625 _string_setting(
625 626 settings,
626 627 'rc_cache.cache_repo.arguments.filename',
627 628 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
628 629
629 630 # cache_license
630 631 _string_setting(
631 632 settings,
632 633 'rc_cache.cache_license.backend',
633 634 'dogpile.cache.rc.file_namespace', lower=False)
634 635 _int_setting(
635 636 settings,
636 637 'rc_cache.cache_license.expiration_time',
637 638 5*60)
638 639 _string_setting(
639 640 settings,
640 641 'rc_cache.cache_license.arguments.filename',
641 642 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
642 643
643 644 # cache_repo_longterm memory, 96H
644 645 _string_setting(
645 646 settings,
646 647 'rc_cache.cache_repo_longterm.backend',
647 648 'dogpile.cache.rc.memory_lru', lower=False)
648 649 _int_setting(
649 650 settings,
650 651 'rc_cache.cache_repo_longterm.expiration_time',
651 652 345600)
652 653 _int_setting(
653 654 settings,
654 655 'rc_cache.cache_repo_longterm.max_size',
655 656 10000)
656 657
657 658 # sql_cache_short
658 659 _string_setting(
659 660 settings,
660 661 'rc_cache.sql_cache_short.backend',
661 662 'dogpile.cache.rc.memory_lru', lower=False)
662 663 _int_setting(
663 664 settings,
664 665 'rc_cache.sql_cache_short.expiration_time',
665 666 30)
666 667 _int_setting(
667 668 settings,
668 669 'rc_cache.sql_cache_short.max_size',
669 670 10000)
670 671
671 672
672 673 def _int_setting(settings, name, default):
673 674 settings[name] = int(settings.get(name, default))
674 675 return settings[name]
675 676
676 677
677 678 def _bool_setting(settings, name, default):
678 679 input_val = settings.get(name, default)
679 680 if isinstance(input_val, unicode):
680 681 input_val = input_val.encode('utf8')
681 682 settings[name] = asbool(input_val)
682 683 return settings[name]
683 684
684 685
685 686 def _list_setting(settings, name, default):
686 687 raw_value = settings.get(name, default)
687 688
688 689 old_separator = ','
689 690 if old_separator in raw_value:
690 691 # If we get a comma separated list, pass it to our own function.
691 692 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
692 693 else:
693 694 # Otherwise we assume it uses pyramids space/newline separation.
694 695 settings[name] = aslist(raw_value)
695 696 return settings[name]
696 697
697 698
698 699 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
699 700 value = settings.get(name, default)
700 701
701 702 if default_when_empty and not value:
702 703 # use default value when value is empty
703 704 value = default
704 705
705 706 if lower:
706 707 value = value.lower()
707 708 settings[name] = value
708 709 return settings[name]
709 710
710 711
711 712 def _substitute_values(mapping, substitutions):
712 713 result = {}
713 714
714 715 try:
715 716 for key, value in mapping.items():
716 717 # initialize without substitution first
717 718 result[key] = value
718 719
719 720 # Note: Cannot use regular replacements, since they would clash
720 721 # with the implementation of ConfigParser. Using "format" instead.
721 722 try:
722 723 result[key] = value.format(**substitutions)
723 724 except KeyError as e:
724 725 env_var = '{}'.format(e.args[0])
725 726
726 727 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
727 728 'Make sure your environment has {var} set, or remove this ' \
728 729 'variable from config file'.format(key=key, var=env_var)
729 730
730 731 if env_var.startswith('ENV_'):
731 732 raise ValueError(msg)
732 733 else:
733 734 log.warning(msg)
734 735
735 736 except ValueError as e:
736 737 log.warning('Failed to substitute ENV variable: %s', e)
737 738 result = mapping
738 739
739 740 return result
@@ -1,360 +1,362 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('favicon', '/favicon.ico', []);
16 16 pyroutes.register('robots', '/robots.txt', []);
17 17 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 18 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 19 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 20 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 21 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 22 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 23 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 24 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 25 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 26 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 27 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 28 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 29 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 30 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 31 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 32 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
33 33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 38 pyroutes.register('admin_home', '/_admin', []);
39 39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
49 49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
50 50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
51 51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
52 52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
53 53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
54 54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
55 55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
56 56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
57 57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
58 58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
59 59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
60 60 pyroutes.register('admin_settings', '/_admin/settings', []);
61 61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
62 62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
63 63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
64 64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
65 65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
66 66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
67 67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
68 68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
69 69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
70 70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
71 71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
72 72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
73 73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
74 74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
75 75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
76 76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
77 77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
78 78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
79 79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
80 80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
81 81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
82 82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
83 83 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
84 84 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
85 85 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
86 86 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
87 87 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
88 88 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
89 89 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
90 90 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
91 91 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
92 92 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
93 93 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
94 94 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
95 95 pyroutes.register('users', '/_admin/users', []);
96 96 pyroutes.register('users_data', '/_admin/users_data', []);
97 97 pyroutes.register('users_create', '/_admin/users/create', []);
98 98 pyroutes.register('users_new', '/_admin/users/new', []);
99 99 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
100 100 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
101 101 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
102 102 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
103 103 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
104 104 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
105 105 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
106 106 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
107 107 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
108 108 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
109 109 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
110 110 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
111 111 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
112 112 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
113 113 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
114 114 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
115 115 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
116 116 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
117 117 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
118 118 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
119 119 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
120 120 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
121 121 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
122 122 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
123 123 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
124 124 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
125 125 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
126 126 pyroutes.register('user_groups', '/_admin/user_groups', []);
127 127 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
128 128 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
129 129 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
130 130 pyroutes.register('repos', '/_admin/repos', []);
131 131 pyroutes.register('repo_new', '/_admin/repos/new', []);
132 132 pyroutes.register('repo_create', '/_admin/repos/create', []);
133 133 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
134 134 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
135 135 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
136 136 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
137 137 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
138 138 pyroutes.register('channelstream_proxy', '/_channelstream', []);
139 pyroutes.register('upload_file', '/_file_store/upload', []);
140 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
139 141 pyroutes.register('logout', '/_admin/logout', []);
140 142 pyroutes.register('reset_password', '/_admin/password_reset', []);
141 143 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
142 144 pyroutes.register('home', '/', []);
143 145 pyroutes.register('user_autocomplete_data', '/_users', []);
144 146 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
145 147 pyroutes.register('repo_list_data', '/_repos', []);
146 148 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
147 149 pyroutes.register('goto_switcher_data', '/_goto_data', []);
148 150 pyroutes.register('markup_preview', '/_markup_preview', []);
149 151 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
150 152 pyroutes.register('journal', '/_admin/journal', []);
151 153 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
152 154 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
153 155 pyroutes.register('journal_public', '/_admin/public_journal', []);
154 156 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
155 157 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
156 158 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
157 159 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
158 160 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
159 161 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
160 162 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
161 163 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
162 164 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
163 165 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
164 166 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
165 167 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
166 168 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
167 169 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
168 170 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
169 171 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
170 172 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
171 173 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
172 174 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
173 175 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
174 176 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
175 177 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
176 178 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
177 179 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
178 180 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
179 181 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
180 182 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
181 183 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
182 184 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
183 185 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 186 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
185 187 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 188 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 189 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 190 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 191 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 192 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 193 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 194 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 195 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 196 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 197 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 198 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 199 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
198 200 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
199 201 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
200 202 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
201 203 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 204 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
203 205 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 206 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
205 207 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
206 208 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
207 209 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
208 210 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
209 211 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
210 212 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
211 213 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
212 214 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
213 215 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
214 216 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
215 217 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
216 218 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
217 219 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
218 220 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
219 221 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
220 222 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
221 223 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
222 224 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
223 225 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
224 226 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
225 227 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
226 228 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
227 229 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
228 230 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
229 231 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
230 232 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
231 233 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
232 234 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
233 235 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
234 236 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
235 237 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
236 238 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
237 239 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
238 240 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
239 241 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
240 242 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
241 243 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
242 244 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
243 245 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
244 246 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
245 247 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
246 248 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
247 249 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
248 250 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
249 251 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
250 252 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
251 253 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
252 254 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
253 255 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
254 256 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
255 257 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
256 258 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
257 259 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
258 260 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
259 261 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
260 262 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
261 263 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
262 264 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
263 265 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
264 266 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
265 267 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
266 268 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
267 269 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
268 270 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
269 271 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
270 272 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
271 273 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
272 274 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
273 275 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
274 276 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
275 277 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
276 278 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
277 279 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
278 280 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
279 281 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
280 282 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
281 283 pyroutes.register('search', '/_admin/search', []);
282 284 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
283 285 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
284 286 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
285 287 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
286 288 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
287 289 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
288 290 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
289 291 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
290 292 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
291 293 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
292 294 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
293 295 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
294 296 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
295 297 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
296 298 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
297 299 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
298 300 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
299 301 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
300 302 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
301 303 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
302 304 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
303 305 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
304 306 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
305 307 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
306 308 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
307 309 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
308 310 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
309 311 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
310 312 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
311 313 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
312 314 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
313 315 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
314 316 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
315 317 pyroutes.register('gists_show', '/_admin/gists', []);
316 318 pyroutes.register('gists_new', '/_admin/gists/new', []);
317 319 pyroutes.register('gists_create', '/_admin/gists/create', []);
318 320 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
319 321 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
320 322 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
321 323 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
322 324 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
323 325 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
324 326 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
325 327 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
326 328 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
327 329 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
328 330 pyroutes.register('apiv2', '/_admin/api', []);
329 331 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
330 332 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
331 333 pyroutes.register('login', '/_admin/login', []);
332 334 pyroutes.register('register', '/_admin/register', []);
333 335 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
334 336 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
335 337 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
336 338 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
337 339 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
338 340 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
339 341 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
340 342 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
341 343 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
342 344 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
343 345 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
344 346 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
345 347 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
346 348 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
347 349 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
348 350 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
349 351 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
350 352 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
351 353 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
352 354 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
353 355 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
354 356 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
355 357 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
356 358 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
357 359 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
358 360 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
359 361 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
360 362 }
@@ -1,458 +1,465 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import threading
22 22 import time
23 23 import logging
24 24 import os.path
25 25 import subprocess32
26 26 import tempfile
27 27 import urllib2
28 28 from lxml.html import fromstring, tostring
29 29 from lxml.cssselect import CSSSelector
30 30 from urlparse import urlparse, parse_qsl
31 31 from urllib import unquote_plus
32 32 import webob
33 33
34 34 from webtest.app import TestResponse, TestApp, string_types
35 35 from webtest.compat import print_stderr
36 36
37 37 import pytest
38 38 import rc_testdata
39 39
40 40 from rhodecode.model.db import User, Repository
41 41 from rhodecode.model.meta import Session
42 42 from rhodecode.model.scm import ScmModel
43 43 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
44 44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 45 from rhodecode.tests import login_user_session
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class CustomTestResponse(TestResponse):
51 51 def _save_output(self, out):
52 52 f = tempfile.NamedTemporaryFile(
53 53 delete=False, prefix='rc-test-', suffix='.html')
54 54 f.write(out)
55 55 return f.name
56 56
57 57 def mustcontain(self, *strings, **kw):
58 58 """
59 59 Assert that the response contains all of the strings passed
60 60 in as arguments.
61 61
62 62 Equivalent to::
63 63
64 64 assert string in res
65 65 """
66 66 if 'no' in kw:
67 67 no = kw['no']
68 68 del kw['no']
69 69 if isinstance(no, string_types):
70 70 no = [no]
71 71 else:
72 72 no = []
73 73 if kw:
74 74 raise TypeError(
75 75 "The only keyword argument allowed is 'no' got %s" % kw)
76 76
77 77 f = self._save_output(str(self))
78 78
79 79 for s in strings:
80 80 if not s in self:
81 81 print_stderr("Actual response (no %r):" % s)
82 82 print_stderr(str(self))
83 83 raise IndexError(
84 84 "Body does not contain string %r, output saved as %s" % (
85 85 s, f))
86 86
87 87 for no_s in no:
88 88 if no_s in self:
89 89 print_stderr("Actual response (has %r)" % no_s)
90 90 print_stderr(str(self))
91 91 raise IndexError(
92 92 "Body contains bad string %r, output saved as %s" % (
93 93 no_s, f))
94 94
95 95 def assert_response(self):
96 96 return AssertResponse(self)
97 97
98 98 def get_session_from_response(self):
99 99 """
100 100 This returns the session from a response object.
101 101 """
102 102
103 103 from pyramid_beaker import session_factory_from_settings
104 session = session_factory_from_settings(
105 self.test_app.app.config.get_settings())
104 session = session_factory_from_settings(self.test_app._pyramid_settings)
106 105 return session(self.request)
107 106
108 107
109 108 class TestRequest(webob.BaseRequest):
110 109
111 110 # for py.test
112 111 disabled = True
113 112 ResponseClass = CustomTestResponse
114 113
115 114 def add_response_callback(self, callback):
116 115 pass
117 116
118 117
119 118 class CustomTestApp(TestApp):
120 119 """
121 120 Custom app to make mustcontain more usefull, and extract special methods
122 121 """
123 122 RequestClass = TestRequest
124 123 rc_login_data = {}
125 124 rc_current_session = None
126 125
127 126 def login(self, username=None, password=None):
128 127 from rhodecode.lib import auth
129 128
130 129 if username and password:
131 130 session = login_user_session(self, username, password)
132 131 else:
133 132 session = login_user_session(self)
134 133
135 134 self.rc_login_data['csrf_token'] = auth.get_csrf_token(session)
136 135 self.rc_current_session = session
137 136 return session['rhodecode_user']
138 137
139 138 @property
140 139 def csrf_token(self):
141 140 return self.rc_login_data['csrf_token']
142 141
142 @property
143 def _pyramid_registry(self):
144 return self.app.config.registry
145
146 @property
147 def _pyramid_settings(self):
148 return self._pyramid_registry.settings
149
143 150
144 151 def set_anonymous_access(enabled):
145 152 """(Dis)allows anonymous access depending on parameter `enabled`"""
146 153 user = User.get_default_user()
147 154 user.active = enabled
148 155 Session().add(user)
149 156 Session().commit()
150 157 time.sleep(1.5) # must sleep for cache (1s to expire)
151 158 log.info('anonymous access is now: %s', enabled)
152 159 assert enabled == User.get_default_user().active, (
153 160 'Cannot set anonymous access')
154 161
155 162
156 163 def check_xfail_backends(node, backend_alias):
157 164 # Using "xfail_backends" here intentionally, since this marks work
158 165 # which is "to be done" soon.
159 166 skip_marker = node.get_closest_marker('xfail_backends')
160 167 if skip_marker and backend_alias in skip_marker.args:
161 168 msg = "Support for backend %s to be developed." % (backend_alias, )
162 169 msg = skip_marker.kwargs.get('reason', msg)
163 170 pytest.xfail(msg)
164 171
165 172
166 173 def check_skip_backends(node, backend_alias):
167 174 # Using "skip_backends" here intentionally, since this marks work which is
168 175 # not supported.
169 176 skip_marker = node.get_closest_marker('skip_backends')
170 177 if skip_marker and backend_alias in skip_marker.args:
171 178 msg = "Feature not supported for backend %s." % (backend_alias, )
172 179 msg = skip_marker.kwargs.get('reason', msg)
173 180 pytest.skip(msg)
174 181
175 182
176 183 def extract_git_repo_from_dump(dump_name, repo_name):
177 184 """Create git repo `repo_name` from dump `dump_name`."""
178 185 repos_path = ScmModel().repos_path
179 186 target_path = os.path.join(repos_path, repo_name)
180 187 rc_testdata.extract_git_dump(dump_name, target_path)
181 188 return target_path
182 189
183 190
184 191 def extract_hg_repo_from_dump(dump_name, repo_name):
185 192 """Create hg repo `repo_name` from dump `dump_name`."""
186 193 repos_path = ScmModel().repos_path
187 194 target_path = os.path.join(repos_path, repo_name)
188 195 rc_testdata.extract_hg_dump(dump_name, target_path)
189 196 return target_path
190 197
191 198
192 199 def extract_svn_repo_from_dump(dump_name, repo_name):
193 200 """Create a svn repo `repo_name` from dump `dump_name`."""
194 201 repos_path = ScmModel().repos_path
195 202 target_path = os.path.join(repos_path, repo_name)
196 203 SubversionRepository(target_path, create=True)
197 204 _load_svn_dump_into_repo(dump_name, target_path)
198 205 return target_path
199 206
200 207
201 208 def assert_message_in_log(log_records, message, levelno, module):
202 209 messages = [
203 210 r.message for r in log_records
204 211 if r.module == module and r.levelno == levelno
205 212 ]
206 213 assert message in messages
207 214
208 215
209 216 def _load_svn_dump_into_repo(dump_name, repo_path):
210 217 """
211 218 Utility to populate a svn repository with a named dump
212 219
213 220 Currently the dumps are in rc_testdata. They might later on be
214 221 integrated with the main repository once they stabilize more.
215 222 """
216 223 dump = rc_testdata.load_svn_dump(dump_name)
217 224 load_dump = subprocess32.Popen(
218 225 ['svnadmin', 'load', repo_path],
219 226 stdin=subprocess32.PIPE, stdout=subprocess32.PIPE,
220 227 stderr=subprocess32.PIPE)
221 228 out, err = load_dump.communicate(dump)
222 229 if load_dump.returncode != 0:
223 230 log.error("Output of load_dump command: %s", out)
224 231 log.error("Error output of load_dump command: %s", err)
225 232 raise Exception(
226 233 'Failed to load dump "%s" into repository at path "%s".'
227 234 % (dump_name, repo_path))
228 235
229 236
230 237 class AssertResponse(object):
231 238 """
232 239 Utility that helps to assert things about a given HTML response.
233 240 """
234 241
235 242 def __init__(self, response):
236 243 self.response = response
237 244
238 245 def get_imports(self):
239 246 return fromstring, tostring, CSSSelector
240 247
241 248 def one_element_exists(self, css_selector):
242 249 self.get_element(css_selector)
243 250
244 251 def no_element_exists(self, css_selector):
245 252 assert not self._get_elements(css_selector)
246 253
247 254 def element_equals_to(self, css_selector, expected_content):
248 255 element = self.get_element(css_selector)
249 256 element_text = self._element_to_string(element)
250 257 assert expected_content in element_text
251 258
252 259 def element_contains(self, css_selector, expected_content):
253 260 element = self.get_element(css_selector)
254 261 assert expected_content in element.text_content()
255 262
256 263 def element_value_contains(self, css_selector, expected_content):
257 264 element = self.get_element(css_selector)
258 265 assert expected_content in element.value
259 266
260 267 def contains_one_link(self, link_text, href):
261 268 fromstring, tostring, CSSSelector = self.get_imports()
262 269 doc = fromstring(self.response.body)
263 270 sel = CSSSelector('a[href]')
264 271 elements = [
265 272 e for e in sel(doc) if e.text_content().strip() == link_text]
266 273 assert len(elements) == 1, "Did not find link or found multiple links"
267 274 self._ensure_url_equal(elements[0].attrib.get('href'), href)
268 275
269 276 def contains_one_anchor(self, anchor_id):
270 277 fromstring, tostring, CSSSelector = self.get_imports()
271 278 doc = fromstring(self.response.body)
272 279 sel = CSSSelector('#' + anchor_id)
273 280 elements = sel(doc)
274 281 assert len(elements) == 1, 'cannot find 1 element {}'.format(anchor_id)
275 282
276 283 def _ensure_url_equal(self, found, expected):
277 284 assert _Url(found) == _Url(expected)
278 285
279 286 def get_element(self, css_selector):
280 287 elements = self._get_elements(css_selector)
281 288 assert len(elements) == 1, 'cannot find 1 element {}'.format(css_selector)
282 289 return elements[0]
283 290
284 291 def get_elements(self, css_selector):
285 292 return self._get_elements(css_selector)
286 293
287 294 def _get_elements(self, css_selector):
288 295 fromstring, tostring, CSSSelector = self.get_imports()
289 296 doc = fromstring(self.response.body)
290 297 sel = CSSSelector(css_selector)
291 298 elements = sel(doc)
292 299 return elements
293 300
294 301 def _element_to_string(self, element):
295 302 fromstring, tostring, CSSSelector = self.get_imports()
296 303 return tostring(element)
297 304
298 305
299 306 class _Url(object):
300 307 """
301 308 A url object that can be compared with other url orbjects
302 309 without regard to the vagaries of encoding, escaping, and ordering
303 310 of parameters in query strings.
304 311
305 312 Inspired by
306 313 http://stackoverflow.com/questions/5371992/comparing-two-urls-in-python
307 314 """
308 315
309 316 def __init__(self, url):
310 317 parts = urlparse(url)
311 318 _query = frozenset(parse_qsl(parts.query))
312 319 _path = unquote_plus(parts.path)
313 320 parts = parts._replace(query=_query, path=_path)
314 321 self.parts = parts
315 322
316 323 def __eq__(self, other):
317 324 return self.parts == other.parts
318 325
319 326 def __hash__(self):
320 327 return hash(self.parts)
321 328
322 329
323 330 def run_test_concurrently(times, raise_catched_exc=True):
324 331 """
325 332 Add this decorator to small pieces of code that you want to test
326 333 concurrently
327 334
328 335 ex:
329 336
330 337 @test_concurrently(25)
331 338 def my_test_function():
332 339 ...
333 340 """
334 341 def test_concurrently_decorator(test_func):
335 342 def wrapper(*args, **kwargs):
336 343 exceptions = []
337 344
338 345 def call_test_func():
339 346 try:
340 347 test_func(*args, **kwargs)
341 348 except Exception as e:
342 349 exceptions.append(e)
343 350 if raise_catched_exc:
344 351 raise
345 352 threads = []
346 353 for i in range(times):
347 354 threads.append(threading.Thread(target=call_test_func))
348 355 for t in threads:
349 356 t.start()
350 357 for t in threads:
351 358 t.join()
352 359 if exceptions:
353 360 raise Exception(
354 361 'test_concurrently intercepted %s exceptions: %s' % (
355 362 len(exceptions), exceptions))
356 363 return wrapper
357 364 return test_concurrently_decorator
358 365
359 366
360 367 def wait_for_url(url, timeout=10):
361 368 """
362 369 Wait until URL becomes reachable.
363 370
364 371 It polls the URL until the timeout is reached or it became reachable.
365 372 If will call to `py.test.fail` in case the URL is not reachable.
366 373 """
367 374 timeout = time.time() + timeout
368 375 last = 0
369 376 wait = 0.1
370 377
371 378 while timeout > last:
372 379 last = time.time()
373 380 if is_url_reachable(url):
374 381 break
375 382 elif (last + wait) > time.time():
376 383 # Go to sleep because not enough time has passed since last check.
377 384 time.sleep(wait)
378 385 else:
379 386 pytest.fail("Timeout while waiting for URL {}".format(url))
380 387
381 388
382 389 def is_url_reachable(url):
383 390 try:
384 391 urllib2.urlopen(url)
385 392 except urllib2.URLError:
386 393 return False
387 394 return True
388 395
389 396
390 397 def repo_on_filesystem(repo_name):
391 398 from rhodecode.lib import vcs
392 399 from rhodecode.tests import TESTS_TMP_PATH
393 400 repo = vcs.get_vcs_instance(
394 401 os.path.join(TESTS_TMP_PATH, repo_name), create=False)
395 402 return repo is not None
396 403
397 404
398 405 def commit_change(
399 406 repo, filename, content, message, vcs_type, parent=None, newfile=False):
400 407 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
401 408
402 409 repo = Repository.get_by_repo_name(repo)
403 410 _commit = parent
404 411 if not parent:
405 412 _commit = EmptyCommit(alias=vcs_type)
406 413
407 414 if newfile:
408 415 nodes = {
409 416 filename: {
410 417 'content': content
411 418 }
412 419 }
413 420 commit = ScmModel().create_nodes(
414 421 user=TEST_USER_ADMIN_LOGIN, repo=repo,
415 422 message=message,
416 423 nodes=nodes,
417 424 parent_commit=_commit,
418 425 author=TEST_USER_ADMIN_LOGIN,
419 426 )
420 427 else:
421 428 commit = ScmModel().commit_change(
422 429 repo=repo.scm_instance(), repo_name=repo.repo_name,
423 430 commit=parent, user=TEST_USER_ADMIN_LOGIN,
424 431 author=TEST_USER_ADMIN_LOGIN,
425 432 message=message,
426 433 content=content,
427 434 f_path=filename
428 435 )
429 436 return commit
430 437
431 438
432 439 def permission_update_data_generator(csrf_token, default=None, grant=None, revoke=None):
433 440 if not default:
434 441 raise ValueError('Permission for default user must be given')
435 442 form_data = [(
436 443 'csrf_token', csrf_token
437 444 )]
438 445 # add default
439 446 form_data.extend([
440 447 ('u_perm_1', default)
441 448 ])
442 449
443 450 if grant:
444 451 for cnt, (obj_id, perm, obj_name, obj_type) in enumerate(grant, 1):
445 452 form_data.extend([
446 453 ('perm_new_member_perm_new{}'.format(cnt), perm),
447 454 ('perm_new_member_id_new{}'.format(cnt), obj_id),
448 455 ('perm_new_member_name_new{}'.format(cnt), obj_name),
449 456 ('perm_new_member_type_new{}'.format(cnt), obj_type),
450 457
451 458 ])
452 459 if revoke:
453 460 for obj_id, obj_type in revoke:
454 461 form_data.extend([
455 462 ('perm_del_member_id_{}'.format(obj_id), obj_id),
456 463 ('perm_del_member_type_{}'.format(obj_id), obj_type),
457 464 ])
458 465 return form_data
General Comments 0
You need to be logged in to leave comments. Login now