##// END OF EJS Templates
ssh: added support for auto generating authorized_keys from stored ssh keys.
marcink -
r1994:801a60b3 default
parent child Browse files
Show More
@@ -0,0 +1,54 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 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 logging
22
23 from . import config_keys
24 from .events import SshKeyFileChangeEvent
25 from .subscribers import generate_ssh_authorized_keys_file_subscriber
26
27 from rhodecode.config.middleware import _bool_setting, _string_setting
28
29 log = logging.getLogger(__name__)
30
31
32 def _sanitize_settings_and_apply_defaults(settings):
33 """
34 Set defaults, convert to python types and validate settings.
35 """
36 _bool_setting(settings, config_keys.generate_authorized_keyfile, 'false')
37 _bool_setting(settings, config_keys.wrapper_allow_shell, 'false')
38
39 _string_setting(settings, config_keys.authorized_keys_file_path, '',
40 lower=False)
41 _string_setting(settings, config_keys.wrapper_cmd, '',
42 lower=False)
43 _string_setting(settings, config_keys.authorized_keys_line_ssh_opts, '',
44 lower=False)
45
46
47 def includeme(config):
48 settings = config.registry.settings
49 _sanitize_settings_and_apply_defaults(settings)
50
51 # if we have enable generation of file, subscribe to event
52 if settings[config_keys.generate_authorized_keyfile]:
53 config.add_subscriber(
54 generate_ssh_authorized_keys_file_subscriber, SshKeyFileChangeEvent)
@@ -0,0 +1,28 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 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 generate_authorized_keyfile = 'ssh.generate_authorized_keyfile'
25 authorized_keys_file_path = 'ssh.authorized_keys_file_path'
26 authorized_keys_line_ssh_opts = 'ssh.authorized_keys_ssh_opts'
27 wrapper_cmd = 'ssh.wrapper_cmd'
28 wrapper_allow_shell = 'ssh.wrapper_cmd_allow_shell'
@@ -0,0 +1,29 b''
1 # Copyright (C) 2016-2017 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19
20 from rhodecode.events.base import RhodecodeEvent
21 from rhodecode.translation import _
22
23
24 class SshKeyFileChangeEvent(RhodecodeEvent):
25 """
26 This event will be triggered on every modification of the stored SSH keys
27 """
28 name = 'rhodecode-ssh-key-file-change'
29 display_name = _('RhodeCode SSH Key files changed.')
@@ -0,0 +1,36 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 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 logging
22
23
24 from .utils import generate_ssh_authorized_keys_file
25
26
27 log = logging.getLogger(__name__)
28
29
30 def generate_ssh_authorized_keys_file_subscriber(event):
31 """
32 Subscriber to the `SshKeyFileChangeEvent`. This triggers the
33 automatic generation of authorized_keys file on any change in
34 ssh keys management
35 """
36 generate_ssh_authorized_keys_file(event.request.registry)
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 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/
@@ -0,0 +1,68 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 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
23 import os
24 import pytest
25 import mock
26
27 from rhodecode.apps.ssh_support import utils
28 from rhodecode.lib.utils2 import AttributeDict
29
30
31 class TestSshKeyFileGeneration(object):
32 @pytest.mark.parametrize('ssh_wrapper_cmd', ['/tmp/sshwrapper.py'])
33 @pytest.mark.parametrize('allow_shell', [True, False])
34 @pytest.mark.parametrize('ssh_opts', [None, 'mycustom,option'])
35 def test_write_keyfile(self, tmpdir, ssh_wrapper_cmd, allow_shell, ssh_opts):
36
37 authorized_keys_file_path = os.path.join(str(tmpdir), 'authorized_keys')
38
39 def keys():
40 return [
41 AttributeDict({'user': AttributeDict(username='admin'),
42 'ssh_key_data': 'ssh-rsa ADMIN_KEY'}),
43 AttributeDict({'user': AttributeDict(username='user'),
44 'ssh_key_data': 'ssh-rsa USER_KEY'}),
45 ]
46
47 with mock.patch('rhodecode.apps.ssh_support.utils.get_all_active_keys',
48 return_value=keys()):
49 utils._generate_ssh_authorized_keys_file(
50 authorized_keys_file_path, ssh_wrapper_cmd,
51 allow_shell, ssh_opts
52 )
53
54 assert os.path.isfile(authorized_keys_file_path)
55 with open(authorized_keys_file_path) as f:
56 content = f.read()
57
58 assert 'command="/tmp/sshwrapper.py' in content
59 assert 'This file is managed by RhodeCode, ' \
60 'please do not edit it manually.' in content
61
62 if allow_shell:
63 assert '--shell --user' in content
64 else:
65 assert '--user' in content
66
67 if ssh_opts:
68 assert ssh_opts in content
@@ -0,0 +1,107 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 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 stat
23 import logging
24 import tempfile
25 import datetime
26
27 from . import config_keys
28 from rhodecode.model.db import true, joinedload, User, UserSshKeys
29
30
31 log = logging.getLogger(__name__)
32
33 HEADER = \
34 "# This file is managed by RhodeCode, please do not edit it manually. # \n" \
35 "# Current entries: {}, create date: UTC:{}.\n"
36
37 # Default SSH options for authorized_keys file, can be override via .ini
38 SSH_OPTS = 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding'
39
40
41 def get_all_active_keys():
42 result = UserSshKeys.query() \
43 .options(joinedload(UserSshKeys.user)) \
44 .filter(UserSshKeys.user != User.get_default_user()) \
45 .filter(User.active == true()) \
46 .all()
47 return result
48
49
50 def _generate_ssh_authorized_keys_file(
51 authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts):
52 all_active_keys = get_all_active_keys()
53
54 if allow_shell:
55 ssh_wrapper_cmd = ssh_wrapper_cmd + ' --shell'
56
57 if not os.path.isfile(authorized_keys_file_path):
58 with open(authorized_keys_file_path, 'w'):
59 pass
60
61 if not os.access(authorized_keys_file_path, os.R_OK):
62 raise OSError('Access to file {} is without read access'.format(
63 authorized_keys_file_path))
64
65 line_tmpl = '{ssh_opts},command="{wrapper_command} --user {user}" {key}\n'
66
67 fd, tmp_authorized_keys = tempfile.mkstemp(
68 '.authorized_keys_write',
69 dir=os.path.dirname(authorized_keys_file_path))
70
71 now = datetime.datetime.utcnow().isoformat()
72 keys_file = os.fdopen(fd, 'wb')
73 keys_file.write(HEADER.format(len(all_active_keys), now))
74
75 for user_key in all_active_keys:
76 username = user_key.user.username
77 keys_file.write(
78 line_tmpl.format(
79 ssh_opts=ssh_opts or SSH_OPTS,
80 wrapper_command=ssh_wrapper_cmd,
81 user=username, key=user_key.ssh_key_data))
82 log.debug('addkey: Key added for user: `%s`', username)
83 keys_file.close()
84
85 # Explicitly setting read-only permissions to authorized_keys
86 os.chmod(tmp_authorized_keys, stat.S_IRUSR | stat.S_IWUSR)
87 # Rename is atomic operation
88 os.rename(tmp_authorized_keys, authorized_keys_file_path)
89
90
91 def generate_ssh_authorized_keys_file(registry):
92 log.info('Generating new authorized key file')
93
94 authorized_keys_file_path = registry.settings.get(
95 config_keys.authorized_keys_file_path)
96
97 ssh_wrapper_cmd = registry.settings.get(
98 config_keys.wrapper_cmd)
99 allow_shell = registry.settings.get(
100 config_keys.wrapper_allow_shell)
101 ssh_opts = registry.settings.get(
102 config_keys.authorized_keys_line_ssh_opts)
103
104 _generate_ssh_authorized_keys_file(
105 authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts)
106
107 return 0
@@ -1,678 +1,699 b''
1 1
2 2
3 3 ################################################################################
4 4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
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 ## Uncomment and replace with the address which should receive any error report
25 25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 26 #email_to = admin@localhost
27 27
28 28 ## in case of Application errors, sent an error email form
29 29 #error_email_from = rhodecode_error@localhost
30 30
31 31 ## additional error message to be send in case of server crash
32 32 #error_message =
33 33
34 34
35 35 #smtp_server = mail.server.com
36 36 #smtp_username =
37 37 #smtp_password =
38 38 #smtp_port =
39 39 #smtp_use_tls = false
40 40 #smtp_use_ssl = true
41 41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 42 #smtp_auth =
43 43
44 44 [server:main]
45 45 ## COMMON ##
46 46 host = 127.0.0.1
47 47 port = 5000
48 48
49 49 ##################################
50 50 ## WAITRESS WSGI SERVER ##
51 51 ## Recommended for Development ##
52 52 ##################################
53 53
54 54 use = egg:waitress#main
55 55 ## number of worker threads
56 56 threads = 5
57 57 ## MAX BODY SIZE 100GB
58 58 max_request_body_size = 107374182400
59 59 ## Use poll instead of select, fixes file descriptors limits problems.
60 60 ## May not work on old windows systems.
61 61 asyncore_use_poll = true
62 62
63 63
64 64 ##########################
65 65 ## GUNICORN WSGI SERVER ##
66 66 ##########################
67 67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
68 68
69 69 #use = egg:gunicorn#main
70 70 ## Sets the number of process workers. You must set `instance_id = *`
71 71 ## when this option is set to more than one worker, recommended
72 72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
73 73 ## The `instance_id = *` must be set in the [app:main] section below
74 74 #workers = 2
75 75 ## number of threads for each of the worker, must be set to 1 for gevent
76 76 ## generally recommened to be at 1
77 77 #threads = 1
78 78 ## process name
79 79 #proc_name = rhodecode
80 80 ## type of worker class, one of sync, gevent
81 81 ## recommended for bigger setup is using of of other than sync one
82 82 #worker_class = sync
83 83 ## The maximum number of simultaneous clients. Valid only for Gevent
84 84 #worker_connections = 10
85 85 ## max number of requests that worker will handle before being gracefully
86 86 ## restarted, could prevent memory leaks
87 87 #max_requests = 1000
88 88 #max_requests_jitter = 30
89 89 ## amount of time a worker can spend with handling a request before it
90 90 ## gets killed and restarted. Set to 6hrs
91 91 #timeout = 21600
92 92
93 93
94 94 ## prefix middleware for RhodeCode.
95 95 ## recommended when using proxy setup.
96 96 ## allows to set RhodeCode under a prefix in server.
97 97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
98 98 ## And set your prefix like: `prefix = /custom_prefix`
99 99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
100 100 ## to make your cookies only work on prefix url
101 101 [filter:proxy-prefix]
102 102 use = egg:PasteDeploy#prefix
103 103 prefix = /
104 104
105 105 [app:main]
106 106 use = egg:rhodecode-enterprise-ce
107 107
108 108 ## enable proxy prefix middleware, defined above
109 109 #filter-with = proxy-prefix
110 110
111 111 # During development the we want to have the debug toolbar enabled
112 112 pyramid.includes =
113 113 pyramid_debugtoolbar
114 114 rhodecode.utils.debugtoolbar
115 115 rhodecode.lib.middleware.request_wrapper
116 116
117 117 pyramid.reload_templates = true
118 118
119 119 debugtoolbar.hosts = 0.0.0.0/0
120 120 debugtoolbar.exclude_prefixes =
121 121 /css
122 122 /fonts
123 123 /images
124 124 /js
125 125
126 126 ## RHODECODE PLUGINS ##
127 127 rhodecode.includes =
128 128 rhodecode.api
129 129
130 130
131 131 # api prefix url
132 132 rhodecode.api.url = /_admin/api
133 133
134 134
135 135 ## END RHODECODE PLUGINS ##
136 136
137 137 ## encryption key used to encrypt social plugin tokens,
138 138 ## remote_urls with credentials etc, if not set it defaults to
139 139 ## `beaker.session.secret`
140 140 #rhodecode.encrypted_values.secret =
141 141
142 142 ## decryption strict mode (enabled by default). It controls if decryption raises
143 143 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
144 144 #rhodecode.encrypted_values.strict = false
145 145
146 146 ## return gzipped responses from Rhodecode (static files/application)
147 147 gzip_responses = false
148 148
149 149 ## autogenerate javascript routes file on startup
150 150 generate_js_files = false
151 151
152 152 ## Optional Languages
153 153 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
154 154 lang = en
155 155
156 156 ## perform a full repository scan on each server start, this should be
157 157 ## set to false after first startup, to allow faster server restarts.
158 158 startup.import_repos = false
159 159
160 160 ## Uncomment and set this path to use archive download cache.
161 161 ## Once enabled, generated archives will be cached at this location
162 162 ## and served from the cache during subsequent requests for the same archive of
163 163 ## the repository.
164 164 #archive_cache_dir = /tmp/tarballcache
165 165
166 166 ## change this to unique ID for security
167 167 app_instance_uuid = rc-production
168 168
169 169 ## cut off limit for large diffs (size in bytes)
170 170 cut_off_limit_diff = 1024000
171 171 cut_off_limit_file = 256000
172 172
173 173 ## use cache version of scm repo everywhere
174 174 vcs_full_cache = true
175 175
176 176 ## force https in RhodeCode, fixes https redirects, assumes it's always https
177 177 ## Normally this is controlled by proper http flags sent from http server
178 178 force_https = false
179 179
180 180 ## use Strict-Transport-Security headers
181 181 use_htsts = false
182 182
183 183 ## number of commits stats will parse on each iteration
184 184 commit_parse_limit = 25
185 185
186 186 ## git rev filter option, --all is the default filter, if you need to
187 187 ## hide all refs in changelog switch this to --branches --tags
188 188 git_rev_filter = --branches --tags
189 189
190 190 # Set to true if your repos are exposed using the dumb protocol
191 191 git_update_server_info = false
192 192
193 193 ## RSS/ATOM feed options
194 194 rss_cut_off_limit = 256000
195 195 rss_items_per_page = 10
196 196 rss_include_diff = false
197 197
198 198 ## gist URL alias, used to create nicer urls for gist. This should be an
199 199 ## url that does rewrites to _admin/gists/{gistid}.
200 200 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
201 201 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
202 202 gist_alias_url =
203 203
204 204 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
205 205 ## used for access.
206 206 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
207 207 ## came from the the logged in user who own this authentication token.
208 208 ##
209 209 ## list of all views can be found under `_admin/permissions/auth_token_access`
210 210 ## The list should be "," separated and on a single line.
211 211 ##
212 212 ## Most common views to enable:
213 213 # RepoCommitsView:repo_commit_download
214 214 # RepoCommitsView:repo_commit_patch
215 215 # RepoCommitsView:repo_commit_raw
216 216 # RepoFilesView:repo_files_diff
217 217 # RepoFilesView:repo_archivefile
218 218 # RepoFilesView:repo_file_raw
219 219 # GistView:*
220 220 api_access_controllers_whitelist =
221 221
222 222 ## default encoding used to convert from and to unicode
223 223 ## can be also a comma separated list of encoding in case of mixed encodings
224 224 default_encoding = UTF-8
225 225
226 226 ## instance-id prefix
227 227 ## a prefix key for this instance used for cache invalidation when running
228 228 ## multiple instances of rhodecode, make sure it's globally unique for
229 229 ## all running rhodecode instances. Leave empty if you don't use it
230 230 instance_id =
231 231
232 232 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
233 233 ## of an authentication plugin also if it is disabled by it's settings.
234 234 ## This could be useful if you are unable to log in to the system due to broken
235 235 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
236 236 ## module to log in again and fix the settings.
237 237 ##
238 238 ## Available builtin plugin IDs (hash is part of the ID):
239 239 ## egg:rhodecode-enterprise-ce#rhodecode
240 240 ## egg:rhodecode-enterprise-ce#pam
241 241 ## egg:rhodecode-enterprise-ce#ldap
242 242 ## egg:rhodecode-enterprise-ce#jasig_cas
243 243 ## egg:rhodecode-enterprise-ce#headers
244 244 ## egg:rhodecode-enterprise-ce#crowd
245 245 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
246 246
247 247 ## alternative return HTTP header for failed authentication. Default HTTP
248 248 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
249 249 ## handling that causing a series of failed authentication calls.
250 250 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
251 251 ## This will be served instead of default 401 on bad authnetication
252 252 auth_ret_code =
253 253
254 254 ## use special detection method when serving auth_ret_code, instead of serving
255 255 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
256 256 ## and then serve auth_ret_code to clients
257 257 auth_ret_code_detection = false
258 258
259 259 ## locking return code. When repository is locked return this HTTP code. 2XX
260 260 ## codes don't break the transactions while 4XX codes do
261 261 lock_ret_code = 423
262 262
263 263 ## allows to change the repository location in settings page
264 264 allow_repo_location_change = true
265 265
266 266 ## allows to setup custom hooks in settings page
267 267 allow_custom_hooks_settings = true
268 268
269 269 ## generated license token, goto license page in RhodeCode settings to obtain
270 270 ## new token
271 271 license_token =
272 272
273 273 ## supervisor connection uri, for managing supervisor and logs.
274 274 supervisor.uri =
275 275 ## supervisord group name/id we only want this RC instance to handle
276 276 supervisor.group_id = dev
277 277
278 278 ## Display extended labs settings
279 279 labs_settings_active = true
280 280
281 281 ####################################
282 282 ### CELERY CONFIG ####
283 283 ####################################
284 284 use_celery = false
285 285 broker.host = localhost
286 286 broker.vhost = rabbitmqhost
287 287 broker.port = 5672
288 288 broker.user = rabbitmq
289 289 broker.password = qweqwe
290 290
291 291 celery.imports = rhodecode.lib.celerylib.tasks
292 292
293 293 celery.result.backend = amqp
294 294 celery.result.dburi = amqp://
295 295 celery.result.serialier = json
296 296
297 297 #celery.send.task.error.emails = true
298 298 #celery.amqp.task.result.expires = 18000
299 299
300 300 celeryd.concurrency = 2
301 301 #celeryd.log.file = celeryd.log
302 302 celeryd.log.level = debug
303 303 celeryd.max.tasks.per.child = 1
304 304
305 305 ## tasks will never be sent to the queue, but executed locally instead.
306 306 celery.always.eager = false
307 307
308 308 ####################################
309 309 ### BEAKER CACHE ####
310 310 ####################################
311 311 # default cache dir for templates. Putting this into a ramdisk
312 312 ## can boost performance, eg. %(here)s/data_ramdisk
313 313 cache_dir = %(here)s/data
314 314
315 315 ## locking and default file storage for Beaker. Putting this into a ramdisk
316 316 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
317 317 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
318 318 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
319 319
320 320 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
321 321
322 322 beaker.cache.super_short_term.type = memory
323 323 beaker.cache.super_short_term.expire = 10
324 324 beaker.cache.super_short_term.key_length = 256
325 325
326 326 beaker.cache.short_term.type = memory
327 327 beaker.cache.short_term.expire = 60
328 328 beaker.cache.short_term.key_length = 256
329 329
330 330 beaker.cache.long_term.type = memory
331 331 beaker.cache.long_term.expire = 36000
332 332 beaker.cache.long_term.key_length = 256
333 333
334 334 beaker.cache.sql_cache_short.type = memory
335 335 beaker.cache.sql_cache_short.expire = 10
336 336 beaker.cache.sql_cache_short.key_length = 256
337 337
338 338 ## default is memory cache, configure only if required
339 339 ## using multi-node or multi-worker setup
340 340 #beaker.cache.auth_plugins.type = ext:database
341 341 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
342 342 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
343 343 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
344 344 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
345 345 #beaker.cache.auth_plugins.sa.pool_size = 10
346 346 #beaker.cache.auth_plugins.sa.max_overflow = 0
347 347
348 348 beaker.cache.repo_cache_long.type = memorylru_base
349 349 beaker.cache.repo_cache_long.max_items = 4096
350 350 beaker.cache.repo_cache_long.expire = 2592000
351 351
352 352 ## default is memorylru_base cache, configure only if required
353 353 ## using multi-node or multi-worker setup
354 354 #beaker.cache.repo_cache_long.type = ext:memcached
355 355 #beaker.cache.repo_cache_long.url = localhost:11211
356 356 #beaker.cache.repo_cache_long.expire = 1209600
357 357 #beaker.cache.repo_cache_long.key_length = 256
358 358
359 359 ####################################
360 360 ### BEAKER SESSION ####
361 361 ####################################
362 362
363 363 ## .session.type is type of storage options for the session, current allowed
364 364 ## types are file, ext:memcached, ext:database, and memory (default).
365 365 beaker.session.type = file
366 366 beaker.session.data_dir = %(here)s/data/sessions/data
367 367
368 368 ## db based session, fast, and allows easy management over logged in users
369 369 #beaker.session.type = ext:database
370 370 #beaker.session.table_name = db_session
371 371 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
372 372 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
373 373 #beaker.session.sa.pool_recycle = 3600
374 374 #beaker.session.sa.echo = false
375 375
376 376 beaker.session.key = rhodecode
377 377 beaker.session.secret = develop-rc-uytcxaz
378 378 beaker.session.lock_dir = %(here)s/data/sessions/lock
379 379
380 380 ## Secure encrypted cookie. Requires AES and AES python libraries
381 381 ## you must disable beaker.session.secret to use this
382 382 #beaker.session.encrypt_key = key_for_encryption
383 383 #beaker.session.validate_key = validation_key
384 384
385 385 ## sets session as invalid(also logging out user) if it haven not been
386 386 ## accessed for given amount of time in seconds
387 387 beaker.session.timeout = 2592000
388 388 beaker.session.httponly = true
389 389 ## Path to use for the cookie. Set to prefix if you use prefix middleware
390 390 #beaker.session.cookie_path = /custom_prefix
391 391
392 392 ## uncomment for https secure cookie
393 393 beaker.session.secure = false
394 394
395 395 ## auto save the session to not to use .save()
396 396 beaker.session.auto = false
397 397
398 398 ## default cookie expiration time in seconds, set to `true` to set expire
399 399 ## at browser close
400 400 #beaker.session.cookie_expires = 3600
401 401
402 402 ###################################
403 403 ## SEARCH INDEXING CONFIGURATION ##
404 404 ###################################
405 405 ## Full text search indexer is available in rhodecode-tools under
406 406 ## `rhodecode-tools index` command
407 407
408 408 ## WHOOSH Backend, doesn't require additional services to run
409 409 ## it works good with few dozen repos
410 410 search.module = rhodecode.lib.index.whoosh
411 411 search.location = %(here)s/data/index
412 412
413 413 ########################################
414 414 ### CHANNELSTREAM CONFIG ####
415 415 ########################################
416 416 ## channelstream enables persistent connections and live notification
417 417 ## in the system. It's also used by the chat system
418 418 channelstream.enabled = false
419 419
420 420 ## server address for channelstream server on the backend
421 421 channelstream.server = 127.0.0.1:9800
422 422
423 423 ## location of the channelstream server from outside world
424 424 ## use ws:// for http or wss:// for https. This address needs to be handled
425 425 ## by external HTTP server such as Nginx or Apache
426 426 ## see nginx/apache configuration examples in our docs
427 427 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
428 428 channelstream.secret = secret
429 429 channelstream.history.location = %(here)s/channelstream_history
430 430
431 431 ## Internal application path that Javascript uses to connect into.
432 432 ## If you use proxy-prefix the prefix should be added before /_channelstream
433 433 channelstream.proxy_path = /_channelstream
434 434
435 435
436 436 ###################################
437 437 ## APPENLIGHT CONFIG ##
438 438 ###################################
439 439
440 440 ## Appenlight is tailored to work with RhodeCode, see
441 441 ## http://appenlight.com for details how to obtain an account
442 442
443 443 ## appenlight integration enabled
444 444 appenlight = false
445 445
446 446 appenlight.server_url = https://api.appenlight.com
447 447 appenlight.api_key = YOUR_API_KEY
448 448 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
449 449
450 450 # used for JS client
451 451 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
452 452
453 453 ## TWEAK AMOUNT OF INFO SENT HERE
454 454
455 455 ## enables 404 error logging (default False)
456 456 appenlight.report_404 = false
457 457
458 458 ## time in seconds after request is considered being slow (default 1)
459 459 appenlight.slow_request_time = 1
460 460
461 461 ## record slow requests in application
462 462 ## (needs to be enabled for slow datastore recording and time tracking)
463 463 appenlight.slow_requests = true
464 464
465 465 ## enable hooking to application loggers
466 466 appenlight.logging = true
467 467
468 468 ## minimum log level for log capture
469 469 appenlight.logging.level = WARNING
470 470
471 471 ## send logs only from erroneous/slow requests
472 472 ## (saves API quota for intensive logging)
473 473 appenlight.logging_on_error = false
474 474
475 475 ## list of additonal keywords that should be grabbed from environ object
476 476 ## can be string with comma separated list of words in lowercase
477 477 ## (by default client will always send following info:
478 478 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
479 479 ## start with HTTP* this list be extended with additional keywords here
480 480 appenlight.environ_keys_whitelist =
481 481
482 482 ## list of keywords that should be blanked from request object
483 483 ## can be string with comma separated list of words in lowercase
484 484 ## (by default client will always blank keys that contain following words
485 485 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
486 486 ## this list be extended with additional keywords set here
487 487 appenlight.request_keys_blacklist =
488 488
489 489 ## list of namespaces that should be ignores when gathering log entries
490 490 ## can be string with comma separated list of namespaces
491 491 ## (by default the client ignores own entries: appenlight_client.client)
492 492 appenlight.log_namespace_blacklist =
493 493
494 494
495 495 ################################################################################
496 496 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
497 497 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
498 498 ## execute malicious code after an exception is raised. ##
499 499 ################################################################################
500 500 #set debug = false
501 501
502 502
503 503 ##############
504 504 ## STYLING ##
505 505 ##############
506 506 debug_style = true
507 507
508 508 ###########################################
509 509 ### MAIN RHODECODE DATABASE CONFIG ###
510 510 ###########################################
511 511 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
512 512 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
513 513 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
514 514 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
515 515
516 516 # see sqlalchemy docs for other advanced settings
517 517
518 518 ## print the sql statements to output
519 519 sqlalchemy.db1.echo = false
520 520 ## recycle the connections after this amount of seconds
521 521 sqlalchemy.db1.pool_recycle = 3600
522 522 sqlalchemy.db1.convert_unicode = true
523 523
524 524 ## the number of connections to keep open inside the connection pool.
525 525 ## 0 indicates no limit
526 526 #sqlalchemy.db1.pool_size = 5
527 527
528 528 ## the number of connections to allow in connection pool "overflow", that is
529 529 ## connections that can be opened above and beyond the pool_size setting,
530 530 ## which defaults to five.
531 531 #sqlalchemy.db1.max_overflow = 10
532 532
533 533
534 534 ##################
535 535 ### VCS CONFIG ###
536 536 ##################
537 537 vcs.server.enable = true
538 538 vcs.server = localhost:9900
539 539
540 540 ## Web server connectivity protocol, responsible for web based VCS operatations
541 541 ## Available protocols are:
542 542 ## `http` - use http-rpc backend (default)
543 543 vcs.server.protocol = http
544 544
545 545 ## Push/Pull operations protocol, available options are:
546 546 ## `http` - use http-rpc backend (default)
547 547 ##
548 548 vcs.scm_app_implementation = http
549 549
550 550 ## Push/Pull operations hooks protocol, available options are:
551 551 ## `http` - use http-rpc backend (default)
552 552 vcs.hooks.protocol = http
553 553
554 554 vcs.server.log_level = debug
555 555 ## Start VCSServer with this instance as a subprocess, usefull for development
556 556 vcs.start_server = true
557 557
558 558 ## List of enabled VCS backends, available options are:
559 559 ## `hg` - mercurial
560 560 ## `git` - git
561 561 ## `svn` - subversion
562 562 vcs.backends = hg, git, svn
563 563
564 564 vcs.connection_timeout = 3600
565 565 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
566 566 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
567 567 #vcs.svn.compatible_version = pre-1.8-compatible
568 568
569 569
570 570 ############################################################
571 571 ### Subversion proxy support (mod_dav_svn) ###
572 572 ### Maps RhodeCode repo groups into SVN paths for Apache ###
573 573 ############################################################
574 574 ## Enable or disable the config file generation.
575 575 svn.proxy.generate_config = false
576 576 ## Generate config file with `SVNListParentPath` set to `On`.
577 577 svn.proxy.list_parent_path = true
578 578 ## Set location and file name of generated config file.
579 579 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
580 580 ## Used as a prefix to the `Location` block in the generated config file.
581 581 ## In most cases it should be set to `/`.
582 582 svn.proxy.location_root = /
583 583 ## Command to reload the mod dav svn configuration on change.
584 584 ## Example: `/etc/init.d/apache2 reload`
585 585 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
586 586 ## If the timeout expires before the reload command finishes, the command will
587 587 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
588 588 #svn.proxy.reload_timeout = 10
589 589
590 ############################################################
591 ### SSH Support Settings ###
592 ############################################################
593
594 ## Defines if the authorized_keys file should be written on any change of
595 ## user ssh keys
596 ssh.generate_authorized_keyfile = false
597
598 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
599 # ssh.authorized_keys_ssh_opts =
600
601 ## File to generate the authorized keys together with options
602 ssh.authorized_keys_file_path = /home/USER/.ssh/authorized_keys
603
604 ## Command to execute as an SSH wrapper, available from
605 ## https://code.rhodecode.com/rhodecode-ssh
606 ssh.wrapper_cmd = /home/USER/rhodecode-ssh/sshwrapper.py
607
608 ## Allow shell when executing the command
609 ssh.wrapper_cmd_allow_shell = false
610
590 611 ## Dummy marker to add new entries after.
591 612 ## Add any custom entries below. Please don't remove.
592 613 custom.conf = 1
593 614
594 615
595 616 ################################
596 617 ### LOGGING CONFIGURATION ####
597 618 ################################
598 619 [loggers]
599 620 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
600 621
601 622 [handlers]
602 623 keys = console, console_sql
603 624
604 625 [formatters]
605 626 keys = generic, color_formatter, color_formatter_sql
606 627
607 628 #############
608 629 ## LOGGERS ##
609 630 #############
610 631 [logger_root]
611 632 level = NOTSET
612 633 handlers = console
613 634
614 635 [logger_routes]
615 636 level = DEBUG
616 637 handlers =
617 638 qualname = routes.middleware
618 639 ## "level = DEBUG" logs the route matched and routing variables.
619 640 propagate = 1
620 641
621 642 [logger_beaker]
622 643 level = DEBUG
623 644 handlers =
624 645 qualname = beaker.container
625 646 propagate = 1
626 647
627 648 [logger_templates]
628 649 level = INFO
629 650 handlers =
630 651 qualname = pylons.templating
631 652 propagate = 1
632 653
633 654 [logger_rhodecode]
634 655 level = DEBUG
635 656 handlers =
636 657 qualname = rhodecode
637 658 propagate = 1
638 659
639 660 [logger_sqlalchemy]
640 661 level = INFO
641 662 handlers = console_sql
642 663 qualname = sqlalchemy.engine
643 664 propagate = 0
644 665
645 666 ##############
646 667 ## HANDLERS ##
647 668 ##############
648 669
649 670 [handler_console]
650 671 class = StreamHandler
651 672 args = (sys.stderr, )
652 673 level = DEBUG
653 674 formatter = color_formatter
654 675
655 676 [handler_console_sql]
656 677 class = StreamHandler
657 678 args = (sys.stderr, )
658 679 level = DEBUG
659 680 formatter = color_formatter_sql
660 681
661 682 ################
662 683 ## FORMATTERS ##
663 684 ################
664 685
665 686 [formatter_generic]
666 687 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
667 688 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
668 689 datefmt = %Y-%m-%d %H:%M:%S
669 690
670 691 [formatter_color_formatter]
671 692 class = rhodecode.lib.logging_formatter.ColorFormatter
672 693 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
673 694 datefmt = %Y-%m-%d %H:%M:%S
674 695
675 696 [formatter_color_formatter_sql]
676 697 class = rhodecode.lib.logging_formatter.ColorFormatterSql
677 698 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
678 699 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,647 +1,668 b''
1 1
2 2
3 3 ################################################################################
4 4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
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 ## Uncomment and replace with the address which should receive any error report
25 25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 26 #email_to = admin@localhost
27 27
28 28 ## in case of Application errors, sent an error email form
29 29 #error_email_from = rhodecode_error@localhost
30 30
31 31 ## additional error message to be send in case of server crash
32 32 #error_message =
33 33
34 34
35 35 #smtp_server = mail.server.com
36 36 #smtp_username =
37 37 #smtp_password =
38 38 #smtp_port =
39 39 #smtp_use_tls = false
40 40 #smtp_use_ssl = true
41 41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 42 #smtp_auth =
43 43
44 44 [server:main]
45 45 ## COMMON ##
46 46 host = 127.0.0.1
47 47 port = 5000
48 48
49 49 ##################################
50 50 ## WAITRESS WSGI SERVER ##
51 51 ## Recommended for Development ##
52 52 ##################################
53 53
54 54 #use = egg:waitress#main
55 55 ## number of worker threads
56 56 #threads = 5
57 57 ## MAX BODY SIZE 100GB
58 58 #max_request_body_size = 107374182400
59 59 ## Use poll instead of select, fixes file descriptors limits problems.
60 60 ## May not work on old windows systems.
61 61 #asyncore_use_poll = true
62 62
63 63
64 64 ##########################
65 65 ## GUNICORN WSGI SERVER ##
66 66 ##########################
67 67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
68 68
69 69 use = egg:gunicorn#main
70 70 ## Sets the number of process workers. You must set `instance_id = *`
71 71 ## when this option is set to more than one worker, recommended
72 72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
73 73 ## The `instance_id = *` must be set in the [app:main] section below
74 74 workers = 2
75 75 ## number of threads for each of the worker, must be set to 1 for gevent
76 76 ## generally recommened to be at 1
77 77 #threads = 1
78 78 ## process name
79 79 proc_name = rhodecode
80 80 ## type of worker class, one of sync, gevent
81 81 ## recommended for bigger setup is using of of other than sync one
82 82 worker_class = sync
83 83 ## The maximum number of simultaneous clients. Valid only for Gevent
84 84 #worker_connections = 10
85 85 ## max number of requests that worker will handle before being gracefully
86 86 ## restarted, could prevent memory leaks
87 87 max_requests = 1000
88 88 max_requests_jitter = 30
89 89 ## amount of time a worker can spend with handling a request before it
90 90 ## gets killed and restarted. Set to 6hrs
91 91 timeout = 21600
92 92
93 93
94 94 ## prefix middleware for RhodeCode.
95 95 ## recommended when using proxy setup.
96 96 ## allows to set RhodeCode under a prefix in server.
97 97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
98 98 ## And set your prefix like: `prefix = /custom_prefix`
99 99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
100 100 ## to make your cookies only work on prefix url
101 101 [filter:proxy-prefix]
102 102 use = egg:PasteDeploy#prefix
103 103 prefix = /
104 104
105 105 [app:main]
106 106 use = egg:rhodecode-enterprise-ce
107 107
108 108 ## enable proxy prefix middleware, defined above
109 109 #filter-with = proxy-prefix
110 110
111 111 ## encryption key used to encrypt social plugin tokens,
112 112 ## remote_urls with credentials etc, if not set it defaults to
113 113 ## `beaker.session.secret`
114 114 #rhodecode.encrypted_values.secret =
115 115
116 116 ## decryption strict mode (enabled by default). It controls if decryption raises
117 117 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
118 118 #rhodecode.encrypted_values.strict = false
119 119
120 120 ## return gzipped responses from Rhodecode (static files/application)
121 121 gzip_responses = false
122 122
123 123 ## autogenerate javascript routes file on startup
124 124 generate_js_files = false
125 125
126 126 ## Optional Languages
127 127 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
128 128 lang = en
129 129
130 130 ## perform a full repository scan on each server start, this should be
131 131 ## set to false after first startup, to allow faster server restarts.
132 132 startup.import_repos = false
133 133
134 134 ## Uncomment and set this path to use archive download cache.
135 135 ## Once enabled, generated archives will be cached at this location
136 136 ## and served from the cache during subsequent requests for the same archive of
137 137 ## the repository.
138 138 #archive_cache_dir = /tmp/tarballcache
139 139
140 140 ## change this to unique ID for security
141 141 app_instance_uuid = rc-production
142 142
143 143 ## cut off limit for large diffs (size in bytes)
144 144 cut_off_limit_diff = 1024000
145 145 cut_off_limit_file = 256000
146 146
147 147 ## use cache version of scm repo everywhere
148 148 vcs_full_cache = true
149 149
150 150 ## force https in RhodeCode, fixes https redirects, assumes it's always https
151 151 ## Normally this is controlled by proper http flags sent from http server
152 152 force_https = false
153 153
154 154 ## use Strict-Transport-Security headers
155 155 use_htsts = false
156 156
157 157 ## number of commits stats will parse on each iteration
158 158 commit_parse_limit = 25
159 159
160 160 ## git rev filter option, --all is the default filter, if you need to
161 161 ## hide all refs in changelog switch this to --branches --tags
162 162 git_rev_filter = --branches --tags
163 163
164 164 # Set to true if your repos are exposed using the dumb protocol
165 165 git_update_server_info = false
166 166
167 167 ## RSS/ATOM feed options
168 168 rss_cut_off_limit = 256000
169 169 rss_items_per_page = 10
170 170 rss_include_diff = false
171 171
172 172 ## gist URL alias, used to create nicer urls for gist. This should be an
173 173 ## url that does rewrites to _admin/gists/{gistid}.
174 174 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
175 175 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
176 176 gist_alias_url =
177 177
178 178 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
179 179 ## used for access.
180 180 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
181 181 ## came from the the logged in user who own this authentication token.
182 182 ##
183 183 ## list of all views can be found under `_admin/permissions/auth_token_access`
184 184 ## The list should be "," separated and on a single line.
185 185 ##
186 186 ## Most common views to enable:
187 187 # RepoCommitsView:repo_commit_download
188 188 # RepoCommitsView:repo_commit_patch
189 189 # RepoCommitsView:repo_commit_raw
190 190 # RepoFilesView:repo_files_diff
191 191 # RepoFilesView:repo_archivefile
192 192 # RepoFilesView:repo_file_raw
193 193 # GistView:*
194 194 api_access_controllers_whitelist =
195 195
196 196 ## default encoding used to convert from and to unicode
197 197 ## can be also a comma separated list of encoding in case of mixed encodings
198 198 default_encoding = UTF-8
199 199
200 200 ## instance-id prefix
201 201 ## a prefix key for this instance used for cache invalidation when running
202 202 ## multiple instances of rhodecode, make sure it's globally unique for
203 203 ## all running rhodecode instances. Leave empty if you don't use it
204 204 instance_id =
205 205
206 206 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
207 207 ## of an authentication plugin also if it is disabled by it's settings.
208 208 ## This could be useful if you are unable to log in to the system due to broken
209 209 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
210 210 ## module to log in again and fix the settings.
211 211 ##
212 212 ## Available builtin plugin IDs (hash is part of the ID):
213 213 ## egg:rhodecode-enterprise-ce#rhodecode
214 214 ## egg:rhodecode-enterprise-ce#pam
215 215 ## egg:rhodecode-enterprise-ce#ldap
216 216 ## egg:rhodecode-enterprise-ce#jasig_cas
217 217 ## egg:rhodecode-enterprise-ce#headers
218 218 ## egg:rhodecode-enterprise-ce#crowd
219 219 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
220 220
221 221 ## alternative return HTTP header for failed authentication. Default HTTP
222 222 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
223 223 ## handling that causing a series of failed authentication calls.
224 224 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
225 225 ## This will be served instead of default 401 on bad authnetication
226 226 auth_ret_code =
227 227
228 228 ## use special detection method when serving auth_ret_code, instead of serving
229 229 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
230 230 ## and then serve auth_ret_code to clients
231 231 auth_ret_code_detection = false
232 232
233 233 ## locking return code. When repository is locked return this HTTP code. 2XX
234 234 ## codes don't break the transactions while 4XX codes do
235 235 lock_ret_code = 423
236 236
237 237 ## allows to change the repository location in settings page
238 238 allow_repo_location_change = true
239 239
240 240 ## allows to setup custom hooks in settings page
241 241 allow_custom_hooks_settings = true
242 242
243 243 ## generated license token, goto license page in RhodeCode settings to obtain
244 244 ## new token
245 245 license_token =
246 246
247 247 ## supervisor connection uri, for managing supervisor and logs.
248 248 supervisor.uri =
249 249 ## supervisord group name/id we only want this RC instance to handle
250 250 supervisor.group_id = prod
251 251
252 252 ## Display extended labs settings
253 253 labs_settings_active = true
254 254
255 255 ####################################
256 256 ### CELERY CONFIG ####
257 257 ####################################
258 258 use_celery = false
259 259 broker.host = localhost
260 260 broker.vhost = rabbitmqhost
261 261 broker.port = 5672
262 262 broker.user = rabbitmq
263 263 broker.password = qweqwe
264 264
265 265 celery.imports = rhodecode.lib.celerylib.tasks
266 266
267 267 celery.result.backend = amqp
268 268 celery.result.dburi = amqp://
269 269 celery.result.serialier = json
270 270
271 271 #celery.send.task.error.emails = true
272 272 #celery.amqp.task.result.expires = 18000
273 273
274 274 celeryd.concurrency = 2
275 275 #celeryd.log.file = celeryd.log
276 276 celeryd.log.level = debug
277 277 celeryd.max.tasks.per.child = 1
278 278
279 279 ## tasks will never be sent to the queue, but executed locally instead.
280 280 celery.always.eager = false
281 281
282 282 ####################################
283 283 ### BEAKER CACHE ####
284 284 ####################################
285 285 # default cache dir for templates. Putting this into a ramdisk
286 286 ## can boost performance, eg. %(here)s/data_ramdisk
287 287 cache_dir = %(here)s/data
288 288
289 289 ## locking and default file storage for Beaker. Putting this into a ramdisk
290 290 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
291 291 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
292 292 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
293 293
294 294 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
295 295
296 296 beaker.cache.super_short_term.type = memory
297 297 beaker.cache.super_short_term.expire = 10
298 298 beaker.cache.super_short_term.key_length = 256
299 299
300 300 beaker.cache.short_term.type = memory
301 301 beaker.cache.short_term.expire = 60
302 302 beaker.cache.short_term.key_length = 256
303 303
304 304 beaker.cache.long_term.type = memory
305 305 beaker.cache.long_term.expire = 36000
306 306 beaker.cache.long_term.key_length = 256
307 307
308 308 beaker.cache.sql_cache_short.type = memory
309 309 beaker.cache.sql_cache_short.expire = 10
310 310 beaker.cache.sql_cache_short.key_length = 256
311 311
312 312 ## default is memory cache, configure only if required
313 313 ## using multi-node or multi-worker setup
314 314 #beaker.cache.auth_plugins.type = ext:database
315 315 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
316 316 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
317 317 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
318 318 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
319 319 #beaker.cache.auth_plugins.sa.pool_size = 10
320 320 #beaker.cache.auth_plugins.sa.max_overflow = 0
321 321
322 322 beaker.cache.repo_cache_long.type = memorylru_base
323 323 beaker.cache.repo_cache_long.max_items = 4096
324 324 beaker.cache.repo_cache_long.expire = 2592000
325 325
326 326 ## default is memorylru_base cache, configure only if required
327 327 ## using multi-node or multi-worker setup
328 328 #beaker.cache.repo_cache_long.type = ext:memcached
329 329 #beaker.cache.repo_cache_long.url = localhost:11211
330 330 #beaker.cache.repo_cache_long.expire = 1209600
331 331 #beaker.cache.repo_cache_long.key_length = 256
332 332
333 333 ####################################
334 334 ### BEAKER SESSION ####
335 335 ####################################
336 336
337 337 ## .session.type is type of storage options for the session, current allowed
338 338 ## types are file, ext:memcached, ext:database, and memory (default).
339 339 beaker.session.type = file
340 340 beaker.session.data_dir = %(here)s/data/sessions/data
341 341
342 342 ## db based session, fast, and allows easy management over logged in users
343 343 #beaker.session.type = ext:database
344 344 #beaker.session.table_name = db_session
345 345 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
346 346 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
347 347 #beaker.session.sa.pool_recycle = 3600
348 348 #beaker.session.sa.echo = false
349 349
350 350 beaker.session.key = rhodecode
351 351 beaker.session.secret = production-rc-uytcxaz
352 352 beaker.session.lock_dir = %(here)s/data/sessions/lock
353 353
354 354 ## Secure encrypted cookie. Requires AES and AES python libraries
355 355 ## you must disable beaker.session.secret to use this
356 356 #beaker.session.encrypt_key = key_for_encryption
357 357 #beaker.session.validate_key = validation_key
358 358
359 359 ## sets session as invalid(also logging out user) if it haven not been
360 360 ## accessed for given amount of time in seconds
361 361 beaker.session.timeout = 2592000
362 362 beaker.session.httponly = true
363 363 ## Path to use for the cookie. Set to prefix if you use prefix middleware
364 364 #beaker.session.cookie_path = /custom_prefix
365 365
366 366 ## uncomment for https secure cookie
367 367 beaker.session.secure = false
368 368
369 369 ## auto save the session to not to use .save()
370 370 beaker.session.auto = false
371 371
372 372 ## default cookie expiration time in seconds, set to `true` to set expire
373 373 ## at browser close
374 374 #beaker.session.cookie_expires = 3600
375 375
376 376 ###################################
377 377 ## SEARCH INDEXING CONFIGURATION ##
378 378 ###################################
379 379 ## Full text search indexer is available in rhodecode-tools under
380 380 ## `rhodecode-tools index` command
381 381
382 382 ## WHOOSH Backend, doesn't require additional services to run
383 383 ## it works good with few dozen repos
384 384 search.module = rhodecode.lib.index.whoosh
385 385 search.location = %(here)s/data/index
386 386
387 387 ########################################
388 388 ### CHANNELSTREAM CONFIG ####
389 389 ########################################
390 390 ## channelstream enables persistent connections and live notification
391 391 ## in the system. It's also used by the chat system
392 392 channelstream.enabled = false
393 393
394 394 ## server address for channelstream server on the backend
395 395 channelstream.server = 127.0.0.1:9800
396 396
397 397 ## location of the channelstream server from outside world
398 398 ## use ws:// for http or wss:// for https. This address needs to be handled
399 399 ## by external HTTP server such as Nginx or Apache
400 400 ## see nginx/apache configuration examples in our docs
401 401 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
402 402 channelstream.secret = secret
403 403 channelstream.history.location = %(here)s/channelstream_history
404 404
405 405 ## Internal application path that Javascript uses to connect into.
406 406 ## If you use proxy-prefix the prefix should be added before /_channelstream
407 407 channelstream.proxy_path = /_channelstream
408 408
409 409
410 410 ###################################
411 411 ## APPENLIGHT CONFIG ##
412 412 ###################################
413 413
414 414 ## Appenlight is tailored to work with RhodeCode, see
415 415 ## http://appenlight.com for details how to obtain an account
416 416
417 417 ## appenlight integration enabled
418 418 appenlight = false
419 419
420 420 appenlight.server_url = https://api.appenlight.com
421 421 appenlight.api_key = YOUR_API_KEY
422 422 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
423 423
424 424 # used for JS client
425 425 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
426 426
427 427 ## TWEAK AMOUNT OF INFO SENT HERE
428 428
429 429 ## enables 404 error logging (default False)
430 430 appenlight.report_404 = false
431 431
432 432 ## time in seconds after request is considered being slow (default 1)
433 433 appenlight.slow_request_time = 1
434 434
435 435 ## record slow requests in application
436 436 ## (needs to be enabled for slow datastore recording and time tracking)
437 437 appenlight.slow_requests = true
438 438
439 439 ## enable hooking to application loggers
440 440 appenlight.logging = true
441 441
442 442 ## minimum log level for log capture
443 443 appenlight.logging.level = WARNING
444 444
445 445 ## send logs only from erroneous/slow requests
446 446 ## (saves API quota for intensive logging)
447 447 appenlight.logging_on_error = false
448 448
449 449 ## list of additonal keywords that should be grabbed from environ object
450 450 ## can be string with comma separated list of words in lowercase
451 451 ## (by default client will always send following info:
452 452 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
453 453 ## start with HTTP* this list be extended with additional keywords here
454 454 appenlight.environ_keys_whitelist =
455 455
456 456 ## list of keywords that should be blanked from request object
457 457 ## can be string with comma separated list of words in lowercase
458 458 ## (by default client will always blank keys that contain following words
459 459 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
460 460 ## this list be extended with additional keywords set here
461 461 appenlight.request_keys_blacklist =
462 462
463 463 ## list of namespaces that should be ignores when gathering log entries
464 464 ## can be string with comma separated list of namespaces
465 465 ## (by default the client ignores own entries: appenlight_client.client)
466 466 appenlight.log_namespace_blacklist =
467 467
468 468
469 469 ################################################################################
470 470 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
471 471 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
472 472 ## execute malicious code after an exception is raised. ##
473 473 ################################################################################
474 474 set debug = false
475 475
476 476
477 477 ###########################################
478 478 ### MAIN RHODECODE DATABASE CONFIG ###
479 479 ###########################################
480 480 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
481 481 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
482 482 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
483 483 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
484 484
485 485 # see sqlalchemy docs for other advanced settings
486 486
487 487 ## print the sql statements to output
488 488 sqlalchemy.db1.echo = false
489 489 ## recycle the connections after this amount of seconds
490 490 sqlalchemy.db1.pool_recycle = 3600
491 491 sqlalchemy.db1.convert_unicode = true
492 492
493 493 ## the number of connections to keep open inside the connection pool.
494 494 ## 0 indicates no limit
495 495 #sqlalchemy.db1.pool_size = 5
496 496
497 497 ## the number of connections to allow in connection pool "overflow", that is
498 498 ## connections that can be opened above and beyond the pool_size setting,
499 499 ## which defaults to five.
500 500 #sqlalchemy.db1.max_overflow = 10
501 501
502 502
503 503 ##################
504 504 ### VCS CONFIG ###
505 505 ##################
506 506 vcs.server.enable = true
507 507 vcs.server = localhost:9900
508 508
509 509 ## Web server connectivity protocol, responsible for web based VCS operatations
510 510 ## Available protocols are:
511 511 ## `http` - use http-rpc backend (default)
512 512 vcs.server.protocol = http
513 513
514 514 ## Push/Pull operations protocol, available options are:
515 515 ## `http` - use http-rpc backend (default)
516 516 ##
517 517 vcs.scm_app_implementation = http
518 518
519 519 ## Push/Pull operations hooks protocol, available options are:
520 520 ## `http` - use http-rpc backend (default)
521 521 vcs.hooks.protocol = http
522 522
523 523 vcs.server.log_level = info
524 524 ## Start VCSServer with this instance as a subprocess, usefull for development
525 525 vcs.start_server = false
526 526
527 527 ## List of enabled VCS backends, available options are:
528 528 ## `hg` - mercurial
529 529 ## `git` - git
530 530 ## `svn` - subversion
531 531 vcs.backends = hg, git, svn
532 532
533 533 vcs.connection_timeout = 3600
534 534 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
535 535 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
536 536 #vcs.svn.compatible_version = pre-1.8-compatible
537 537
538 538
539 539 ############################################################
540 540 ### Subversion proxy support (mod_dav_svn) ###
541 541 ### Maps RhodeCode repo groups into SVN paths for Apache ###
542 542 ############################################################
543 543 ## Enable or disable the config file generation.
544 544 svn.proxy.generate_config = false
545 545 ## Generate config file with `SVNListParentPath` set to `On`.
546 546 svn.proxy.list_parent_path = true
547 547 ## Set location and file name of generated config file.
548 548 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
549 549 ## Used as a prefix to the `Location` block in the generated config file.
550 550 ## In most cases it should be set to `/`.
551 551 svn.proxy.location_root = /
552 552 ## Command to reload the mod dav svn configuration on change.
553 553 ## Example: `/etc/init.d/apache2 reload`
554 554 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
555 555 ## If the timeout expires before the reload command finishes, the command will
556 556 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
557 557 #svn.proxy.reload_timeout = 10
558 558
559 ############################################################
560 ### SSH Support Settings ###
561 ############################################################
562
563 ## Defines if the authorized_keys file should be written on any change of
564 ## user ssh keys
565 ssh.generate_authorized_keyfile = false
566
567 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
568 # ssh.authorized_keys_ssh_opts =
569
570 ## File to generate the authorized keys together with options
571 ssh.authorized_keys_file_path = /home/USER/.ssh/authorized_keys
572
573 ## Command to execute as an SSH wrapper, available from
574 ## https://code.rhodecode.com/rhodecode-ssh
575 ssh.wrapper_cmd = /home/USER/rhodecode-ssh/sshwrapper.py
576
577 ## Allow shell when executing the command
578 ssh.wrapper_cmd_allow_shell = false
579
559 580 ## Dummy marker to add new entries after.
560 581 ## Add any custom entries below. Please don't remove.
561 582 custom.conf = 1
562 583
563 584
564 585 ################################
565 586 ### LOGGING CONFIGURATION ####
566 587 ################################
567 588 [loggers]
568 589 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
569 590
570 591 [handlers]
571 592 keys = console, console_sql
572 593
573 594 [formatters]
574 595 keys = generic, color_formatter, color_formatter_sql
575 596
576 597 #############
577 598 ## LOGGERS ##
578 599 #############
579 600 [logger_root]
580 601 level = NOTSET
581 602 handlers = console
582 603
583 604 [logger_routes]
584 605 level = DEBUG
585 606 handlers =
586 607 qualname = routes.middleware
587 608 ## "level = DEBUG" logs the route matched and routing variables.
588 609 propagate = 1
589 610
590 611 [logger_beaker]
591 612 level = DEBUG
592 613 handlers =
593 614 qualname = beaker.container
594 615 propagate = 1
595 616
596 617 [logger_templates]
597 618 level = INFO
598 619 handlers =
599 620 qualname = pylons.templating
600 621 propagate = 1
601 622
602 623 [logger_rhodecode]
603 624 level = DEBUG
604 625 handlers =
605 626 qualname = rhodecode
606 627 propagate = 1
607 628
608 629 [logger_sqlalchemy]
609 630 level = INFO
610 631 handlers = console_sql
611 632 qualname = sqlalchemy.engine
612 633 propagate = 0
613 634
614 635 ##############
615 636 ## HANDLERS ##
616 637 ##############
617 638
618 639 [handler_console]
619 640 class = StreamHandler
620 641 args = (sys.stderr, )
621 642 level = INFO
622 643 formatter = generic
623 644
624 645 [handler_console_sql]
625 646 class = StreamHandler
626 647 args = (sys.stderr, )
627 648 level = WARN
628 649 formatter = generic
629 650
630 651 ################
631 652 ## FORMATTERS ##
632 653 ################
633 654
634 655 [formatter_generic]
635 656 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
636 657 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
637 658 datefmt = %Y-%m-%d %H:%M:%S
638 659
639 660 [formatter_color_formatter]
640 661 class = rhodecode.lib.logging_formatter.ColorFormatter
641 662 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
642 663 datefmt = %Y-%m-%d %H:%M:%S
643 664
644 665 [formatter_color_formatter_sql]
645 666 class = rhodecode.lib.logging_formatter.ColorFormatterSql
646 667 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
647 668 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,630 +1,637 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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 logging
22 22 import datetime
23 23 import formencode
24 24
25 25 from pyramid.httpexceptions import HTTPFound
26 26 from pyramid.view import view_config
27 27 from sqlalchemy.sql.functions import coalesce
28 28 from sqlalchemy.exc import IntegrityError
29 29
30 30 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
32 from rhodecode.events import trigger
31 33
32 34 from rhodecode.lib import audit_logger
33 35 from rhodecode.lib.ext_json import json
34 36 from rhodecode.lib.auth import (
35 37 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
36 38 from rhodecode.lib import helpers as h
37 39 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 40 from rhodecode.model.auth_token import AuthTokenModel
39 41 from rhodecode.model.ssh_key import SshKeyModel
40 42 from rhodecode.model.user import UserModel
41 43 from rhodecode.model.user_group import UserGroupModel
42 44 from rhodecode.model.db import (
43 45 or_, User, UserIpMap, UserEmailMap, UserApiKeys, UserSshKeys)
44 46 from rhodecode.model.meta import Session
45 47
46 48 log = logging.getLogger(__name__)
47 49
48 50
49 51 class AdminUsersView(BaseAppView, DataGridAppView):
50 52 ALLOW_SCOPED_TOKENS = False
51 53 """
52 54 This view has alternative version inside EE, if modified please take a look
53 55 in there as well.
54 56 """
55 57
56 58 def load_default_context(self):
57 59 c = self._get_local_tmpl_context()
58 60 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
59 61 self._register_global_c(c)
60 62 return c
61 63
62 64 def _redirect_for_default_user(self, username):
63 65 _ = self.request.translate
64 66 if username == User.DEFAULT_USER:
65 67 h.flash(_("You can't edit this user"), category='warning')
66 68 # TODO(marcink): redirect to 'users' admin panel once this
67 69 # is a pyramid view
68 70 raise HTTPFound('/')
69 71
70 72 @HasPermissionAllDecorator('hg.admin')
71 73 @view_config(
72 74 route_name='users', request_method='GET',
73 75 renderer='rhodecode:templates/admin/users/users.mako')
74 76 def users_list(self):
75 77 c = self.load_default_context()
76 78 return self._get_template_context(c)
77 79
78 80 @HasPermissionAllDecorator('hg.admin')
79 81 @view_config(
80 82 # renderer defined below
81 83 route_name='users_data', request_method='GET',
82 84 renderer='json_ext', xhr=True)
83 85 def users_list_data(self):
84 86 column_map = {
85 87 'first_name': 'name',
86 88 'last_name': 'lastname',
87 89 }
88 90 draw, start, limit = self._extract_chunk(self.request)
89 91 search_q, order_by, order_dir = self._extract_ordering(
90 92 self.request, column_map=column_map)
91 93
92 94 _render = self.request.get_partial_renderer(
93 95 'data_table/_dt_elements.mako')
94 96
95 97 def user_actions(user_id, username):
96 98 return _render("user_actions", user_id, username)
97 99
98 100 users_data_total_count = User.query()\
99 101 .filter(User.username != User.DEFAULT_USER) \
100 102 .count()
101 103
102 104 # json generate
103 105 base_q = User.query().filter(User.username != User.DEFAULT_USER)
104 106
105 107 if search_q:
106 108 like_expression = u'%{}%'.format(safe_unicode(search_q))
107 109 base_q = base_q.filter(or_(
108 110 User.username.ilike(like_expression),
109 111 User._email.ilike(like_expression),
110 112 User.name.ilike(like_expression),
111 113 User.lastname.ilike(like_expression),
112 114 ))
113 115
114 116 users_data_total_filtered_count = base_q.count()
115 117
116 118 sort_col = getattr(User, order_by, None)
117 119 if sort_col:
118 120 if order_dir == 'asc':
119 121 # handle null values properly to order by NULL last
120 122 if order_by in ['last_activity']:
121 123 sort_col = coalesce(sort_col, datetime.date.max)
122 124 sort_col = sort_col.asc()
123 125 else:
124 126 # handle null values properly to order by NULL last
125 127 if order_by in ['last_activity']:
126 128 sort_col = coalesce(sort_col, datetime.date.min)
127 129 sort_col = sort_col.desc()
128 130
129 131 base_q = base_q.order_by(sort_col)
130 132 base_q = base_q.offset(start).limit(limit)
131 133
132 134 users_list = base_q.all()
133 135
134 136 users_data = []
135 137 for user in users_list:
136 138 users_data.append({
137 139 "username": h.gravatar_with_user(self.request, user.username),
138 140 "email": user.email,
139 141 "first_name": user.first_name,
140 142 "last_name": user.last_name,
141 143 "last_login": h.format_date(user.last_login),
142 144 "last_activity": h.format_date(user.last_activity),
143 145 "active": h.bool2icon(user.active),
144 146 "active_raw": user.active,
145 147 "admin": h.bool2icon(user.admin),
146 148 "extern_type": user.extern_type,
147 149 "extern_name": user.extern_name,
148 150 "action": user_actions(user.user_id, user.username),
149 151 })
150 152
151 153 data = ({
152 154 'draw': draw,
153 155 'data': users_data,
154 156 'recordsTotal': users_data_total_count,
155 157 'recordsFiltered': users_data_total_filtered_count,
156 158 })
157 159
158 160 return data
159 161
160 162 @LoginRequired()
161 163 @HasPermissionAllDecorator('hg.admin')
162 164 @view_config(
163 165 route_name='edit_user_auth_tokens', request_method='GET',
164 166 renderer='rhodecode:templates/admin/users/user_edit.mako')
165 167 def auth_tokens(self):
166 168 _ = self.request.translate
167 169 c = self.load_default_context()
168 170
169 171 user_id = self.request.matchdict.get('user_id')
170 172 c.user = User.get_or_404(user_id)
171 173 self._redirect_for_default_user(c.user.username)
172 174
173 175 c.active = 'auth_tokens'
174 176
175 177 c.lifetime_values = [
176 178 (str(-1), _('forever')),
177 179 (str(5), _('5 minutes')),
178 180 (str(60), _('1 hour')),
179 181 (str(60 * 24), _('1 day')),
180 182 (str(60 * 24 * 30), _('1 month')),
181 183 ]
182 184 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
183 185 c.role_values = [
184 186 (x, AuthTokenModel.cls._get_role_name(x))
185 187 for x in AuthTokenModel.cls.ROLES]
186 188 c.role_options = [(c.role_values, _("Role"))]
187 189 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
188 190 c.user.user_id, show_expired=True)
189 191 return self._get_template_context(c)
190 192
191 193 def maybe_attach_token_scope(self, token):
192 194 # implemented in EE edition
193 195 pass
194 196
195 197 @LoginRequired()
196 198 @HasPermissionAllDecorator('hg.admin')
197 199 @CSRFRequired()
198 200 @view_config(
199 201 route_name='edit_user_auth_tokens_add', request_method='POST')
200 202 def auth_tokens_add(self):
201 203 _ = self.request.translate
202 204 c = self.load_default_context()
203 205
204 206 user_id = self.request.matchdict.get('user_id')
205 207 c.user = User.get_or_404(user_id)
206 208
207 209 self._redirect_for_default_user(c.user.username)
208 210
209 211 user_data = c.user.get_api_data()
210 212 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
211 213 description = self.request.POST.get('description')
212 214 role = self.request.POST.get('role')
213 215
214 216 token = AuthTokenModel().create(
215 217 c.user.user_id, description, lifetime, role)
216 218 token_data = token.get_api_data()
217 219
218 220 self.maybe_attach_token_scope(token)
219 221 audit_logger.store_web(
220 222 'user.edit.token.add', action_data={
221 223 'data': {'token': token_data, 'user': user_data}},
222 224 user=self._rhodecode_user, )
223 225 Session().commit()
224 226
225 227 h.flash(_("Auth token successfully created"), category='success')
226 228 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
227 229
228 230 @LoginRequired()
229 231 @HasPermissionAllDecorator('hg.admin')
230 232 @CSRFRequired()
231 233 @view_config(
232 234 route_name='edit_user_auth_tokens_delete', request_method='POST')
233 235 def auth_tokens_delete(self):
234 236 _ = self.request.translate
235 237 c = self.load_default_context()
236 238
237 239 user_id = self.request.matchdict.get('user_id')
238 240 c.user = User.get_or_404(user_id)
239 241 self._redirect_for_default_user(c.user.username)
240 242 user_data = c.user.get_api_data()
241 243
242 244 del_auth_token = self.request.POST.get('del_auth_token')
243 245
244 246 if del_auth_token:
245 247 token = UserApiKeys.get_or_404(del_auth_token)
246 248 token_data = token.get_api_data()
247 249
248 250 AuthTokenModel().delete(del_auth_token, c.user.user_id)
249 251 audit_logger.store_web(
250 252 'user.edit.token.delete', action_data={
251 253 'data': {'token': token_data, 'user': user_data}},
252 254 user=self._rhodecode_user,)
253 255 Session().commit()
254 256 h.flash(_("Auth token successfully deleted"), category='success')
255 257
256 258 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
257 259
258 260 @LoginRequired()
259 261 @HasPermissionAllDecorator('hg.admin')
260 262 @view_config(
261 263 route_name='edit_user_ssh_keys', request_method='GET',
262 264 renderer='rhodecode:templates/admin/users/user_edit.mako')
263 265 def ssh_keys(self):
264 266 _ = self.request.translate
265 267 c = self.load_default_context()
266 268
267 269 user_id = self.request.matchdict.get('user_id')
268 270 c.user = User.get_or_404(user_id)
269 271 self._redirect_for_default_user(c.user.username)
270 272
271 273 c.active = 'ssh_keys'
272 274 c.default_key = self.request.GET.get('default_key')
273 275 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
274 276 return self._get_template_context(c)
275 277
276 278 @LoginRequired()
277 279 @HasPermissionAllDecorator('hg.admin')
278 280 @view_config(
279 281 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
280 282 renderer='rhodecode:templates/admin/users/user_edit.mako')
281 283 def ssh_keys_generate_keypair(self):
282 284 _ = self.request.translate
283 285 c = self.load_default_context()
284 286
285 287 user_id = self.request.matchdict.get('user_id')
286 288 c.user = User.get_or_404(user_id)
287 289 self._redirect_for_default_user(c.user.username)
288 290
289 291 c.active = 'ssh_keys_generate'
290 292 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
291 293 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
292 294
293 295 return self._get_template_context(c)
294 296
295 297 @LoginRequired()
296 298 @HasPermissionAllDecorator('hg.admin')
297 299 @CSRFRequired()
298 300 @view_config(
299 301 route_name='edit_user_ssh_keys_add', request_method='POST')
300 302 def ssh_keys_add(self):
301 303 _ = self.request.translate
302 304 c = self.load_default_context()
303 305
304 306 user_id = self.request.matchdict.get('user_id')
305 307 c.user = User.get_or_404(user_id)
306 308
307 309 self._redirect_for_default_user(c.user.username)
308 310
309 311 user_data = c.user.get_api_data()
310 312 key_data = self.request.POST.get('key_data')
311 313 description = self.request.POST.get('description')
312 314
313 315 try:
314 316 if not key_data:
315 317 raise ValueError('Please add a valid public key')
316 318
317 319 key = SshKeyModel().parse_key(key_data.strip())
318 320 fingerprint = key.hash_md5()
319 321
320 322 ssh_key = SshKeyModel().create(
321 323 c.user.user_id, fingerprint, key_data, description)
322 324 ssh_key_data = ssh_key.get_api_data()
323 325
324 326 audit_logger.store_web(
325 327 'user.edit.ssh_key.add', action_data={
326 328 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
327 329 user=self._rhodecode_user, )
328 330 Session().commit()
329 331
332 # Trigger an event on change of keys.
333 trigger(SshKeyFileChangeEvent(), self.request.registry)
334
330 335 h.flash(_("Ssh Key successfully created"), category='success')
331 336
332 337 except IntegrityError:
333 338 log.exception("Exception during ssh key saving")
334 339 h.flash(_('An error occurred during ssh key saving: {}').format(
335 340 'Such key already exists, please use a different one'),
336 341 category='error')
337 342 except Exception as e:
338 343 log.exception("Exception during ssh key saving")
339 344 h.flash(_('An error occurred during ssh key saving: {}').format(e),
340 345 category='error')
341 346
342 347 return HTTPFound(
343 348 h.route_path('edit_user_ssh_keys', user_id=user_id))
344 349
345 350 @LoginRequired()
346 351 @HasPermissionAllDecorator('hg.admin')
347 352 @CSRFRequired()
348 353 @view_config(
349 354 route_name='edit_user_ssh_keys_delete', request_method='POST')
350 355 def ssh_keys_delete(self):
351 356 _ = self.request.translate
352 357 c = self.load_default_context()
353 358
354 359 user_id = self.request.matchdict.get('user_id')
355 360 c.user = User.get_or_404(user_id)
356 361 self._redirect_for_default_user(c.user.username)
357 362 user_data = c.user.get_api_data()
358 363
359 364 del_ssh_key = self.request.POST.get('del_ssh_key')
360 365
361 366 if del_ssh_key:
362 367 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
363 368 ssh_key_data = ssh_key.get_api_data()
364 369
365 370 SshKeyModel().delete(del_ssh_key, c.user.user_id)
366 371 audit_logger.store_web(
367 372 'user.edit.ssh_key.delete', action_data={
368 373 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
369 374 user=self._rhodecode_user,)
370 375 Session().commit()
376 # Trigger an event on change of keys.
377 trigger(SshKeyFileChangeEvent(), self.request.registry)
371 378 h.flash(_("Ssh key successfully deleted"), category='success')
372 379
373 380 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
374 381
375 382 @LoginRequired()
376 383 @HasPermissionAllDecorator('hg.admin')
377 384 @view_config(
378 385 route_name='edit_user_emails', request_method='GET',
379 386 renderer='rhodecode:templates/admin/users/user_edit.mako')
380 387 def emails(self):
381 388 _ = self.request.translate
382 389 c = self.load_default_context()
383 390
384 391 user_id = self.request.matchdict.get('user_id')
385 392 c.user = User.get_or_404(user_id)
386 393 self._redirect_for_default_user(c.user.username)
387 394
388 395 c.active = 'emails'
389 396 c.user_email_map = UserEmailMap.query() \
390 397 .filter(UserEmailMap.user == c.user).all()
391 398
392 399 return self._get_template_context(c)
393 400
394 401 @LoginRequired()
395 402 @HasPermissionAllDecorator('hg.admin')
396 403 @CSRFRequired()
397 404 @view_config(
398 405 route_name='edit_user_emails_add', request_method='POST')
399 406 def emails_add(self):
400 407 _ = self.request.translate
401 408 c = self.load_default_context()
402 409
403 410 user_id = self.request.matchdict.get('user_id')
404 411 c.user = User.get_or_404(user_id)
405 412 self._redirect_for_default_user(c.user.username)
406 413
407 414 email = self.request.POST.get('new_email')
408 415 user_data = c.user.get_api_data()
409 416 try:
410 417 UserModel().add_extra_email(c.user.user_id, email)
411 418 audit_logger.store_web(
412 419 'user.edit.email.add', action_data={'email': email, 'user': user_data},
413 420 user=self._rhodecode_user)
414 421 Session().commit()
415 422 h.flash(_("Added new email address `%s` for user account") % email,
416 423 category='success')
417 424 except formencode.Invalid as error:
418 425 h.flash(h.escape(error.error_dict['email']), category='error')
419 426 except Exception:
420 427 log.exception("Exception during email saving")
421 428 h.flash(_('An error occurred during email saving'),
422 429 category='error')
423 430 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
424 431
425 432 @LoginRequired()
426 433 @HasPermissionAllDecorator('hg.admin')
427 434 @CSRFRequired()
428 435 @view_config(
429 436 route_name='edit_user_emails_delete', request_method='POST')
430 437 def emails_delete(self):
431 438 _ = self.request.translate
432 439 c = self.load_default_context()
433 440
434 441 user_id = self.request.matchdict.get('user_id')
435 442 c.user = User.get_or_404(user_id)
436 443 self._redirect_for_default_user(c.user.username)
437 444
438 445 email_id = self.request.POST.get('del_email_id')
439 446 user_model = UserModel()
440 447
441 448 email = UserEmailMap.query().get(email_id).email
442 449 user_data = c.user.get_api_data()
443 450 user_model.delete_extra_email(c.user.user_id, email_id)
444 451 audit_logger.store_web(
445 452 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
446 453 user=self._rhodecode_user)
447 454 Session().commit()
448 455 h.flash(_("Removed email address from user account"),
449 456 category='success')
450 457 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
451 458
452 459 @LoginRequired()
453 460 @HasPermissionAllDecorator('hg.admin')
454 461 @view_config(
455 462 route_name='edit_user_ips', request_method='GET',
456 463 renderer='rhodecode:templates/admin/users/user_edit.mako')
457 464 def ips(self):
458 465 _ = self.request.translate
459 466 c = self.load_default_context()
460 467
461 468 user_id = self.request.matchdict.get('user_id')
462 469 c.user = User.get_or_404(user_id)
463 470 self._redirect_for_default_user(c.user.username)
464 471
465 472 c.active = 'ips'
466 473 c.user_ip_map = UserIpMap.query() \
467 474 .filter(UserIpMap.user == c.user).all()
468 475
469 476 c.inherit_default_ips = c.user.inherit_default_permissions
470 477 c.default_user_ip_map = UserIpMap.query() \
471 478 .filter(UserIpMap.user == User.get_default_user()).all()
472 479
473 480 return self._get_template_context(c)
474 481
475 482 @LoginRequired()
476 483 @HasPermissionAllDecorator('hg.admin')
477 484 @CSRFRequired()
478 485 @view_config(
479 486 route_name='edit_user_ips_add', request_method='POST')
480 487 def ips_add(self):
481 488 _ = self.request.translate
482 489 c = self.load_default_context()
483 490
484 491 user_id = self.request.matchdict.get('user_id')
485 492 c.user = User.get_or_404(user_id)
486 493 # NOTE(marcink): this view is allowed for default users, as we can
487 494 # edit their IP white list
488 495
489 496 user_model = UserModel()
490 497 desc = self.request.POST.get('description')
491 498 try:
492 499 ip_list = user_model.parse_ip_range(
493 500 self.request.POST.get('new_ip'))
494 501 except Exception as e:
495 502 ip_list = []
496 503 log.exception("Exception during ip saving")
497 504 h.flash(_('An error occurred during ip saving:%s' % (e,)),
498 505 category='error')
499 506 added = []
500 507 user_data = c.user.get_api_data()
501 508 for ip in ip_list:
502 509 try:
503 510 user_model.add_extra_ip(c.user.user_id, ip, desc)
504 511 audit_logger.store_web(
505 512 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
506 513 user=self._rhodecode_user)
507 514 Session().commit()
508 515 added.append(ip)
509 516 except formencode.Invalid as error:
510 517 msg = error.error_dict['ip']
511 518 h.flash(msg, category='error')
512 519 except Exception:
513 520 log.exception("Exception during ip saving")
514 521 h.flash(_('An error occurred during ip saving'),
515 522 category='error')
516 523 if added:
517 524 h.flash(
518 525 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
519 526 category='success')
520 527 if 'default_user' in self.request.POST:
521 528 # case for editing global IP list we do it for 'DEFAULT' user
522 529 raise HTTPFound(h.route_path('admin_permissions_ips'))
523 530 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
524 531
525 532 @LoginRequired()
526 533 @HasPermissionAllDecorator('hg.admin')
527 534 @CSRFRequired()
528 535 @view_config(
529 536 route_name='edit_user_ips_delete', request_method='POST')
530 537 def ips_delete(self):
531 538 _ = self.request.translate
532 539 c = self.load_default_context()
533 540
534 541 user_id = self.request.matchdict.get('user_id')
535 542 c.user = User.get_or_404(user_id)
536 543 # NOTE(marcink): this view is allowed for default users, as we can
537 544 # edit their IP white list
538 545
539 546 ip_id = self.request.POST.get('del_ip_id')
540 547 user_model = UserModel()
541 548 user_data = c.user.get_api_data()
542 549 ip = UserIpMap.query().get(ip_id).ip_addr
543 550 user_model.delete_extra_ip(c.user.user_id, ip_id)
544 551 audit_logger.store_web(
545 552 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
546 553 user=self._rhodecode_user)
547 554 Session().commit()
548 555 h.flash(_("Removed ip address from user whitelist"), category='success')
549 556
550 557 if 'default_user' in self.request.POST:
551 558 # case for editing global IP list we do it for 'DEFAULT' user
552 559 raise HTTPFound(h.route_path('admin_permissions_ips'))
553 560 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
554 561
555 562 @LoginRequired()
556 563 @HasPermissionAllDecorator('hg.admin')
557 564 @view_config(
558 565 route_name='edit_user_groups_management', request_method='GET',
559 566 renderer='rhodecode:templates/admin/users/user_edit.mako')
560 567 def groups_management(self):
561 568 c = self.load_default_context()
562 569
563 570 user_id = self.request.matchdict.get('user_id')
564 571 c.user = User.get_or_404(user_id)
565 572 c.data = c.user.group_member
566 573 self._redirect_for_default_user(c.user.username)
567 574 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
568 575 for group in c.user.group_member]
569 576 c.groups = json.dumps(groups)
570 577 c.active = 'groups'
571 578
572 579 return self._get_template_context(c)
573 580
574 581 @LoginRequired()
575 582 @HasPermissionAllDecorator('hg.admin')
576 583 @CSRFRequired()
577 584 @view_config(
578 585 route_name='edit_user_groups_management_updates', request_method='POST')
579 586 def groups_management_updates(self):
580 587 _ = self.request.translate
581 588 c = self.load_default_context()
582 589
583 590 user_id = self.request.matchdict.get('user_id')
584 591 c.user = User.get_or_404(user_id)
585 592 self._redirect_for_default_user(c.user.username)
586 593
587 594 users_groups = set(self.request.POST.getall('users_group_id'))
588 595 users_groups_model = []
589 596
590 597 for ugid in users_groups:
591 598 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
592 599 user_group_model = UserGroupModel()
593 600 user_group_model.change_groups(c.user, users_groups_model)
594 601
595 602 Session().commit()
596 603 c.active = 'user_groups_management'
597 604 h.flash(_("Groups successfully changed"), category='success')
598 605
599 606 return HTTPFound(h.route_path(
600 607 'edit_user_groups_management', user_id=user_id))
601 608
602 609 @LoginRequired()
603 610 @HasPermissionAllDecorator('hg.admin')
604 611 @view_config(
605 612 route_name='edit_user_audit_logs', request_method='GET',
606 613 renderer='rhodecode:templates/admin/users/user_edit.mako')
607 614 def user_audit_logs(self):
608 615 _ = self.request.translate
609 616 c = self.load_default_context()
610 617
611 618 user_id = self.request.matchdict.get('user_id')
612 619 c.user = User.get_or_404(user_id)
613 620 self._redirect_for_default_user(c.user.username)
614 621 c.active = 'audit'
615 622
616 623 p = safe_int(self.request.GET.get('page', 1), 1)
617 624
618 625 filter_term = self.request.GET.get('filter')
619 626 user_log = UserModel().get_user_log(c.user, filter_term)
620 627
621 628 def url_generator(**kw):
622 629 if filter_term:
623 630 kw['filter'] = filter_term
624 631 return self.request.current_route_path(_query=kw)
625 632
626 633 c.audit_logs = h.Page(
627 634 user_log, page=p, items_per_page=10, url=url_generator)
628 635 c.filter_term = filter_term
629 636 return self._get_template_context(c)
630 637
@@ -1,534 +1,535 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25 import traceback
26 26 from collections import OrderedDict
27 27
28 28 from paste.registry import RegistryManager
29 29 from paste.gzipper import make_gzip_middleware
30 30 from pylons.wsgiapp import PylonsApp
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.wsgi import wsgiapp
35 35 from pyramid.httpexceptions import (
36 36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
37 37 from pyramid.events import ApplicationCreated
38 38 from pyramid.renderers import render_to_response
39 39 from routes.middleware import RoutesMiddleware
40 40 import rhodecode
41 41
42 42 from rhodecode.model import meta
43 43 from rhodecode.config import patches
44 44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 45 from rhodecode.config.environment import (
46 46 load_environment, load_pyramid_environment)
47 47
48 48 from rhodecode.lib.vcs import VCSCommunicationError
49 49 from rhodecode.lib.exceptions import VCSServerUnavailable
50 50 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
51 51 from rhodecode.lib.middleware.error_handling import (
52 52 PylonsErrorHandlingMiddleware)
53 53 from rhodecode.lib.middleware.https_fixup import HttpsFixup
54 54 from rhodecode.lib.middleware.vcs import VCSMiddleware
55 55 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
56 56 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
57 57 from rhodecode.subscribers import (
58 58 scan_repositories_if_enabled, write_js_routes_if_enabled,
59 59 write_metadata_if_needed)
60 60
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 # this is used to avoid avoid the route lookup overhead in routesmiddleware
66 66 # for certain routes which won't go to pylons to - eg. static files, debugger
67 67 # it is only needed for the pylons migration and can be removed once complete
68 68 class SkippableRoutesMiddleware(RoutesMiddleware):
69 69 """ Routes middleware that allows you to skip prefixes """
70 70
71 71 def __init__(self, *args, **kw):
72 72 self.skip_prefixes = kw.pop('skip_prefixes', [])
73 73 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
74 74
75 75 def __call__(self, environ, start_response):
76 76 for prefix in self.skip_prefixes:
77 77 if environ['PATH_INFO'].startswith(prefix):
78 78 # added to avoid the case when a missing /_static route falls
79 79 # through to pylons and causes an exception as pylons is
80 80 # expecting wsgiorg.routingargs to be set in the environ
81 81 # by RoutesMiddleware.
82 82 if 'wsgiorg.routing_args' not in environ:
83 83 environ['wsgiorg.routing_args'] = (None, {})
84 84 return self.app(environ, start_response)
85 85
86 86 return super(SkippableRoutesMiddleware, self).__call__(
87 87 environ, start_response)
88 88
89 89
90 90 def make_app(global_conf, static_files=True, **app_conf):
91 91 """Create a Pylons WSGI application and return it
92 92
93 93 ``global_conf``
94 94 The inherited configuration for this application. Normally from
95 95 the [DEFAULT] section of the Paste ini file.
96 96
97 97 ``app_conf``
98 98 The application's local configuration. Normally specified in
99 99 the [app:<name>] section of the Paste ini file (where <name>
100 100 defaults to main).
101 101
102 102 """
103 103 # Apply compatibility patches
104 104 patches.kombu_1_5_1_python_2_7_11()
105 105 patches.inspect_getargspec()
106 106
107 107 # Configure the Pylons environment
108 108 config = load_environment(global_conf, app_conf)
109 109
110 110 # The Pylons WSGI app
111 111 app = PylonsApp(config=config)
112 112
113 113 # Establish the Registry for this application
114 114 app = RegistryManager(app)
115 115
116 116 app.config = config
117 117
118 118 return app
119 119
120 120
121 121 def make_pyramid_app(global_config, **settings):
122 122 """
123 123 Constructs the WSGI application based on Pyramid and wraps the Pylons based
124 124 application.
125 125
126 126 Specials:
127 127
128 128 * We migrate from Pylons to Pyramid. While doing this, we keep both
129 129 frameworks functional. This involves moving some WSGI middlewares around
130 130 and providing access to some data internals, so that the old code is
131 131 still functional.
132 132
133 133 * The application can also be integrated like a plugin via the call to
134 134 `includeme`. This is accompanied with the other utility functions which
135 135 are called. Changing this should be done with great care to not break
136 136 cases when these fragments are assembled from another place.
137 137
138 138 """
139 139 # The edition string should be available in pylons too, so we add it here
140 140 # before copying the settings.
141 141 settings.setdefault('rhodecode.edition', 'Community Edition')
142 142
143 143 # As long as our Pylons application does expect "unprepared" settings, make
144 144 # sure that we keep an unmodified copy. This avoids unintentional change of
145 145 # behavior in the old application.
146 146 settings_pylons = settings.copy()
147 147
148 148 sanitize_settings_and_apply_defaults(settings)
149 149 config = Configurator(settings=settings)
150 150 add_pylons_compat_data(config.registry, global_config, settings_pylons)
151 151
152 152 load_pyramid_environment(global_config, settings)
153 153
154 154 includeme_first(config)
155 155 includeme(config)
156 156
157 157 pyramid_app = config.make_wsgi_app()
158 158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
159 159 pyramid_app.config = config
160 160
161 161 # creating the app uses a connection - return it after we are done
162 162 meta.Session.remove()
163 163
164 164 return pyramid_app
165 165
166 166
167 167 def make_not_found_view(config):
168 168 """
169 169 This creates the view which should be registered as not-found-view to
170 170 pyramid. Basically it contains of the old pylons app, converted to a view.
171 171 Additionally it is wrapped by some other middlewares.
172 172 """
173 173 settings = config.registry.settings
174 174 vcs_server_enabled = settings['vcs.server.enable']
175 175
176 176 # Make pylons app from unprepared settings.
177 177 pylons_app = make_app(
178 178 config.registry._pylons_compat_global_config,
179 179 **config.registry._pylons_compat_settings)
180 180 config.registry._pylons_compat_config = pylons_app.config
181 181
182 182 # Appenlight monitoring.
183 183 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
184 184 pylons_app, settings)
185 185
186 186 # The pylons app is executed inside of the pyramid 404 exception handler.
187 187 # Exceptions which are raised inside of it are not handled by pyramid
188 188 # again. Therefore we add a middleware that invokes the error handler in
189 189 # case of an exception or error response. This way we return proper error
190 190 # HTML pages in case of an error.
191 191 reraise = (settings.get('debugtoolbar.enabled', False) or
192 192 rhodecode.disable_error_handler)
193 193 pylons_app = PylonsErrorHandlingMiddleware(
194 194 pylons_app, error_handler, reraise)
195 195
196 196 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
197 197 # view to handle the request. Therefore it is wrapped around the pylons
198 198 # app. It has to be outside of the error handling otherwise error responses
199 199 # from the vcsserver are converted to HTML error pages. This confuses the
200 200 # command line tools and the user won't get a meaningful error message.
201 201 if vcs_server_enabled:
202 202 pylons_app = VCSMiddleware(
203 203 pylons_app, settings, appenlight_client, registry=config.registry)
204 204
205 205 # Convert WSGI app to pyramid view and return it.
206 206 return wsgiapp(pylons_app)
207 207
208 208
209 209 def add_pylons_compat_data(registry, global_config, settings):
210 210 """
211 211 Attach data to the registry to support the Pylons integration.
212 212 """
213 213 registry._pylons_compat_global_config = global_config
214 214 registry._pylons_compat_settings = settings
215 215
216 216
217 217 def error_handler(exception, request):
218 218 import rhodecode
219 219 from rhodecode.lib import helpers
220 220
221 221 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
222 222
223 223 base_response = HTTPInternalServerError()
224 224 # prefer original exception for the response since it may have headers set
225 225 if isinstance(exception, HTTPException):
226 226 base_response = exception
227 227 elif isinstance(exception, VCSCommunicationError):
228 228 base_response = VCSServerUnavailable()
229 229
230 230 def is_http_error(response):
231 231 # error which should have traceback
232 232 return response.status_code > 499
233 233
234 234 if is_http_error(base_response):
235 235 log.exception(
236 236 'error occurred handling this request for path: %s', request.path)
237 237
238 238 c = AttributeDict()
239 239 c.error_message = base_response.status
240 240 c.error_explanation = base_response.explanation or str(base_response)
241 241 c.visual = AttributeDict()
242 242
243 243 c.visual.rhodecode_support_url = (
244 244 request.registry.settings.get('rhodecode_support_url') or
245 245 request.route_url('rhodecode_support')
246 246 )
247 247 c.redirect_time = 0
248 248 c.rhodecode_name = rhodecode_title
249 249 if not c.rhodecode_name:
250 250 c.rhodecode_name = 'Rhodecode'
251 251
252 252 c.causes = []
253 253 if hasattr(base_response, 'causes'):
254 254 c.causes = base_response.causes
255 255 c.messages = helpers.flash.pop_messages(request=request)
256 256 c.traceback = traceback.format_exc()
257 257 response = render_to_response(
258 258 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
259 259 response=base_response)
260 260
261 261 return response
262 262
263 263
264 264 def includeme(config):
265 265 settings = config.registry.settings
266 266
267 267 # plugin information
268 268 config.registry.rhodecode_plugins = OrderedDict()
269 269
270 270 config.add_directive(
271 271 'register_rhodecode_plugin', register_rhodecode_plugin)
272 272
273 273 if asbool(settings.get('appenlight', 'false')):
274 274 config.include('appenlight_client.ext.pyramid_tween')
275 275
276 276 if 'mako.default_filters' not in settings:
277 277 # set custom default filters if we don't have it defined
278 278 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
279 279 settings['mako.default_filters'] = 'h_filter'
280 280
281 281 # Includes which are required. The application would fail without them.
282 282 config.include('pyramid_mako')
283 283 config.include('pyramid_beaker')
284 284
285 285 config.include('rhodecode.authentication')
286 286 config.include('rhodecode.integrations')
287 287
288 288 # apps
289 289 config.include('rhodecode.apps._base')
290 290 config.include('rhodecode.apps.ops')
291 291
292 292 config.include('rhodecode.apps.admin')
293 293 config.include('rhodecode.apps.channelstream')
294 294 config.include('rhodecode.apps.login')
295 295 config.include('rhodecode.apps.home')
296 296 config.include('rhodecode.apps.journal')
297 297 config.include('rhodecode.apps.repository')
298 298 config.include('rhodecode.apps.repo_group')
299 299 config.include('rhodecode.apps.search')
300 300 config.include('rhodecode.apps.user_profile')
301 301 config.include('rhodecode.apps.my_account')
302 302 config.include('rhodecode.apps.svn_support')
303 config.include('rhodecode.apps.ssh_support')
303 304 config.include('rhodecode.apps.gist')
304 305
305 306 config.include('rhodecode.apps.debug_style')
306 307 config.include('rhodecode.tweens')
307 308 config.include('rhodecode.api')
308 309
309 310 config.add_route(
310 311 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
311 312
312 313 config.add_translation_dirs('rhodecode:i18n/')
313 314 settings['default_locale_name'] = settings.get('lang', 'en')
314 315
315 316 # Add subscribers.
316 317 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
317 318 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
318 319 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
319 320
320 321 config.add_request_method(
321 322 'rhodecode.lib.partial_renderer.get_partial_renderer',
322 323 'get_partial_renderer')
323 324
324 325 # events
325 326 # TODO(marcink): this should be done when pyramid migration is finished
326 327 # config.add_subscriber(
327 328 # 'rhodecode.integrations.integrations_event_handler',
328 329 # 'rhodecode.events.RhodecodeEvent')
329 330
330 331 # Set the authorization policy.
331 332 authz_policy = ACLAuthorizationPolicy()
332 333 config.set_authorization_policy(authz_policy)
333 334
334 335 # Set the default renderer for HTML templates to mako.
335 336 config.add_mako_renderer('.html')
336 337
337 338 config.add_renderer(
338 339 name='json_ext',
339 340 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
340 341
341 342 # include RhodeCode plugins
342 343 includes = aslist(settings.get('rhodecode.includes', []))
343 344 for inc in includes:
344 345 config.include(inc)
345 346
346 347 # This is the glue which allows us to migrate in chunks. By registering the
347 348 # pylons based application as the "Not Found" view in Pyramid, we will
348 349 # fallback to the old application each time the new one does not yet know
349 350 # how to handle a request.
350 351 config.add_notfound_view(make_not_found_view(config))
351 352
352 353 if not settings.get('debugtoolbar.enabled', False):
353 354 # disabled debugtoolbar handle all exceptions via the error_handlers
354 355 config.add_view(error_handler, context=Exception)
355 356
356 357 config.add_view(error_handler, context=HTTPError)
357 358
358 359
359 360 def includeme_first(config):
360 361 # redirect automatic browser favicon.ico requests to correct place
361 362 def favicon_redirect(context, request):
362 363 return HTTPFound(
363 364 request.static_path('rhodecode:public/images/favicon.ico'))
364 365
365 366 config.add_view(favicon_redirect, route_name='favicon')
366 367 config.add_route('favicon', '/favicon.ico')
367 368
368 369 def robots_redirect(context, request):
369 370 return HTTPFound(
370 371 request.static_path('rhodecode:public/robots.txt'))
371 372
372 373 config.add_view(robots_redirect, route_name='robots')
373 374 config.add_route('robots', '/robots.txt')
374 375
375 376 config.add_static_view(
376 377 '_static/deform', 'deform:static')
377 378 config.add_static_view(
378 379 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
379 380
380 381
381 382 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
382 383 """
383 384 Apply outer WSGI middlewares around the application.
384 385
385 386 Part of this has been moved up from the Pylons layer, so that the
386 387 data is also available if old Pylons code is hit through an already ported
387 388 view.
388 389 """
389 390 settings = config.registry.settings
390 391
391 392 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
392 393 pyramid_app = HttpsFixup(pyramid_app, settings)
393 394
394 395 # Add RoutesMiddleware to support the pylons compatibility tween during
395 396 # migration to pyramid.
396 397
397 398 # TODO(marcink): remove after migration to pyramid
398 399 if hasattr(config.registry, '_pylons_compat_config'):
399 400 routes_map = config.registry._pylons_compat_config['routes.map']
400 401 pyramid_app = SkippableRoutesMiddleware(
401 402 pyramid_app, routes_map,
402 403 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
403 404
404 405 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
405 406
406 407 if settings['gzip_responses']:
407 408 pyramid_app = make_gzip_middleware(
408 409 pyramid_app, settings, compress_level=1)
409 410
410 411 # this should be the outer most middleware in the wsgi stack since
411 412 # middleware like Routes make database calls
412 413 def pyramid_app_with_cleanup(environ, start_response):
413 414 try:
414 415 return pyramid_app(environ, start_response)
415 416 finally:
416 417 # Dispose current database session and rollback uncommitted
417 418 # transactions.
418 419 meta.Session.remove()
419 420
420 421 # In a single threaded mode server, on non sqlite db we should have
421 422 # '0 Current Checked out connections' at the end of a request,
422 423 # if not, then something, somewhere is leaving a connection open
423 424 pool = meta.Base.metadata.bind.engine.pool
424 425 log.debug('sa pool status: %s', pool.status())
425 426
426 427 return pyramid_app_with_cleanup
427 428
428 429
429 430 def sanitize_settings_and_apply_defaults(settings):
430 431 """
431 432 Applies settings defaults and does all type conversion.
432 433
433 434 We would move all settings parsing and preparation into this place, so that
434 435 we have only one place left which deals with this part. The remaining parts
435 436 of the application would start to rely fully on well prepared settings.
436 437
437 438 This piece would later be split up per topic to avoid a big fat monster
438 439 function.
439 440 """
440 441
441 442 # Pyramid's mako renderer has to search in the templates folder so that the
442 443 # old templates still work. Ported and new templates are expected to use
443 444 # real asset specifications for the includes.
444 445 mako_directories = settings.setdefault('mako.directories', [
445 446 # Base templates of the original Pylons application
446 447 'rhodecode:templates',
447 448 ])
448 449 log.debug(
449 450 "Using the following Mako template directories: %s",
450 451 mako_directories)
451 452
452 453 # Default includes, possible to change as a user
453 454 pyramid_includes = settings.setdefault('pyramid.includes', [
454 455 'rhodecode.lib.middleware.request_wrapper',
455 456 ])
456 457 log.debug(
457 458 "Using the following pyramid.includes: %s",
458 459 pyramid_includes)
459 460
460 461 # TODO: johbo: Re-think this, usually the call to config.include
461 462 # should allow to pass in a prefix.
462 463 settings.setdefault('rhodecode.api.url', '/_admin/api')
463 464
464 465 # Sanitize generic settings.
465 466 _list_setting(settings, 'default_encoding', 'UTF-8')
466 467 _bool_setting(settings, 'is_test', 'false')
467 468 _bool_setting(settings, 'gzip_responses', 'false')
468 469
469 470 # Call split out functions that sanitize settings for each topic.
470 471 _sanitize_appenlight_settings(settings)
471 472 _sanitize_vcs_settings(settings)
472 473
473 474 return settings
474 475
475 476
476 477 def _sanitize_appenlight_settings(settings):
477 478 _bool_setting(settings, 'appenlight', 'false')
478 479
479 480
480 481 def _sanitize_vcs_settings(settings):
481 482 """
482 483 Applies settings defaults and does type conversion for all VCS related
483 484 settings.
484 485 """
485 486 _string_setting(settings, 'vcs.svn.compatible_version', '')
486 487 _string_setting(settings, 'git_rev_filter', '--all')
487 488 _string_setting(settings, 'vcs.hooks.protocol', 'http')
488 489 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
489 490 _string_setting(settings, 'vcs.server', '')
490 491 _string_setting(settings, 'vcs.server.log_level', 'debug')
491 492 _string_setting(settings, 'vcs.server.protocol', 'http')
492 493 _bool_setting(settings, 'startup.import_repos', 'false')
493 494 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
494 495 _bool_setting(settings, 'vcs.server.enable', 'true')
495 496 _bool_setting(settings, 'vcs.start_server', 'false')
496 497 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
497 498 _int_setting(settings, 'vcs.connection_timeout', 3600)
498 499
499 500 # Support legacy values of vcs.scm_app_implementation. Legacy
500 501 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
501 502 # which is now mapped to 'http'.
502 503 scm_app_impl = settings['vcs.scm_app_implementation']
503 504 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
504 505 settings['vcs.scm_app_implementation'] = 'http'
505 506
506 507
507 508 def _int_setting(settings, name, default):
508 509 settings[name] = int(settings.get(name, default))
509 510
510 511
511 512 def _bool_setting(settings, name, default):
512 513 input = settings.get(name, default)
513 514 if isinstance(input, unicode):
514 515 input = input.encode('utf8')
515 516 settings[name] = asbool(input)
516 517
517 518
518 519 def _list_setting(settings, name, default):
519 520 raw_value = settings.get(name, default)
520 521
521 522 old_separator = ','
522 523 if old_separator in raw_value:
523 524 # If we get a comma separated list, pass it to our own function.
524 525 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
525 526 else:
526 527 # Otherwise we assume it uses pyramids space/newline separation.
527 528 settings[name] = aslist(raw_value)
528 529
529 530
530 531 def _string_setting(settings, name, default, lower=True):
531 532 value = settings.get(name, default)
532 533 if lower:
533 534 value = value.lower()
534 535 settings[name] = value
General Comments 0
You need to be logged in to leave comments. Login now