##// END OF EJS Templates
caches: rewrite of auth/permission caches to dogpile.
marcink -
r2845:3d5ae486 default
parent child Browse files
Show More
@@ -0,0 +1,66 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2015-2018 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 from dogpile.cache import register_backend
22 from dogpile.cache import make_region
23
24 register_backend(
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
26 "LRUMemoryBackend")
27
28 register_backend(
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
30 "FileNamespaceBackend")
31
32 register_backend(
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
34 "RedisPickleBackend")
35
36
37 from . import region_meta
38 from .utils import get_default_cache_settings, key_generator, get_or_create_region
39
40
41 def configure_dogpile_cache(settings):
42 cache_dir = settings.get('cache_dir')
43 if cache_dir:
44 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
45
46 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
47
48 # inspect available namespaces
49 avail_regions = set()
50 for key in rc_cache_data.keys():
51 namespace_name = key.split('.', 1)[0]
52 avail_regions.add(namespace_name)
53
54 # register them into namespace
55 for region_name in avail_regions:
56 new_region = make_region(
57 name=region_name,
58 function_key_generator=key_generator
59 )
60
61 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
62 region_meta.dogpile_cache_regions[region_name] = new_region
63
64
65 def includeme(config):
66 configure_dogpile_cache(config.registry.settings)
@@ -0,0 +1,109 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2015-2018 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 from dogpile.cache.backends import memory as memory_backend
22 from dogpile.cache.backends import file as file_backend
23 from dogpile.cache.backends import redis as redis_backend
24 from dogpile.cache.backends.file import NO_VALUE, compat
25
26 from rhodecode.lib.memory_lru_debug import LRUDict
27
28 _default_max_size = 1024
29
30
31 class LRUMemoryBackend(memory_backend.MemoryBackend):
32
33 def __init__(self, arguments):
34 max_size = arguments.pop('max_size', _default_max_size)
35 arguments['cache_dict'] = LRUDict(max_size)
36 super(LRUMemoryBackend, self).__init__(arguments)
37
38
39 class Serializer(object):
40 def _dumps(self, value):
41 return compat.pickle.dumps(value)
42
43 def _loads(self, value):
44 return compat.pickle.loads(value)
45
46
47 class FileNamespaceBackend(Serializer, file_backend.DBMBackend):
48
49 def __init__(self, arguments):
50 super(FileNamespaceBackend, self).__init__(arguments)
51
52 def list_keys(self):
53 with self._dbm_file(True) as dbm:
54 return dbm.keys()
55
56 def get_store(self):
57 return self.filename
58
59 def get(self, key):
60 with self._dbm_file(False) as dbm:
61 if hasattr(dbm, 'get'):
62 value = dbm.get(key, NO_VALUE)
63 else:
64 # gdbm objects lack a .get method
65 try:
66 value = dbm[key]
67 except KeyError:
68 value = NO_VALUE
69 if value is not NO_VALUE:
70 value = self._loads(value)
71 return value
72
73 def set(self, key, value):
74 with self._dbm_file(True) as dbm:
75 dbm[key] = self._dumps(value)
76
77 def set_multi(self, mapping):
78 with self._dbm_file(True) as dbm:
79 for key, value in mapping.items():
80 dbm[key] = self._dumps(value)
81
82
83 class RedisPickleBackend(Serializer, redis_backend.RedisBackend):
84 def list_keys(self):
85 return self.client.keys()
86
87 def get_store(self):
88 return self.client.connection_pool
89
90 def set(self, key, value):
91 if self.redis_expiration_time:
92 self.client.setex(key, self.redis_expiration_time,
93 self._dumps(value))
94 else:
95 self.client.set(key, self._dumps(value))
96
97 def set_multi(self, mapping):
98 mapping = dict(
99 (k, self._dumps(v))
100 for k, v in mapping.items()
101 )
102
103 if not self.redis_expiration_time:
104 self.client.mset(mapping)
105 else:
106 pipe = self.client.pipeline()
107 for key, value in mapping.items():
108 pipe.setex(key, self.redis_expiration_time, value)
109 pipe.execute()
@@ -0,0 +1,28 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2015-2018 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import os
21 import tempfile
22
23 dogpile_config_defaults = {
24 'cache_dir': os.path.join(tempfile.gettempdir(), 'rc_cache')
25 }
26
27 # GLOBAL TO STORE ALL REGISTERED REGIONS
28 dogpile_cache_regions = {}
@@ -0,0 +1,99 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2015-2018 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import os
21 import logging
22 from dogpile.cache import make_region
23
24 from rhodecode.lib.utils import safe_str, sha1
25 from . import region_meta
26
27 log = logging.getLogger(__name__)
28
29
30 def get_default_cache_settings(settings, prefixes=None):
31 prefixes = prefixes or []
32 cache_settings = {}
33 for key in settings.keys():
34 for prefix in prefixes:
35 if key.startswith(prefix):
36 name = key.split(prefix)[1].strip()
37 val = settings[key]
38 if isinstance(val, basestring):
39 val = val.strip()
40 cache_settings[name] = val
41 return cache_settings
42
43
44 def compute_key_from_params(*args):
45 """
46 Helper to compute key from given params to be used in cache manager
47 """
48 return sha1("_".join(map(safe_str, args)))
49
50
51 def key_generator(namespace, fn):
52 fname = fn.__name__
53
54 def generate_key(*args):
55 namespace_pref = namespace or 'default'
56 arg_key = compute_key_from_params(*args)
57 final_key = "{}:{}_{}".format(namespace_pref, fname, arg_key)
58
59 return final_key
60
61 return generate_key
62
63
64 def get_or_create_region(region_name, region_namespace=None):
65 from rhodecode.lib.rc_cache.backends import FileNamespaceBackend
66 region_obj = region_meta.dogpile_cache_regions.get(region_name)
67 if not region_obj:
68 raise EnvironmentError(
69 'Region `{}` not in configured: {}.'.format(
70 region_name, region_meta.dogpile_cache_regions.keys()))
71
72 region_uid_name = '{}:{}'.format(region_name, region_namespace)
73 if isinstance(region_obj.actual_backend, FileNamespaceBackend):
74 region_exist = region_meta.dogpile_cache_regions.get(region_namespace)
75 if region_exist:
76 log.debug('Using already configured region: %s', region_namespace)
77 return region_exist
78 cache_dir = region_meta.dogpile_config_defaults['cache_dir']
79 expiration_time = region_obj.expiration_time
80
81 if not os.path.isdir(cache_dir):
82 os.makedirs(cache_dir)
83 new_region = make_region(
84 name=region_uid_name, function_key_generator=key_generator
85 )
86 namespace_filename = os.path.join(
87 cache_dir, "{}.cache.dbm".format(region_namespace))
88 # special type that allows 1db per namespace
89 new_region.configure(
90 backend='dogpile.cache.rc.file_namespace',
91 expiration_time=expiration_time,
92 arguments={"filename": namespace_filename}
93 )
94
95 # create and save in region caches
96 log.debug('configuring new region: %s',region_uid_name)
97 region_obj = region_meta.dogpile_cache_regions[region_namespace] = new_region
98
99 return region_obj
@@ -0,0 +1,29 b''
1 <%namespace name="base" file="/base/base.mako"/>
2
3 <div class="panel panel-default">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Caches')}</h3>
6 </div>
7 <div class="panel-body">
8 <pre>
9 region: ${c.region.name}
10 backend: ${c.region.actual_backend.__class__}
11 store: ${c.region.actual_backend.get_store()}
12
13 % for k in c.user_keys:
14 - ${k}
15 % endfor
16 </pre>
17
18 ${h.secure_form(h.route_path('edit_user_caches_update', user_id=c.user.user_id), request=request)}
19 <div class="form">
20 <div class="fields">
21 ${h.submit('reset_cache_%s' % c.user.user_id, _('Invalidate user cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate user cache')+"');")}
22 </div>
23 </div>
24 ${h.end_form()}
25
26 </div>
27 </div>
28
29
@@ -1,720 +1,705 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 recommended 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 = gevent
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.lib.middleware.request_wrapper
115 115
116 116 pyramid.reload_templates = true
117 117
118 118 debugtoolbar.hosts = 0.0.0.0/0
119 119 debugtoolbar.exclude_prefixes =
120 120 /css
121 121 /fonts
122 122 /images
123 123 /js
124 124
125 125 ## RHODECODE PLUGINS ##
126 126 rhodecode.includes =
127 127 rhodecode.api
128 128
129 129
130 130 # api prefix url
131 131 rhodecode.api.url = /_admin/api
132 132
133 133
134 134 ## END RHODECODE PLUGINS ##
135 135
136 136 ## encryption key used to encrypt social plugin tokens,
137 137 ## remote_urls with credentials etc, if not set it defaults to
138 138 ## `beaker.session.secret`
139 139 #rhodecode.encrypted_values.secret =
140 140
141 141 ## decryption strict mode (enabled by default). It controls if decryption raises
142 142 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
143 143 #rhodecode.encrypted_values.strict = false
144 144
145 145 ## return gzipped responses from Rhodecode (static files/application)
146 146 gzip_responses = false
147 147
148 148 ## autogenerate javascript routes file on startup
149 149 generate_js_files = false
150 150
151 151 ## Optional Languages
152 152 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
153 153 lang = en
154 154
155 155 ## perform a full repository scan on each server start, this should be
156 156 ## set to false after first startup, to allow faster server restarts.
157 157 startup.import_repos = false
158 158
159 159 ## Uncomment and set this path to use archive download cache.
160 160 ## Once enabled, generated archives will be cached at this location
161 161 ## and served from the cache during subsequent requests for the same archive of
162 162 ## the repository.
163 163 #archive_cache_dir = /tmp/tarballcache
164 164
165 165 ## URL at which the application is running. This is used for bootstraping
166 166 ## requests in context when no web request is available. Used in ishell, or
167 167 ## SSH calls. Set this for events to receive proper url for SSH calls.
168 168 app.base_url = http://rhodecode.local
169 169
170 170 ## change this to unique ID for security
171 171 app_instance_uuid = rc-production
172 172
173 173 ## cut off limit for large diffs (size in bytes). If overall diff size on
174 174 ## commit, or pull request exceeds this limit this diff will be displayed
175 175 ## partially. E.g 512000 == 512Kb
176 176 cut_off_limit_diff = 512000
177 177
178 178 ## cut off limit for large files inside diffs (size in bytes). Each individual
179 179 ## file inside diff which exceeds this limit will be displayed partially.
180 180 ## E.g 128000 == 128Kb
181 181 cut_off_limit_file = 128000
182 182
183 183 ## use cache version of scm repo everywhere
184 184 vcs_full_cache = true
185 185
186 186 ## force https in RhodeCode, fixes https redirects, assumes it's always https
187 187 ## Normally this is controlled by proper http flags sent from http server
188 188 force_https = false
189 189
190 190 ## use Strict-Transport-Security headers
191 191 use_htsts = false
192 192
193 193 ## git rev filter option, --all is the default filter, if you need to
194 194 ## hide all refs in changelog switch this to --branches --tags
195 195 git_rev_filter = --branches --tags
196 196
197 197 # Set to true if your repos are exposed using the dumb protocol
198 198 git_update_server_info = false
199 199
200 200 ## RSS/ATOM feed options
201 201 rss_cut_off_limit = 256000
202 202 rss_items_per_page = 10
203 203 rss_include_diff = false
204 204
205 205 ## gist URL alias, used to create nicer urls for gist. This should be an
206 206 ## url that does rewrites to _admin/gists/{gistid}.
207 207 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
208 208 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
209 209 gist_alias_url =
210 210
211 211 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
212 212 ## used for access.
213 213 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
214 214 ## came from the the logged in user who own this authentication token.
215 215 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
216 216 ## authentication token. Such view would be only accessible when used together
217 217 ## with this authentication token
218 218 ##
219 219 ## list of all views can be found under `/_admin/permissions/auth_token_access`
220 220 ## The list should be "," separated and on a single line.
221 221 ##
222 222 ## Most common views to enable:
223 223 # RepoCommitsView:repo_commit_download
224 224 # RepoCommitsView:repo_commit_patch
225 225 # RepoCommitsView:repo_commit_raw
226 226 # RepoCommitsView:repo_commit_raw@TOKEN
227 227 # RepoFilesView:repo_files_diff
228 228 # RepoFilesView:repo_archivefile
229 229 # RepoFilesView:repo_file_raw
230 230 # GistView:*
231 231 api_access_controllers_whitelist =
232 232
233 233 ## default encoding used to convert from and to unicode
234 234 ## can be also a comma separated list of encoding in case of mixed encodings
235 235 default_encoding = UTF-8
236 236
237 237 ## instance-id prefix
238 238 ## a prefix key for this instance used for cache invalidation when running
239 239 ## multiple instances of rhodecode, make sure it's globally unique for
240 240 ## all running rhodecode instances. Leave empty if you don't use it
241 241 instance_id =
242 242
243 243 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
244 244 ## of an authentication plugin also if it is disabled by it's settings.
245 245 ## This could be useful if you are unable to log in to the system due to broken
246 246 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
247 247 ## module to log in again and fix the settings.
248 248 ##
249 249 ## Available builtin plugin IDs (hash is part of the ID):
250 250 ## egg:rhodecode-enterprise-ce#rhodecode
251 251 ## egg:rhodecode-enterprise-ce#pam
252 252 ## egg:rhodecode-enterprise-ce#ldap
253 253 ## egg:rhodecode-enterprise-ce#jasig_cas
254 254 ## egg:rhodecode-enterprise-ce#headers
255 255 ## egg:rhodecode-enterprise-ce#crowd
256 256 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
257 257
258 258 ## alternative return HTTP header for failed authentication. Default HTTP
259 259 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
260 260 ## handling that causing a series of failed authentication calls.
261 261 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
262 262 ## This will be served instead of default 401 on bad authnetication
263 263 auth_ret_code =
264 264
265 265 ## use special detection method when serving auth_ret_code, instead of serving
266 266 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
267 267 ## and then serve auth_ret_code to clients
268 268 auth_ret_code_detection = false
269 269
270 270 ## locking return code. When repository is locked return this HTTP code. 2XX
271 271 ## codes don't break the transactions while 4XX codes do
272 272 lock_ret_code = 423
273 273
274 274 ## allows to change the repository location in settings page
275 275 allow_repo_location_change = true
276 276
277 277 ## allows to setup custom hooks in settings page
278 278 allow_custom_hooks_settings = true
279 279
280 280 ## generated license token, goto license page in RhodeCode settings to obtain
281 281 ## new token
282 282 license_token =
283 283
284 284 ## supervisor connection uri, for managing supervisor and logs.
285 285 supervisor.uri =
286 286 ## supervisord group name/id we only want this RC instance to handle
287 287 supervisor.group_id = dev
288 288
289 289 ## Display extended labs settings
290 290 labs_settings_active = true
291 291
292 292 ####################################
293 293 ### CELERY CONFIG ####
294 294 ####################################
295 295 ## run: /path/to/celery worker \
296 296 ## -E --beat --app rhodecode.lib.celerylib.loader \
297 297 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
298 298 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
299 299
300 300 use_celery = false
301 301
302 302 ## connection url to the message broker (default rabbitmq)
303 303 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
304 304
305 305 ## maximum tasks to execute before worker restart
306 306 celery.max_tasks_per_child = 100
307 307
308 308 ## tasks will never be sent to the queue, but executed locally instead.
309 309 celery.task_always_eager = false
310 310
311 311 ####################################
312 312 ### BEAKER CACHE ####
313 313 ####################################
314 314 # default cache dir for templates. Putting this into a ramdisk
315 315 ## can boost performance, eg. %(here)s/data_ramdisk
316 316 cache_dir = %(here)s/data
317 317
318 318 ## locking and default file storage for Beaker. Putting this into a ramdisk
319 319 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
320 320 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
321 321 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
322 322
323 beaker.cache.regions = short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
324
325 # used for caching user permissions
326 beaker.cache.short_term.type = file
327 beaker.cache.short_term.expire = 0
328 beaker.cache.short_term.key_length = 256
323 beaker.cache.regions = long_term, sql_cache_short, repo_cache_long
329 324
330 325 beaker.cache.long_term.type = memory
331 326 beaker.cache.long_term.expire = 36000
332 327 beaker.cache.long_term.key_length = 256
333 328
334 329 beaker.cache.sql_cache_short.type = memory
335 330 beaker.cache.sql_cache_short.expire = 10
336 331 beaker.cache.sql_cache_short.key_length = 256
337 332
338 ## default is memory cache, configure only if required
339 ## using multi-node or multi-worker setup
340 #beaker.cache.auth_plugins.type = ext:database
341 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
342 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
343 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
344 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
345 #beaker.cache.auth_plugins.sa.pool_size = 10
346 #beaker.cache.auth_plugins.sa.max_overflow = 0
347
348 333 beaker.cache.repo_cache_long.type = memorylru_base
349 334 beaker.cache.repo_cache_long.max_items = 4096
350 335 beaker.cache.repo_cache_long.expire = 2592000
351 336
352 337 ## default is memorylru_base cache, configure only if required
353 338 ## using multi-node or multi-worker setup
354 339 #beaker.cache.repo_cache_long.type = ext:memcached
355 340 #beaker.cache.repo_cache_long.url = localhost:11211
356 341 #beaker.cache.repo_cache_long.expire = 1209600
357 342 #beaker.cache.repo_cache_long.key_length = 256
358 343
359 344 ####################################
360 345 ### BEAKER SESSION ####
361 346 ####################################
362 347
363 348 ## .session.type is type of storage options for the session, current allowed
364 349 ## types are file, ext:memcached, ext:database, and memory (default).
365 350 beaker.session.type = file
366 351 beaker.session.data_dir = %(here)s/data/sessions/data
367 352
368 353 ## db based session, fast, and allows easy management over logged in users
369 354 #beaker.session.type = ext:database
370 355 #beaker.session.table_name = db_session
371 356 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
372 357 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
373 358 #beaker.session.sa.pool_recycle = 3600
374 359 #beaker.session.sa.echo = false
375 360
376 361 beaker.session.key = rhodecode
377 362 beaker.session.secret = develop-rc-uytcxaz
378 363 beaker.session.lock_dir = %(here)s/data/sessions/lock
379 364
380 365 ## Secure encrypted cookie. Requires AES and AES python libraries
381 366 ## you must disable beaker.session.secret to use this
382 367 #beaker.session.encrypt_key = key_for_encryption
383 368 #beaker.session.validate_key = validation_key
384 369
385 370 ## sets session as invalid(also logging out user) if it haven not been
386 371 ## accessed for given amount of time in seconds
387 372 beaker.session.timeout = 2592000
388 373 beaker.session.httponly = true
389 374 ## Path to use for the cookie. Set to prefix if you use prefix middleware
390 375 #beaker.session.cookie_path = /custom_prefix
391 376
392 377 ## uncomment for https secure cookie
393 378 beaker.session.secure = false
394 379
395 380 ## auto save the session to not to use .save()
396 381 beaker.session.auto = false
397 382
398 383 ## default cookie expiration time in seconds, set to `true` to set expire
399 384 ## at browser close
400 385 #beaker.session.cookie_expires = 3600
401 386
402 387 ###################################
403 388 ## SEARCH INDEXING CONFIGURATION ##
404 389 ###################################
405 390 ## Full text search indexer is available in rhodecode-tools under
406 391 ## `rhodecode-tools index` command
407 392
408 393 ## WHOOSH Backend, doesn't require additional services to run
409 394 ## it works good with few dozen repos
410 395 search.module = rhodecode.lib.index.whoosh
411 396 search.location = %(here)s/data/index
412 397
413 398 ########################################
414 399 ### CHANNELSTREAM CONFIG ####
415 400 ########################################
416 401 ## channelstream enables persistent connections and live notification
417 402 ## in the system. It's also used by the chat system
418 403 channelstream.enabled = false
419 404
420 405 ## server address for channelstream server on the backend
421 406 channelstream.server = 127.0.0.1:9800
422 407
423 408 ## location of the channelstream server from outside world
424 409 ## use ws:// for http or wss:// for https. This address needs to be handled
425 410 ## by external HTTP server such as Nginx or Apache
426 411 ## see nginx/apache configuration examples in our docs
427 412 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
428 413 channelstream.secret = secret
429 414 channelstream.history.location = %(here)s/channelstream_history
430 415
431 416 ## Internal application path that Javascript uses to connect into.
432 417 ## If you use proxy-prefix the prefix should be added before /_channelstream
433 418 channelstream.proxy_path = /_channelstream
434 419
435 420
436 421 ###################################
437 422 ## APPENLIGHT CONFIG ##
438 423 ###################################
439 424
440 425 ## Appenlight is tailored to work with RhodeCode, see
441 426 ## http://appenlight.com for details how to obtain an account
442 427
443 428 ## appenlight integration enabled
444 429 appenlight = false
445 430
446 431 appenlight.server_url = https://api.appenlight.com
447 432 appenlight.api_key = YOUR_API_KEY
448 433 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
449 434
450 435 # used for JS client
451 436 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
452 437
453 438 ## TWEAK AMOUNT OF INFO SENT HERE
454 439
455 440 ## enables 404 error logging (default False)
456 441 appenlight.report_404 = false
457 442
458 443 ## time in seconds after request is considered being slow (default 1)
459 444 appenlight.slow_request_time = 1
460 445
461 446 ## record slow requests in application
462 447 ## (needs to be enabled for slow datastore recording and time tracking)
463 448 appenlight.slow_requests = true
464 449
465 450 ## enable hooking to application loggers
466 451 appenlight.logging = true
467 452
468 453 ## minimum log level for log capture
469 454 appenlight.logging.level = WARNING
470 455
471 456 ## send logs only from erroneous/slow requests
472 457 ## (saves API quota for intensive logging)
473 458 appenlight.logging_on_error = false
474 459
475 460 ## list of additonal keywords that should be grabbed from environ object
476 461 ## can be string with comma separated list of words in lowercase
477 462 ## (by default client will always send following info:
478 463 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
479 464 ## start with HTTP* this list be extended with additional keywords here
480 465 appenlight.environ_keys_whitelist =
481 466
482 467 ## list of keywords that should be blanked from request object
483 468 ## can be string with comma separated list of words in lowercase
484 469 ## (by default client will always blank keys that contain following words
485 470 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
486 471 ## this list be extended with additional keywords set here
487 472 appenlight.request_keys_blacklist =
488 473
489 474 ## list of namespaces that should be ignores when gathering log entries
490 475 ## can be string with comma separated list of namespaces
491 476 ## (by default the client ignores own entries: appenlight_client.client)
492 477 appenlight.log_namespace_blacklist =
493 478
494 479
495 480 ################################################################################
496 481 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
497 482 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
498 483 ## execute malicious code after an exception is raised. ##
499 484 ################################################################################
500 485 #set debug = false
501 486
502 487
503 488 ##############
504 489 ## STYLING ##
505 490 ##############
506 491 debug_style = true
507 492
508 493 ###########################################
509 494 ### MAIN RHODECODE DATABASE CONFIG ###
510 495 ###########################################
511 496 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
512 497 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
513 498 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
514 499 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
515 500
516 501 # see sqlalchemy docs for other advanced settings
517 502
518 503 ## print the sql statements to output
519 504 sqlalchemy.db1.echo = false
520 505 ## recycle the connections after this amount of seconds
521 506 sqlalchemy.db1.pool_recycle = 3600
522 507 sqlalchemy.db1.convert_unicode = true
523 508
524 509 ## the number of connections to keep open inside the connection pool.
525 510 ## 0 indicates no limit
526 511 #sqlalchemy.db1.pool_size = 5
527 512
528 513 ## the number of connections to allow in connection pool "overflow", that is
529 514 ## connections that can be opened above and beyond the pool_size setting,
530 515 ## which defaults to five.
531 516 #sqlalchemy.db1.max_overflow = 10
532 517
533 518
534 519 ##################
535 520 ### VCS CONFIG ###
536 521 ##################
537 522 vcs.server.enable = true
538 523 vcs.server = localhost:9900
539 524
540 525 ## Web server connectivity protocol, responsible for web based VCS operatations
541 526 ## Available protocols are:
542 527 ## `http` - use http-rpc backend (default)
543 528 vcs.server.protocol = http
544 529
545 530 ## Push/Pull operations protocol, available options are:
546 531 ## `http` - use http-rpc backend (default)
547 532 ##
548 533 vcs.scm_app_implementation = http
549 534
550 535 ## Push/Pull operations hooks protocol, available options are:
551 536 ## `http` - use http-rpc backend (default)
552 537 vcs.hooks.protocol = http
553 538
554 539 ## Host on which this instance is listening for hooks. If vcsserver is in other location
555 540 ## this should be adjusted.
556 541 vcs.hooks.host = 127.0.0.1
557 542
558 543 vcs.server.log_level = debug
559 544 ## Start VCSServer with this instance as a subprocess, usefull for development
560 545 vcs.start_server = false
561 546
562 547 ## List of enabled VCS backends, available options are:
563 548 ## `hg` - mercurial
564 549 ## `git` - git
565 550 ## `svn` - subversion
566 551 vcs.backends = hg, git, svn
567 552
568 553 vcs.connection_timeout = 3600
569 554 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
570 555 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
571 556 #vcs.svn.compatible_version = pre-1.8-compatible
572 557
573 558
574 559 ############################################################
575 560 ### Subversion proxy support (mod_dav_svn) ###
576 561 ### Maps RhodeCode repo groups into SVN paths for Apache ###
577 562 ############################################################
578 563 ## Enable or disable the config file generation.
579 564 svn.proxy.generate_config = false
580 565 ## Generate config file with `SVNListParentPath` set to `On`.
581 566 svn.proxy.list_parent_path = true
582 567 ## Set location and file name of generated config file.
583 568 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
584 569 ## alternative mod_dav config template. This needs to be a mako template
585 570 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
586 571 ## Used as a prefix to the `Location` block in the generated config file.
587 572 ## In most cases it should be set to `/`.
588 573 svn.proxy.location_root = /
589 574 ## Command to reload the mod dav svn configuration on change.
590 575 ## Example: `/etc/init.d/apache2 reload`
591 576 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
592 577 ## If the timeout expires before the reload command finishes, the command will
593 578 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
594 579 #svn.proxy.reload_timeout = 10
595 580
596 581 ############################################################
597 582 ### SSH Support Settings ###
598 583 ############################################################
599 584
600 585 ## Defines if a custom authorized_keys file should be created and written on
601 586 ## any change user ssh keys. Setting this to false also disables posibility
602 587 ## of adding SSH keys by users from web interface. Super admins can still
603 588 ## manage SSH Keys.
604 589 ssh.generate_authorized_keyfile = false
605 590
606 591 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
607 592 # ssh.authorized_keys_ssh_opts =
608 593
609 594 ## Path to the authrozied_keys file where the generate entries are placed.
610 595 ## It is possible to have multiple key files specified in `sshd_config` e.g.
611 596 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
612 597 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
613 598
614 599 ## Command to execute the SSH wrapper. The binary is available in the
615 600 ## rhodecode installation directory.
616 601 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
617 602 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
618 603
619 604 ## Allow shell when executing the ssh-wrapper command
620 605 ssh.wrapper_cmd_allow_shell = false
621 606
622 607 ## Enables logging, and detailed output send back to the client during SSH
623 608 ## operations. Usefull for debugging, shouldn't be used in production.
624 609 ssh.enable_debug_logging = true
625 610
626 611 ## Paths to binary executable, by default they are the names, but we can
627 612 ## override them if we want to use a custom one
628 613 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
629 614 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
630 615 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
631 616
632 617
633 618 ## Dummy marker to add new entries after.
634 619 ## Add any custom entries below. Please don't remove.
635 620 custom.conf = 1
636 621
637 622
638 623 ################################
639 624 ### LOGGING CONFIGURATION ####
640 625 ################################
641 626 [loggers]
642 627 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper, celery
643 628
644 629 [handlers]
645 630 keys = console, console_sql
646 631
647 632 [formatters]
648 633 keys = generic, color_formatter, color_formatter_sql
649 634
650 635 #############
651 636 ## LOGGERS ##
652 637 #############
653 638 [logger_root]
654 639 level = NOTSET
655 640 handlers = console
656 641
657 642 [logger_sqlalchemy]
658 643 level = INFO
659 644 handlers = console_sql
660 645 qualname = sqlalchemy.engine
661 646 propagate = 0
662 647
663 648 [logger_beaker]
664 649 level = DEBUG
665 650 handlers =
666 651 qualname = beaker.container
667 652 propagate = 1
668 653
669 654 [logger_rhodecode]
670 655 level = DEBUG
671 656 handlers =
672 657 qualname = rhodecode
673 658 propagate = 1
674 659
675 660 [logger_ssh_wrapper]
676 661 level = DEBUG
677 662 handlers =
678 663 qualname = ssh_wrapper
679 664 propagate = 1
680 665
681 666 [logger_celery]
682 667 level = DEBUG
683 668 handlers =
684 669 qualname = celery
685 670
686 671
687 672 ##############
688 673 ## HANDLERS ##
689 674 ##############
690 675
691 676 [handler_console]
692 677 class = StreamHandler
693 678 args = (sys.stderr, )
694 679 level = DEBUG
695 680 formatter = color_formatter
696 681
697 682 [handler_console_sql]
698 683 class = StreamHandler
699 684 args = (sys.stderr, )
700 685 level = DEBUG
701 686 formatter = color_formatter_sql
702 687
703 688 ################
704 689 ## FORMATTERS ##
705 690 ################
706 691
707 692 [formatter_generic]
708 693 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
709 694 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
710 695 datefmt = %Y-%m-%d %H:%M:%S
711 696
712 697 [formatter_color_formatter]
713 698 class = rhodecode.lib.logging_formatter.ColorFormatter
714 699 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
715 700 datefmt = %Y-%m-%d %H:%M:%S
716 701
717 702 [formatter_color_formatter_sql]
718 703 class = rhodecode.lib.logging_formatter.ColorFormatterSql
719 704 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
720 705 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,689 +1,674 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 recommended 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 = gevent
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 ## URL at which the application is running. This is used for bootstraping
141 141 ## requests in context when no web request is available. Used in ishell, or
142 142 ## SSH calls. Set this for events to receive proper url for SSH calls.
143 143 app.base_url = http://rhodecode.local
144 144
145 145 ## change this to unique ID for security
146 146 app_instance_uuid = rc-production
147 147
148 148 ## cut off limit for large diffs (size in bytes). If overall diff size on
149 149 ## commit, or pull request exceeds this limit this diff will be displayed
150 150 ## partially. E.g 512000 == 512Kb
151 151 cut_off_limit_diff = 512000
152 152
153 153 ## cut off limit for large files inside diffs (size in bytes). Each individual
154 154 ## file inside diff which exceeds this limit will be displayed partially.
155 155 ## E.g 128000 == 128Kb
156 156 cut_off_limit_file = 128000
157 157
158 158 ## use cache version of scm repo everywhere
159 159 vcs_full_cache = true
160 160
161 161 ## force https in RhodeCode, fixes https redirects, assumes it's always https
162 162 ## Normally this is controlled by proper http flags sent from http server
163 163 force_https = false
164 164
165 165 ## use Strict-Transport-Security headers
166 166 use_htsts = false
167 167
168 168 ## git rev filter option, --all is the default filter, if you need to
169 169 ## hide all refs in changelog switch this to --branches --tags
170 170 git_rev_filter = --branches --tags
171 171
172 172 # Set to true if your repos are exposed using the dumb protocol
173 173 git_update_server_info = false
174 174
175 175 ## RSS/ATOM feed options
176 176 rss_cut_off_limit = 256000
177 177 rss_items_per_page = 10
178 178 rss_include_diff = false
179 179
180 180 ## gist URL alias, used to create nicer urls for gist. This should be an
181 181 ## url that does rewrites to _admin/gists/{gistid}.
182 182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
183 183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
184 184 gist_alias_url =
185 185
186 186 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
187 187 ## used for access.
188 188 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
189 189 ## came from the the logged in user who own this authentication token.
190 190 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
191 191 ## authentication token. Such view would be only accessible when used together
192 192 ## with this authentication token
193 193 ##
194 194 ## list of all views can be found under `/_admin/permissions/auth_token_access`
195 195 ## The list should be "," separated and on a single line.
196 196 ##
197 197 ## Most common views to enable:
198 198 # RepoCommitsView:repo_commit_download
199 199 # RepoCommitsView:repo_commit_patch
200 200 # RepoCommitsView:repo_commit_raw
201 201 # RepoCommitsView:repo_commit_raw@TOKEN
202 202 # RepoFilesView:repo_files_diff
203 203 # RepoFilesView:repo_archivefile
204 204 # RepoFilesView:repo_file_raw
205 205 # GistView:*
206 206 api_access_controllers_whitelist =
207 207
208 208 ## default encoding used to convert from and to unicode
209 209 ## can be also a comma separated list of encoding in case of mixed encodings
210 210 default_encoding = UTF-8
211 211
212 212 ## instance-id prefix
213 213 ## a prefix key for this instance used for cache invalidation when running
214 214 ## multiple instances of rhodecode, make sure it's globally unique for
215 215 ## all running rhodecode instances. Leave empty if you don't use it
216 216 instance_id =
217 217
218 218 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
219 219 ## of an authentication plugin also if it is disabled by it's settings.
220 220 ## This could be useful if you are unable to log in to the system due to broken
221 221 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
222 222 ## module to log in again and fix the settings.
223 223 ##
224 224 ## Available builtin plugin IDs (hash is part of the ID):
225 225 ## egg:rhodecode-enterprise-ce#rhodecode
226 226 ## egg:rhodecode-enterprise-ce#pam
227 227 ## egg:rhodecode-enterprise-ce#ldap
228 228 ## egg:rhodecode-enterprise-ce#jasig_cas
229 229 ## egg:rhodecode-enterprise-ce#headers
230 230 ## egg:rhodecode-enterprise-ce#crowd
231 231 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
232 232
233 233 ## alternative return HTTP header for failed authentication. Default HTTP
234 234 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
235 235 ## handling that causing a series of failed authentication calls.
236 236 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
237 237 ## This will be served instead of default 401 on bad authnetication
238 238 auth_ret_code =
239 239
240 240 ## use special detection method when serving auth_ret_code, instead of serving
241 241 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
242 242 ## and then serve auth_ret_code to clients
243 243 auth_ret_code_detection = false
244 244
245 245 ## locking return code. When repository is locked return this HTTP code. 2XX
246 246 ## codes don't break the transactions while 4XX codes do
247 247 lock_ret_code = 423
248 248
249 249 ## allows to change the repository location in settings page
250 250 allow_repo_location_change = true
251 251
252 252 ## allows to setup custom hooks in settings page
253 253 allow_custom_hooks_settings = true
254 254
255 255 ## generated license token, goto license page in RhodeCode settings to obtain
256 256 ## new token
257 257 license_token =
258 258
259 259 ## supervisor connection uri, for managing supervisor and logs.
260 260 supervisor.uri =
261 261 ## supervisord group name/id we only want this RC instance to handle
262 262 supervisor.group_id = prod
263 263
264 264 ## Display extended labs settings
265 265 labs_settings_active = true
266 266
267 267 ####################################
268 268 ### CELERY CONFIG ####
269 269 ####################################
270 270 ## run: /path/to/celery worker \
271 271 ## -E --beat --app rhodecode.lib.celerylib.loader \
272 272 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
273 273 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
274 274
275 275 use_celery = false
276 276
277 277 ## connection url to the message broker (default rabbitmq)
278 278 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
279 279
280 280 ## maximum tasks to execute before worker restart
281 281 celery.max_tasks_per_child = 100
282 282
283 283 ## tasks will never be sent to the queue, but executed locally instead.
284 284 celery.task_always_eager = false
285 285
286 286 ####################################
287 287 ### BEAKER CACHE ####
288 288 ####################################
289 289 # default cache dir for templates. Putting this into a ramdisk
290 290 ## can boost performance, eg. %(here)s/data_ramdisk
291 291 cache_dir = %(here)s/data
292 292
293 293 ## locking and default file storage for Beaker. Putting this into a ramdisk
294 294 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
295 295 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
296 296 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
297 297
298 beaker.cache.regions = short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
299
300 # used for caching user permissions
301 beaker.cache.short_term.type = file
302 beaker.cache.short_term.expire = 0
303 beaker.cache.short_term.key_length = 256
298 beaker.cache.regions = long_term, sql_cache_short, repo_cache_long
304 299
305 300 beaker.cache.long_term.type = memory
306 301 beaker.cache.long_term.expire = 36000
307 302 beaker.cache.long_term.key_length = 256
308 303
309 304 beaker.cache.sql_cache_short.type = memory
310 305 beaker.cache.sql_cache_short.expire = 10
311 306 beaker.cache.sql_cache_short.key_length = 256
312 307
313 ## default is memory cache, configure only if required
314 ## using multi-node or multi-worker setup
315 #beaker.cache.auth_plugins.type = ext:database
316 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
317 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
318 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
319 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
320 #beaker.cache.auth_plugins.sa.pool_size = 10
321 #beaker.cache.auth_plugins.sa.max_overflow = 0
322
323 308 beaker.cache.repo_cache_long.type = memorylru_base
324 309 beaker.cache.repo_cache_long.max_items = 4096
325 310 beaker.cache.repo_cache_long.expire = 2592000
326 311
327 312 ## default is memorylru_base cache, configure only if required
328 313 ## using multi-node or multi-worker setup
329 314 #beaker.cache.repo_cache_long.type = ext:memcached
330 315 #beaker.cache.repo_cache_long.url = localhost:11211
331 316 #beaker.cache.repo_cache_long.expire = 1209600
332 317 #beaker.cache.repo_cache_long.key_length = 256
333 318
334 319 ####################################
335 320 ### BEAKER SESSION ####
336 321 ####################################
337 322
338 323 ## .session.type is type of storage options for the session, current allowed
339 324 ## types are file, ext:memcached, ext:database, and memory (default).
340 325 beaker.session.type = file
341 326 beaker.session.data_dir = %(here)s/data/sessions/data
342 327
343 328 ## db based session, fast, and allows easy management over logged in users
344 329 #beaker.session.type = ext:database
345 330 #beaker.session.table_name = db_session
346 331 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
347 332 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
348 333 #beaker.session.sa.pool_recycle = 3600
349 334 #beaker.session.sa.echo = false
350 335
351 336 beaker.session.key = rhodecode
352 337 beaker.session.secret = production-rc-uytcxaz
353 338 beaker.session.lock_dir = %(here)s/data/sessions/lock
354 339
355 340 ## Secure encrypted cookie. Requires AES and AES python libraries
356 341 ## you must disable beaker.session.secret to use this
357 342 #beaker.session.encrypt_key = key_for_encryption
358 343 #beaker.session.validate_key = validation_key
359 344
360 345 ## sets session as invalid(also logging out user) if it haven not been
361 346 ## accessed for given amount of time in seconds
362 347 beaker.session.timeout = 2592000
363 348 beaker.session.httponly = true
364 349 ## Path to use for the cookie. Set to prefix if you use prefix middleware
365 350 #beaker.session.cookie_path = /custom_prefix
366 351
367 352 ## uncomment for https secure cookie
368 353 beaker.session.secure = false
369 354
370 355 ## auto save the session to not to use .save()
371 356 beaker.session.auto = false
372 357
373 358 ## default cookie expiration time in seconds, set to `true` to set expire
374 359 ## at browser close
375 360 #beaker.session.cookie_expires = 3600
376 361
377 362 ###################################
378 363 ## SEARCH INDEXING CONFIGURATION ##
379 364 ###################################
380 365 ## Full text search indexer is available in rhodecode-tools under
381 366 ## `rhodecode-tools index` command
382 367
383 368 ## WHOOSH Backend, doesn't require additional services to run
384 369 ## it works good with few dozen repos
385 370 search.module = rhodecode.lib.index.whoosh
386 371 search.location = %(here)s/data/index
387 372
388 373 ########################################
389 374 ### CHANNELSTREAM CONFIG ####
390 375 ########################################
391 376 ## channelstream enables persistent connections and live notification
392 377 ## in the system. It's also used by the chat system
393 378 channelstream.enabled = false
394 379
395 380 ## server address for channelstream server on the backend
396 381 channelstream.server = 127.0.0.1:9800
397 382
398 383 ## location of the channelstream server from outside world
399 384 ## use ws:// for http or wss:// for https. This address needs to be handled
400 385 ## by external HTTP server such as Nginx or Apache
401 386 ## see nginx/apache configuration examples in our docs
402 387 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
403 388 channelstream.secret = secret
404 389 channelstream.history.location = %(here)s/channelstream_history
405 390
406 391 ## Internal application path that Javascript uses to connect into.
407 392 ## If you use proxy-prefix the prefix should be added before /_channelstream
408 393 channelstream.proxy_path = /_channelstream
409 394
410 395
411 396 ###################################
412 397 ## APPENLIGHT CONFIG ##
413 398 ###################################
414 399
415 400 ## Appenlight is tailored to work with RhodeCode, see
416 401 ## http://appenlight.com for details how to obtain an account
417 402
418 403 ## appenlight integration enabled
419 404 appenlight = false
420 405
421 406 appenlight.server_url = https://api.appenlight.com
422 407 appenlight.api_key = YOUR_API_KEY
423 408 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
424 409
425 410 # used for JS client
426 411 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
427 412
428 413 ## TWEAK AMOUNT OF INFO SENT HERE
429 414
430 415 ## enables 404 error logging (default False)
431 416 appenlight.report_404 = false
432 417
433 418 ## time in seconds after request is considered being slow (default 1)
434 419 appenlight.slow_request_time = 1
435 420
436 421 ## record slow requests in application
437 422 ## (needs to be enabled for slow datastore recording and time tracking)
438 423 appenlight.slow_requests = true
439 424
440 425 ## enable hooking to application loggers
441 426 appenlight.logging = true
442 427
443 428 ## minimum log level for log capture
444 429 appenlight.logging.level = WARNING
445 430
446 431 ## send logs only from erroneous/slow requests
447 432 ## (saves API quota for intensive logging)
448 433 appenlight.logging_on_error = false
449 434
450 435 ## list of additonal keywords that should be grabbed from environ object
451 436 ## can be string with comma separated list of words in lowercase
452 437 ## (by default client will always send following info:
453 438 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
454 439 ## start with HTTP* this list be extended with additional keywords here
455 440 appenlight.environ_keys_whitelist =
456 441
457 442 ## list of keywords that should be blanked from request object
458 443 ## can be string with comma separated list of words in lowercase
459 444 ## (by default client will always blank keys that contain following words
460 445 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
461 446 ## this list be extended with additional keywords set here
462 447 appenlight.request_keys_blacklist =
463 448
464 449 ## list of namespaces that should be ignores when gathering log entries
465 450 ## can be string with comma separated list of namespaces
466 451 ## (by default the client ignores own entries: appenlight_client.client)
467 452 appenlight.log_namespace_blacklist =
468 453
469 454
470 455 ################################################################################
471 456 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
472 457 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
473 458 ## execute malicious code after an exception is raised. ##
474 459 ################################################################################
475 460 set debug = false
476 461
477 462
478 463 ###########################################
479 464 ### MAIN RHODECODE DATABASE CONFIG ###
480 465 ###########################################
481 466 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
482 467 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
483 468 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
484 469 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
485 470
486 471 # see sqlalchemy docs for other advanced settings
487 472
488 473 ## print the sql statements to output
489 474 sqlalchemy.db1.echo = false
490 475 ## recycle the connections after this amount of seconds
491 476 sqlalchemy.db1.pool_recycle = 3600
492 477 sqlalchemy.db1.convert_unicode = true
493 478
494 479 ## the number of connections to keep open inside the connection pool.
495 480 ## 0 indicates no limit
496 481 #sqlalchemy.db1.pool_size = 5
497 482
498 483 ## the number of connections to allow in connection pool "overflow", that is
499 484 ## connections that can be opened above and beyond the pool_size setting,
500 485 ## which defaults to five.
501 486 #sqlalchemy.db1.max_overflow = 10
502 487
503 488
504 489 ##################
505 490 ### VCS CONFIG ###
506 491 ##################
507 492 vcs.server.enable = true
508 493 vcs.server = localhost:9900
509 494
510 495 ## Web server connectivity protocol, responsible for web based VCS operatations
511 496 ## Available protocols are:
512 497 ## `http` - use http-rpc backend (default)
513 498 vcs.server.protocol = http
514 499
515 500 ## Push/Pull operations protocol, available options are:
516 501 ## `http` - use http-rpc backend (default)
517 502 ##
518 503 vcs.scm_app_implementation = http
519 504
520 505 ## Push/Pull operations hooks protocol, available options are:
521 506 ## `http` - use http-rpc backend (default)
522 507 vcs.hooks.protocol = http
523 508 ## Host on which this instance is listening for hooks. If vcsserver is in other location
524 509 ## this should be adjusted.
525 510 vcs.hooks.host = 127.0.0.1
526 511
527 512 vcs.server.log_level = info
528 513 ## Start VCSServer with this instance as a subprocess, usefull for development
529 514 vcs.start_server = false
530 515
531 516 ## List of enabled VCS backends, available options are:
532 517 ## `hg` - mercurial
533 518 ## `git` - git
534 519 ## `svn` - subversion
535 520 vcs.backends = hg, git, svn
536 521
537 522 vcs.connection_timeout = 3600
538 523 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
539 524 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
540 525 #vcs.svn.compatible_version = pre-1.8-compatible
541 526
542 527
543 528 ############################################################
544 529 ### Subversion proxy support (mod_dav_svn) ###
545 530 ### Maps RhodeCode repo groups into SVN paths for Apache ###
546 531 ############################################################
547 532 ## Enable or disable the config file generation.
548 533 svn.proxy.generate_config = false
549 534 ## Generate config file with `SVNListParentPath` set to `On`.
550 535 svn.proxy.list_parent_path = true
551 536 ## Set location and file name of generated config file.
552 537 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
553 538 ## alternative mod_dav config template. This needs to be a mako template
554 539 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
555 540 ## Used as a prefix to the `Location` block in the generated config file.
556 541 ## In most cases it should be set to `/`.
557 542 svn.proxy.location_root = /
558 543 ## Command to reload the mod dav svn configuration on change.
559 544 ## Example: `/etc/init.d/apache2 reload`
560 545 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
561 546 ## If the timeout expires before the reload command finishes, the command will
562 547 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
563 548 #svn.proxy.reload_timeout = 10
564 549
565 550 ############################################################
566 551 ### SSH Support Settings ###
567 552 ############################################################
568 553
569 554 ## Defines if a custom authorized_keys file should be created and written on
570 555 ## any change user ssh keys. Setting this to false also disables posibility
571 556 ## of adding SSH keys by users from web interface. Super admins can still
572 557 ## manage SSH Keys.
573 558 ssh.generate_authorized_keyfile = false
574 559
575 560 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
576 561 # ssh.authorized_keys_ssh_opts =
577 562
578 563 ## Path to the authrozied_keys file where the generate entries are placed.
579 564 ## It is possible to have multiple key files specified in `sshd_config` e.g.
580 565 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
581 566 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
582 567
583 568 ## Command to execute the SSH wrapper. The binary is available in the
584 569 ## rhodecode installation directory.
585 570 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
586 571 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
587 572
588 573 ## Allow shell when executing the ssh-wrapper command
589 574 ssh.wrapper_cmd_allow_shell = false
590 575
591 576 ## Enables logging, and detailed output send back to the client during SSH
592 577 ## operations. Usefull for debugging, shouldn't be used in production.
593 578 ssh.enable_debug_logging = false
594 579
595 580 ## Paths to binary executable, by default they are the names, but we can
596 581 ## override them if we want to use a custom one
597 582 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
598 583 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
599 584 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
600 585
601 586
602 587 ## Dummy marker to add new entries after.
603 588 ## Add any custom entries below. Please don't remove.
604 589 custom.conf = 1
605 590
606 591
607 592 ################################
608 593 ### LOGGING CONFIGURATION ####
609 594 ################################
610 595 [loggers]
611 596 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper, celery
612 597
613 598 [handlers]
614 599 keys = console, console_sql
615 600
616 601 [formatters]
617 602 keys = generic, color_formatter, color_formatter_sql
618 603
619 604 #############
620 605 ## LOGGERS ##
621 606 #############
622 607 [logger_root]
623 608 level = NOTSET
624 609 handlers = console
625 610
626 611 [logger_sqlalchemy]
627 612 level = INFO
628 613 handlers = console_sql
629 614 qualname = sqlalchemy.engine
630 615 propagate = 0
631 616
632 617 [logger_beaker]
633 618 level = DEBUG
634 619 handlers =
635 620 qualname = beaker.container
636 621 propagate = 1
637 622
638 623 [logger_rhodecode]
639 624 level = DEBUG
640 625 handlers =
641 626 qualname = rhodecode
642 627 propagate = 1
643 628
644 629 [logger_ssh_wrapper]
645 630 level = DEBUG
646 631 handlers =
647 632 qualname = ssh_wrapper
648 633 propagate = 1
649 634
650 635 [logger_celery]
651 636 level = DEBUG
652 637 handlers =
653 638 qualname = celery
654 639
655 640
656 641 ##############
657 642 ## HANDLERS ##
658 643 ##############
659 644
660 645 [handler_console]
661 646 class = StreamHandler
662 647 args = (sys.stderr, )
663 648 level = INFO
664 649 formatter = generic
665 650
666 651 [handler_console_sql]
667 652 class = StreamHandler
668 653 args = (sys.stderr, )
669 654 level = WARN
670 655 formatter = generic
671 656
672 657 ################
673 658 ## FORMATTERS ##
674 659 ################
675 660
676 661 [formatter_generic]
677 662 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
678 663 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
679 664 datefmt = %Y-%m-%d %H:%M:%S
680 665
681 666 [formatter_color_formatter]
682 667 class = rhodecode.lib.logging_formatter.ColorFormatter
683 668 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
684 669 datefmt = %Y-%m-%d %H:%M:%S
685 670
686 671 [formatter_color_formatter_sql]
687 672 class = rhodecode.lib.logging_formatter.ColorFormatterSql
688 673 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
689 674 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,414 +1,424 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def admin_routes(config):
26 26 """
27 27 Admin prefixed routes
28 28 """
29 29
30 30 config.add_route(
31 31 name='admin_audit_logs',
32 32 pattern='/audit_logs')
33 33
34 34 config.add_route(
35 35 name='admin_audit_log_entry',
36 36 pattern='/audit_logs/{audit_log_id}')
37 37
38 38 config.add_route(
39 39 name='pull_requests_global_0', # backward compat
40 40 pattern='/pull_requests/{pull_request_id:\d+}')
41 41 config.add_route(
42 42 name='pull_requests_global_1', # backward compat
43 43 pattern='/pull-requests/{pull_request_id:\d+}')
44 44 config.add_route(
45 45 name='pull_requests_global',
46 46 pattern='/pull-request/{pull_request_id:\d+}')
47 47
48 48 config.add_route(
49 49 name='admin_settings_open_source',
50 50 pattern='/settings/open_source')
51 51 config.add_route(
52 52 name='admin_settings_vcs_svn_generate_cfg',
53 53 pattern='/settings/vcs/svn_generate_cfg')
54 54
55 55 config.add_route(
56 56 name='admin_settings_system',
57 57 pattern='/settings/system')
58 58 config.add_route(
59 59 name='admin_settings_system_update',
60 60 pattern='/settings/system/updates')
61 61
62 62 config.add_route(
63 63 name='admin_settings_sessions',
64 64 pattern='/settings/sessions')
65 65 config.add_route(
66 66 name='admin_settings_sessions_cleanup',
67 67 pattern='/settings/sessions/cleanup')
68 68
69 69 config.add_route(
70 70 name='admin_settings_process_management',
71 71 pattern='/settings/process_management')
72 72 config.add_route(
73 73 name='admin_settings_process_management_data',
74 74 pattern='/settings/process_management/data')
75 75 config.add_route(
76 76 name='admin_settings_process_management_signal',
77 77 pattern='/settings/process_management/signal')
78 78 config.add_route(
79 79 name='admin_settings_process_management_master_signal',
80 80 pattern='/settings/process_management/master_signal')
81 81
82 82 # default settings
83 83 config.add_route(
84 84 name='admin_defaults_repositories',
85 85 pattern='/defaults/repositories')
86 86 config.add_route(
87 87 name='admin_defaults_repositories_update',
88 88 pattern='/defaults/repositories/update')
89 89
90 90 # admin settings
91 91
92 92 config.add_route(
93 93 name='admin_settings',
94 94 pattern='/settings')
95 95 config.add_route(
96 96 name='admin_settings_update',
97 97 pattern='/settings/update')
98 98
99 99 config.add_route(
100 100 name='admin_settings_global',
101 101 pattern='/settings/global')
102 102 config.add_route(
103 103 name='admin_settings_global_update',
104 104 pattern='/settings/global/update')
105 105
106 106 config.add_route(
107 107 name='admin_settings_vcs',
108 108 pattern='/settings/vcs')
109 109 config.add_route(
110 110 name='admin_settings_vcs_update',
111 111 pattern='/settings/vcs/update')
112 112 config.add_route(
113 113 name='admin_settings_vcs_svn_pattern_delete',
114 114 pattern='/settings/vcs/svn_pattern_delete')
115 115
116 116 config.add_route(
117 117 name='admin_settings_mapping',
118 118 pattern='/settings/mapping')
119 119 config.add_route(
120 120 name='admin_settings_mapping_update',
121 121 pattern='/settings/mapping/update')
122 122
123 123 config.add_route(
124 124 name='admin_settings_visual',
125 125 pattern='/settings/visual')
126 126 config.add_route(
127 127 name='admin_settings_visual_update',
128 128 pattern='/settings/visual/update')
129 129
130 130
131 131 config.add_route(
132 132 name='admin_settings_issuetracker',
133 133 pattern='/settings/issue-tracker')
134 134 config.add_route(
135 135 name='admin_settings_issuetracker_update',
136 136 pattern='/settings/issue-tracker/update')
137 137 config.add_route(
138 138 name='admin_settings_issuetracker_test',
139 139 pattern='/settings/issue-tracker/test')
140 140 config.add_route(
141 141 name='admin_settings_issuetracker_delete',
142 142 pattern='/settings/issue-tracker/delete')
143 143
144 144 config.add_route(
145 145 name='admin_settings_email',
146 146 pattern='/settings/email')
147 147 config.add_route(
148 148 name='admin_settings_email_update',
149 149 pattern='/settings/email/update')
150 150
151 151 config.add_route(
152 152 name='admin_settings_hooks',
153 153 pattern='/settings/hooks')
154 154 config.add_route(
155 155 name='admin_settings_hooks_update',
156 156 pattern='/settings/hooks/update')
157 157 config.add_route(
158 158 name='admin_settings_hooks_delete',
159 159 pattern='/settings/hooks/delete')
160 160
161 161 config.add_route(
162 162 name='admin_settings_search',
163 163 pattern='/settings/search')
164 164
165 165 config.add_route(
166 166 name='admin_settings_labs',
167 167 pattern='/settings/labs')
168 168 config.add_route(
169 169 name='admin_settings_labs_update',
170 170 pattern='/settings/labs/update')
171 171
172 172 # Automation EE feature
173 173 config.add_route(
174 174 'admin_settings_automation',
175 175 pattern=ADMIN_PREFIX + '/settings/automation')
176 176
177 177 # global permissions
178 178
179 179 config.add_route(
180 180 name='admin_permissions_application',
181 181 pattern='/permissions/application')
182 182 config.add_route(
183 183 name='admin_permissions_application_update',
184 184 pattern='/permissions/application/update')
185 185
186 186 config.add_route(
187 187 name='admin_permissions_global',
188 188 pattern='/permissions/global')
189 189 config.add_route(
190 190 name='admin_permissions_global_update',
191 191 pattern='/permissions/global/update')
192 192
193 193 config.add_route(
194 194 name='admin_permissions_object',
195 195 pattern='/permissions/object')
196 196 config.add_route(
197 197 name='admin_permissions_object_update',
198 198 pattern='/permissions/object/update')
199 199
200 200 config.add_route(
201 201 name='admin_permissions_ips',
202 202 pattern='/permissions/ips')
203 203
204 204 config.add_route(
205 205 name='admin_permissions_overview',
206 206 pattern='/permissions/overview')
207 207
208 208 config.add_route(
209 209 name='admin_permissions_auth_token_access',
210 210 pattern='/permissions/auth_token_access')
211 211
212 212 config.add_route(
213 213 name='admin_permissions_ssh_keys',
214 214 pattern='/permissions/ssh_keys')
215 215 config.add_route(
216 216 name='admin_permissions_ssh_keys_data',
217 217 pattern='/permissions/ssh_keys/data')
218 218 config.add_route(
219 219 name='admin_permissions_ssh_keys_update',
220 220 pattern='/permissions/ssh_keys/update')
221 221
222 222 # users admin
223 223 config.add_route(
224 224 name='users',
225 225 pattern='/users')
226 226
227 227 config.add_route(
228 228 name='users_data',
229 229 pattern='/users_data')
230 230
231 231 config.add_route(
232 232 name='users_create',
233 233 pattern='/users/create')
234 234
235 235 config.add_route(
236 236 name='users_new',
237 237 pattern='/users/new')
238 238
239 239 # user management
240 240 config.add_route(
241 241 name='user_edit',
242 242 pattern='/users/{user_id:\d+}/edit',
243 243 user_route=True)
244 244 config.add_route(
245 245 name='user_edit_advanced',
246 246 pattern='/users/{user_id:\d+}/edit/advanced',
247 247 user_route=True)
248 248 config.add_route(
249 249 name='user_edit_global_perms',
250 250 pattern='/users/{user_id:\d+}/edit/global_permissions',
251 251 user_route=True)
252 252 config.add_route(
253 253 name='user_edit_global_perms_update',
254 254 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
255 255 user_route=True)
256 256 config.add_route(
257 257 name='user_update',
258 258 pattern='/users/{user_id:\d+}/update',
259 259 user_route=True)
260 260 config.add_route(
261 261 name='user_delete',
262 262 pattern='/users/{user_id:\d+}/delete',
263 263 user_route=True)
264 264 config.add_route(
265 265 name='user_force_password_reset',
266 266 pattern='/users/{user_id:\d+}/password_reset',
267 267 user_route=True)
268 268 config.add_route(
269 269 name='user_create_personal_repo_group',
270 270 pattern='/users/{user_id:\d+}/create_repo_group',
271 271 user_route=True)
272 272
273 273 # user auth tokens
274 274 config.add_route(
275 275 name='edit_user_auth_tokens',
276 276 pattern='/users/{user_id:\d+}/edit/auth_tokens',
277 277 user_route=True)
278 278 config.add_route(
279 279 name='edit_user_auth_tokens_add',
280 280 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
281 281 user_route=True)
282 282 config.add_route(
283 283 name='edit_user_auth_tokens_delete',
284 284 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
285 285 user_route=True)
286 286
287 287 # user ssh keys
288 288 config.add_route(
289 289 name='edit_user_ssh_keys',
290 290 pattern='/users/{user_id:\d+}/edit/ssh_keys',
291 291 user_route=True)
292 292 config.add_route(
293 293 name='edit_user_ssh_keys_generate_keypair',
294 294 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
295 295 user_route=True)
296 296 config.add_route(
297 297 name='edit_user_ssh_keys_add',
298 298 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
299 299 user_route=True)
300 300 config.add_route(
301 301 name='edit_user_ssh_keys_delete',
302 302 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
303 303 user_route=True)
304 304
305 305 # user emails
306 306 config.add_route(
307 307 name='edit_user_emails',
308 308 pattern='/users/{user_id:\d+}/edit/emails',
309 309 user_route=True)
310 310 config.add_route(
311 311 name='edit_user_emails_add',
312 312 pattern='/users/{user_id:\d+}/edit/emails/new',
313 313 user_route=True)
314 314 config.add_route(
315 315 name='edit_user_emails_delete',
316 316 pattern='/users/{user_id:\d+}/edit/emails/delete',
317 317 user_route=True)
318 318
319 319 # user IPs
320 320 config.add_route(
321 321 name='edit_user_ips',
322 322 pattern='/users/{user_id:\d+}/edit/ips',
323 323 user_route=True)
324 324 config.add_route(
325 325 name='edit_user_ips_add',
326 326 pattern='/users/{user_id:\d+}/edit/ips/new',
327 327 user_route_with_default=True) # enabled for default user too
328 328 config.add_route(
329 329 name='edit_user_ips_delete',
330 330 pattern='/users/{user_id:\d+}/edit/ips/delete',
331 331 user_route_with_default=True) # enabled for default user too
332 332
333 333 # user perms
334 334 config.add_route(
335 335 name='edit_user_perms_summary',
336 336 pattern='/users/{user_id:\d+}/edit/permissions_summary',
337 337 user_route=True)
338 338 config.add_route(
339 339 name='edit_user_perms_summary_json',
340 340 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
341 341 user_route=True)
342 342
343 343 # user user groups management
344 344 config.add_route(
345 345 name='edit_user_groups_management',
346 346 pattern='/users/{user_id:\d+}/edit/groups_management',
347 347 user_route=True)
348 348
349 349 config.add_route(
350 350 name='edit_user_groups_management_updates',
351 351 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
352 352 user_route=True)
353 353
354 354 # user audit logs
355 355 config.add_route(
356 356 name='edit_user_audit_logs',
357 357 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
358 358
359 # user caches
360 config.add_route(
361 name='edit_user_caches',
362 pattern='/users/{user_id:\d+}/edit/caches',
363 user_route=True)
364 config.add_route(
365 name='edit_user_caches_update',
366 pattern='/users/{user_id:\d+}/edit/caches/update',
367 user_route=True)
368
359 369 # user-groups admin
360 370 config.add_route(
361 371 name='user_groups',
362 372 pattern='/user_groups')
363 373
364 374 config.add_route(
365 375 name='user_groups_data',
366 376 pattern='/user_groups_data')
367 377
368 378 config.add_route(
369 379 name='user_groups_new',
370 380 pattern='/user_groups/new')
371 381
372 382 config.add_route(
373 383 name='user_groups_create',
374 384 pattern='/user_groups/create')
375 385
376 386 # repos admin
377 387 config.add_route(
378 388 name='repos',
379 389 pattern='/repos')
380 390
381 391 config.add_route(
382 392 name='repo_new',
383 393 pattern='/repos/new')
384 394
385 395 config.add_route(
386 396 name='repo_create',
387 397 pattern='/repos/create')
388 398
389 399 # repo groups admin
390 400 config.add_route(
391 401 name='repo_groups',
392 402 pattern='/repo_groups')
393 403
394 404 config.add_route(
395 405 name='repo_group_new',
396 406 pattern='/repo_group/new')
397 407
398 408 config.add_route(
399 409 name='repo_group_create',
400 410 pattern='/repo_group/create')
401 411
402 412
403 413 def includeme(config):
404 414 from rhodecode.apps.admin.navigation import includeme as nav_includeme
405 415
406 416 # Create admin navigation registry and add it to the pyramid registry.
407 417 nav_includeme(config)
408 418
409 419 # main admin routes
410 420 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
411 421 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
412 422
413 423 # Scan module for configuration decorators.
414 424 config.scan('.views', ignore='.tests')
@@ -1,1200 +1,1254 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34 34 from rhodecode.events import trigger
35 35 from rhodecode.model.db import true
36 36
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger, rc_cache
38 38 from rhodecode.lib.exceptions import (
39 39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
40 40 UserOwnsUserGroupsException, DefaultUserException)
41 41 from rhodecode.lib.ext_json import json
42 42 from rhodecode.lib.auth import (
43 43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
44 44 from rhodecode.lib import helpers as h
45 45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
46 46 from rhodecode.model.auth_token import AuthTokenModel
47 47 from rhodecode.model.forms import (
48 48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
49 49 UserExtraEmailForm, UserExtraIpForm)
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.model.repo_group import RepoGroupModel
52 52 from rhodecode.model.ssh_key import SshKeyModel
53 53 from rhodecode.model.user import UserModel
54 54 from rhodecode.model.user_group import UserGroupModel
55 55 from rhodecode.model.db import (
56 56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
57 57 UserApiKeys, UserSshKeys, RepoGroup)
58 58 from rhodecode.model.meta import Session
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class AdminUsersView(BaseAppView, DataGridAppView):
64 64
65 65 def load_default_context(self):
66 66 c = self._get_local_tmpl_context()
67 67 return c
68 68
69 69 @LoginRequired()
70 70 @HasPermissionAllDecorator('hg.admin')
71 71 @view_config(
72 72 route_name='users', request_method='GET',
73 73 renderer='rhodecode:templates/admin/users/users.mako')
74 74 def users_list(self):
75 75 c = self.load_default_context()
76 76 return self._get_template_context(c)
77 77
78 78 @LoginRequired()
79 79 @HasPermissionAllDecorator('hg.admin')
80 80 @view_config(
81 81 # renderer defined below
82 82 route_name='users_data', request_method='GET',
83 83 renderer='json_ext', xhr=True)
84 84 def users_list_data(self):
85 85 self.load_default_context()
86 86 column_map = {
87 87 'first_name': 'name',
88 88 'last_name': 'lastname',
89 89 }
90 90 draw, start, limit = self._extract_chunk(self.request)
91 91 search_q, order_by, order_dir = self._extract_ordering(
92 92 self.request, column_map=column_map)
93 93 _render = self.request.get_partial_renderer(
94 94 'rhodecode:templates/data_table/_dt_elements.mako')
95 95
96 96 def user_actions(user_id, username):
97 97 return _render("user_actions", user_id, username)
98 98
99 99 users_data_total_count = User.query()\
100 100 .filter(User.username != User.DEFAULT_USER) \
101 101 .count()
102 102
103 103 users_data_total_inactive_count = User.query()\
104 104 .filter(User.username != User.DEFAULT_USER) \
105 105 .filter(User.active != true())\
106 106 .count()
107 107
108 108 # json generate
109 109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 110 base_inactive_q = base_q.filter(User.active != true())
111 111
112 112 if search_q:
113 113 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 114 base_q = base_q.filter(or_(
115 115 User.username.ilike(like_expression),
116 116 User._email.ilike(like_expression),
117 117 User.name.ilike(like_expression),
118 118 User.lastname.ilike(like_expression),
119 119 ))
120 120 base_inactive_q = base_q.filter(User.active != true())
121 121
122 122 users_data_total_filtered_count = base_q.count()
123 123 users_data_total_filtered_inactive_count = base_inactive_q.count()
124 124
125 125 sort_col = getattr(User, order_by, None)
126 126 if sort_col:
127 127 if order_dir == 'asc':
128 128 # handle null values properly to order by NULL last
129 129 if order_by in ['last_activity']:
130 130 sort_col = coalesce(sort_col, datetime.date.max)
131 131 sort_col = sort_col.asc()
132 132 else:
133 133 # handle null values properly to order by NULL last
134 134 if order_by in ['last_activity']:
135 135 sort_col = coalesce(sort_col, datetime.date.min)
136 136 sort_col = sort_col.desc()
137 137
138 138 base_q = base_q.order_by(sort_col)
139 139 base_q = base_q.offset(start).limit(limit)
140 140
141 141 users_list = base_q.all()
142 142
143 143 users_data = []
144 144 for user in users_list:
145 145 users_data.append({
146 146 "username": h.gravatar_with_user(self.request, user.username),
147 147 "email": user.email,
148 148 "first_name": user.first_name,
149 149 "last_name": user.last_name,
150 150 "last_login": h.format_date(user.last_login),
151 151 "last_activity": h.format_date(user.last_activity),
152 152 "active": h.bool2icon(user.active),
153 153 "active_raw": user.active,
154 154 "admin": h.bool2icon(user.admin),
155 155 "extern_type": user.extern_type,
156 156 "extern_name": user.extern_name,
157 157 "action": user_actions(user.user_id, user.username),
158 158 })
159 159 data = ({
160 160 'draw': draw,
161 161 'data': users_data,
162 162 'recordsTotal': users_data_total_count,
163 163 'recordsFiltered': users_data_total_filtered_count,
164 164 'recordsTotalInactive': users_data_total_inactive_count,
165 165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
166 166 })
167 167
168 168 return data
169 169
170 170 def _set_personal_repo_group_template_vars(self, c_obj):
171 171 DummyUser = AttributeDict({
172 172 'username': '${username}',
173 173 'user_id': '${user_id}',
174 174 })
175 175 c_obj.default_create_repo_group = RepoGroupModel() \
176 176 .get_default_create_personal_repo_group()
177 177 c_obj.personal_repo_group_name = RepoGroupModel() \
178 178 .get_personal_group_name(DummyUser)
179 179
180 180 @LoginRequired()
181 181 @HasPermissionAllDecorator('hg.admin')
182 182 @view_config(
183 183 route_name='users_new', request_method='GET',
184 184 renderer='rhodecode:templates/admin/users/user_add.mako')
185 185 def users_new(self):
186 186 _ = self.request.translate
187 187 c = self.load_default_context()
188 188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
189 189 self._set_personal_repo_group_template_vars(c)
190 190 return self._get_template_context(c)
191 191
192 192 @LoginRequired()
193 193 @HasPermissionAllDecorator('hg.admin')
194 194 @CSRFRequired()
195 195 @view_config(
196 196 route_name='users_create', request_method='POST',
197 197 renderer='rhodecode:templates/admin/users/user_add.mako')
198 198 def users_create(self):
199 199 _ = self.request.translate
200 200 c = self.load_default_context()
201 201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
202 202 user_model = UserModel()
203 203 user_form = UserForm(self.request.translate)()
204 204 try:
205 205 form_result = user_form.to_python(dict(self.request.POST))
206 206 user = user_model.create(form_result)
207 207 Session().flush()
208 208 creation_data = user.get_api_data()
209 209 username = form_result['username']
210 210
211 211 audit_logger.store_web(
212 212 'user.create', action_data={'data': creation_data},
213 213 user=c.rhodecode_user)
214 214
215 215 user_link = h.link_to(
216 216 h.escape(username),
217 217 h.route_path('user_edit', user_id=user.user_id))
218 218 h.flash(h.literal(_('Created user %(user_link)s')
219 219 % {'user_link': user_link}), category='success')
220 220 Session().commit()
221 221 except formencode.Invalid as errors:
222 222 self._set_personal_repo_group_template_vars(c)
223 223 data = render(
224 224 'rhodecode:templates/admin/users/user_add.mako',
225 225 self._get_template_context(c), self.request)
226 226 html = formencode.htmlfill.render(
227 227 data,
228 228 defaults=errors.value,
229 229 errors=errors.error_dict or {},
230 230 prefix_error=False,
231 231 encoding="UTF-8",
232 232 force_defaults=False
233 233 )
234 234 return Response(html)
235 235 except UserCreationError as e:
236 236 h.flash(e, 'error')
237 237 except Exception:
238 238 log.exception("Exception creation of user")
239 239 h.flash(_('Error occurred during creation of user %s')
240 240 % self.request.POST.get('username'), category='error')
241 241 raise HTTPFound(h.route_path('users'))
242 242
243 243
244 244 class UsersView(UserAppView):
245 245 ALLOW_SCOPED_TOKENS = False
246 246 """
247 247 This view has alternative version inside EE, if modified please take a look
248 248 in there as well.
249 249 """
250 250
251 251 def load_default_context(self):
252 252 c = self._get_local_tmpl_context()
253 253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
254 254 c.allowed_languages = [
255 255 ('en', 'English (en)'),
256 256 ('de', 'German (de)'),
257 257 ('fr', 'French (fr)'),
258 258 ('it', 'Italian (it)'),
259 259 ('ja', 'Japanese (ja)'),
260 260 ('pl', 'Polish (pl)'),
261 261 ('pt', 'Portuguese (pt)'),
262 262 ('ru', 'Russian (ru)'),
263 263 ('zh', 'Chinese (zh)'),
264 264 ]
265 265 req = self.request
266 266
267 267 c.available_permissions = req.registry.settings['available_permissions']
268 268 PermissionModel().set_global_permission_choices(
269 269 c, gettext_translator=req.translate)
270 270
271 271 return c
272 272
273 273 @LoginRequired()
274 274 @HasPermissionAllDecorator('hg.admin')
275 275 @CSRFRequired()
276 276 @view_config(
277 277 route_name='user_update', request_method='POST',
278 278 renderer='rhodecode:templates/admin/users/user_edit.mako')
279 279 def user_update(self):
280 280 _ = self.request.translate
281 281 c = self.load_default_context()
282 282
283 283 user_id = self.db_user_id
284 284 c.user = self.db_user
285 285
286 286 c.active = 'profile'
287 287 c.extern_type = c.user.extern_type
288 288 c.extern_name = c.user.extern_name
289 289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
290 290 available_languages = [x[0] for x in c.allowed_languages]
291 291 _form = UserForm(self.request.translate, edit=True,
292 292 available_languages=available_languages,
293 293 old_data={'user_id': user_id,
294 294 'email': c.user.email})()
295 295 form_result = {}
296 296 old_values = c.user.get_api_data()
297 297 try:
298 298 form_result = _form.to_python(dict(self.request.POST))
299 299 skip_attrs = ['extern_type', 'extern_name']
300 300 # TODO: plugin should define if username can be updated
301 301 if c.extern_type != "rhodecode":
302 302 # forbid updating username for external accounts
303 303 skip_attrs.append('username')
304 304
305 305 UserModel().update_user(
306 306 user_id, skip_attrs=skip_attrs, **form_result)
307 307
308 308 audit_logger.store_web(
309 309 'user.edit', action_data={'old_data': old_values},
310 310 user=c.rhodecode_user)
311 311
312 312 Session().commit()
313 313 h.flash(_('User updated successfully'), category='success')
314 314 except formencode.Invalid as errors:
315 315 data = render(
316 316 'rhodecode:templates/admin/users/user_edit.mako',
317 317 self._get_template_context(c), self.request)
318 318 html = formencode.htmlfill.render(
319 319 data,
320 320 defaults=errors.value,
321 321 errors=errors.error_dict or {},
322 322 prefix_error=False,
323 323 encoding="UTF-8",
324 324 force_defaults=False
325 325 )
326 326 return Response(html)
327 327 except UserCreationError as e:
328 328 h.flash(e, 'error')
329 329 except Exception:
330 330 log.exception("Exception updating user")
331 331 h.flash(_('Error occurred during update of user %s')
332 332 % form_result.get('username'), category='error')
333 333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
334 334
335 335 @LoginRequired()
336 336 @HasPermissionAllDecorator('hg.admin')
337 337 @CSRFRequired()
338 338 @view_config(
339 339 route_name='user_delete', request_method='POST',
340 340 renderer='rhodecode:templates/admin/users/user_edit.mako')
341 341 def user_delete(self):
342 342 _ = self.request.translate
343 343 c = self.load_default_context()
344 344 c.user = self.db_user
345 345
346 346 _repos = c.user.repositories
347 347 _repo_groups = c.user.repository_groups
348 348 _user_groups = c.user.user_groups
349 349
350 350 handle_repos = None
351 351 handle_repo_groups = None
352 352 handle_user_groups = None
353 353 # dummy call for flash of handle
354 354 set_handle_flash_repos = lambda: None
355 355 set_handle_flash_repo_groups = lambda: None
356 356 set_handle_flash_user_groups = lambda: None
357 357
358 358 if _repos and self.request.POST.get('user_repos'):
359 359 do = self.request.POST['user_repos']
360 360 if do == 'detach':
361 361 handle_repos = 'detach'
362 362 set_handle_flash_repos = lambda: h.flash(
363 363 _('Detached %s repositories') % len(_repos),
364 364 category='success')
365 365 elif do == 'delete':
366 366 handle_repos = 'delete'
367 367 set_handle_flash_repos = lambda: h.flash(
368 368 _('Deleted %s repositories') % len(_repos),
369 369 category='success')
370 370
371 371 if _repo_groups and self.request.POST.get('user_repo_groups'):
372 372 do = self.request.POST['user_repo_groups']
373 373 if do == 'detach':
374 374 handle_repo_groups = 'detach'
375 375 set_handle_flash_repo_groups = lambda: h.flash(
376 376 _('Detached %s repository groups') % len(_repo_groups),
377 377 category='success')
378 378 elif do == 'delete':
379 379 handle_repo_groups = 'delete'
380 380 set_handle_flash_repo_groups = lambda: h.flash(
381 381 _('Deleted %s repository groups') % len(_repo_groups),
382 382 category='success')
383 383
384 384 if _user_groups and self.request.POST.get('user_user_groups'):
385 385 do = self.request.POST['user_user_groups']
386 386 if do == 'detach':
387 387 handle_user_groups = 'detach'
388 388 set_handle_flash_user_groups = lambda: h.flash(
389 389 _('Detached %s user groups') % len(_user_groups),
390 390 category='success')
391 391 elif do == 'delete':
392 392 handle_user_groups = 'delete'
393 393 set_handle_flash_user_groups = lambda: h.flash(
394 394 _('Deleted %s user groups') % len(_user_groups),
395 395 category='success')
396 396
397 397 old_values = c.user.get_api_data()
398 398 try:
399 399 UserModel().delete(c.user, handle_repos=handle_repos,
400 400 handle_repo_groups=handle_repo_groups,
401 401 handle_user_groups=handle_user_groups)
402 402
403 403 audit_logger.store_web(
404 404 'user.delete', action_data={'old_data': old_values},
405 405 user=c.rhodecode_user)
406 406
407 407 Session().commit()
408 408 set_handle_flash_repos()
409 409 set_handle_flash_repo_groups()
410 410 set_handle_flash_user_groups()
411 411 h.flash(_('Successfully deleted user'), category='success')
412 412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
413 413 UserOwnsUserGroupsException, DefaultUserException) as e:
414 414 h.flash(e, category='warning')
415 415 except Exception:
416 416 log.exception("Exception during deletion of user")
417 417 h.flash(_('An error occurred during deletion of user'),
418 418 category='error')
419 419 raise HTTPFound(h.route_path('users'))
420 420
421 421 @LoginRequired()
422 422 @HasPermissionAllDecorator('hg.admin')
423 423 @view_config(
424 424 route_name='user_edit', request_method='GET',
425 425 renderer='rhodecode:templates/admin/users/user_edit.mako')
426 426 def user_edit(self):
427 427 _ = self.request.translate
428 428 c = self.load_default_context()
429 429 c.user = self.db_user
430 430
431 431 c.active = 'profile'
432 432 c.extern_type = c.user.extern_type
433 433 c.extern_name = c.user.extern_name
434 434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
435 435
436 436 defaults = c.user.get_dict()
437 437 defaults.update({'language': c.user.user_data.get('language')})
438 438
439 439 data = render(
440 440 'rhodecode:templates/admin/users/user_edit.mako',
441 441 self._get_template_context(c), self.request)
442 442 html = formencode.htmlfill.render(
443 443 data,
444 444 defaults=defaults,
445 445 encoding="UTF-8",
446 446 force_defaults=False
447 447 )
448 448 return Response(html)
449 449
450 450 @LoginRequired()
451 451 @HasPermissionAllDecorator('hg.admin')
452 452 @view_config(
453 453 route_name='user_edit_advanced', request_method='GET',
454 454 renderer='rhodecode:templates/admin/users/user_edit.mako')
455 455 def user_edit_advanced(self):
456 456 _ = self.request.translate
457 457 c = self.load_default_context()
458 458
459 459 user_id = self.db_user_id
460 460 c.user = self.db_user
461 461
462 462 c.active = 'advanced'
463 463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
464 464 c.personal_repo_group_name = RepoGroupModel()\
465 465 .get_personal_group_name(c.user)
466 466
467 467 c.user_to_review_rules = sorted(
468 468 (x.user for x in c.user.user_review_rules),
469 469 key=lambda u: u.username.lower())
470 470
471 471 c.first_admin = User.get_first_super_admin()
472 472 defaults = c.user.get_dict()
473 473
474 474 # Interim workaround if the user participated on any pull requests as a
475 475 # reviewer.
476 476 has_review = len(c.user.reviewer_pull_requests)
477 477 c.can_delete_user = not has_review
478 478 c.can_delete_user_message = ''
479 479 inactive_link = h.link_to(
480 480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
481 481 if has_review == 1:
482 482 c.can_delete_user_message = h.literal(_(
483 483 'The user participates as reviewer in {} pull request and '
484 484 'cannot be deleted. \nYou can set the user to '
485 485 '"{}" instead of deleting it.').format(
486 486 has_review, inactive_link))
487 487 elif has_review:
488 488 c.can_delete_user_message = h.literal(_(
489 489 'The user participates as reviewer in {} pull requests and '
490 490 'cannot be deleted. \nYou can set the user to '
491 491 '"{}" instead of deleting it.').format(
492 492 has_review, inactive_link))
493 493
494 494 data = render(
495 495 'rhodecode:templates/admin/users/user_edit.mako',
496 496 self._get_template_context(c), self.request)
497 497 html = formencode.htmlfill.render(
498 498 data,
499 499 defaults=defaults,
500 500 encoding="UTF-8",
501 501 force_defaults=False
502 502 )
503 503 return Response(html)
504 504
505 505 @LoginRequired()
506 506 @HasPermissionAllDecorator('hg.admin')
507 507 @view_config(
508 508 route_name='user_edit_global_perms', request_method='GET',
509 509 renderer='rhodecode:templates/admin/users/user_edit.mako')
510 510 def user_edit_global_perms(self):
511 511 _ = self.request.translate
512 512 c = self.load_default_context()
513 513 c.user = self.db_user
514 514
515 515 c.active = 'global_perms'
516 516
517 517 c.default_user = User.get_default_user()
518 518 defaults = c.user.get_dict()
519 519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
520 520 defaults.update(c.default_user.get_default_perms())
521 521 defaults.update(c.user.get_default_perms())
522 522
523 523 data = render(
524 524 'rhodecode:templates/admin/users/user_edit.mako',
525 525 self._get_template_context(c), self.request)
526 526 html = formencode.htmlfill.render(
527 527 data,
528 528 defaults=defaults,
529 529 encoding="UTF-8",
530 530 force_defaults=False
531 531 )
532 532 return Response(html)
533 533
534 534 @LoginRequired()
535 535 @HasPermissionAllDecorator('hg.admin')
536 536 @CSRFRequired()
537 537 @view_config(
538 538 route_name='user_edit_global_perms_update', request_method='POST',
539 539 renderer='rhodecode:templates/admin/users/user_edit.mako')
540 540 def user_edit_global_perms_update(self):
541 541 _ = self.request.translate
542 542 c = self.load_default_context()
543 543
544 544 user_id = self.db_user_id
545 545 c.user = self.db_user
546 546
547 547 c.active = 'global_perms'
548 548 try:
549 549 # first stage that verifies the checkbox
550 550 _form = UserIndividualPermissionsForm(self.request.translate)
551 551 form_result = _form.to_python(dict(self.request.POST))
552 552 inherit_perms = form_result['inherit_default_permissions']
553 553 c.user.inherit_default_permissions = inherit_perms
554 554 Session().add(c.user)
555 555
556 556 if not inherit_perms:
557 557 # only update the individual ones if we un check the flag
558 558 _form = UserPermissionsForm(
559 559 self.request.translate,
560 560 [x[0] for x in c.repo_create_choices],
561 561 [x[0] for x in c.repo_create_on_write_choices],
562 562 [x[0] for x in c.repo_group_create_choices],
563 563 [x[0] for x in c.user_group_create_choices],
564 564 [x[0] for x in c.fork_choices],
565 565 [x[0] for x in c.inherit_default_permission_choices])()
566 566
567 567 form_result = _form.to_python(dict(self.request.POST))
568 568 form_result.update({'perm_user_id': c.user.user_id})
569 569
570 570 PermissionModel().update_user_permissions(form_result)
571 571
572 572 # TODO(marcink): implement global permissions
573 573 # audit_log.store_web('user.edit.permissions')
574 574
575 575 Session().commit()
576 576 h.flash(_('User global permissions updated successfully'),
577 577 category='success')
578 578
579 579 except formencode.Invalid as errors:
580 580 data = render(
581 581 'rhodecode:templates/admin/users/user_edit.mako',
582 582 self._get_template_context(c), self.request)
583 583 html = formencode.htmlfill.render(
584 584 data,
585 585 defaults=errors.value,
586 586 errors=errors.error_dict or {},
587 587 prefix_error=False,
588 588 encoding="UTF-8",
589 589 force_defaults=False
590 590 )
591 591 return Response(html)
592 592 except Exception:
593 593 log.exception("Exception during permissions saving")
594 594 h.flash(_('An error occurred during permissions saving'),
595 595 category='error')
596 596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
597 597
598 598 @LoginRequired()
599 599 @HasPermissionAllDecorator('hg.admin')
600 600 @CSRFRequired()
601 601 @view_config(
602 602 route_name='user_force_password_reset', request_method='POST',
603 603 renderer='rhodecode:templates/admin/users/user_edit.mako')
604 604 def user_force_password_reset(self):
605 605 """
606 606 toggle reset password flag for this user
607 607 """
608 608 _ = self.request.translate
609 609 c = self.load_default_context()
610 610
611 611 user_id = self.db_user_id
612 612 c.user = self.db_user
613 613
614 614 try:
615 615 old_value = c.user.user_data.get('force_password_change')
616 616 c.user.update_userdata(force_password_change=not old_value)
617 617
618 618 if old_value:
619 619 msg = _('Force password change disabled for user')
620 620 audit_logger.store_web(
621 621 'user.edit.password_reset.disabled',
622 622 user=c.rhodecode_user)
623 623 else:
624 624 msg = _('Force password change enabled for user')
625 625 audit_logger.store_web(
626 626 'user.edit.password_reset.enabled',
627 627 user=c.rhodecode_user)
628 628
629 629 Session().commit()
630 630 h.flash(msg, category='success')
631 631 except Exception:
632 632 log.exception("Exception during password reset for user")
633 633 h.flash(_('An error occurred during password reset for user'),
634 634 category='error')
635 635
636 636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
637 637
638 638 @LoginRequired()
639 639 @HasPermissionAllDecorator('hg.admin')
640 640 @CSRFRequired()
641 641 @view_config(
642 642 route_name='user_create_personal_repo_group', request_method='POST',
643 643 renderer='rhodecode:templates/admin/users/user_edit.mako')
644 644 def user_create_personal_repo_group(self):
645 645 """
646 646 Create personal repository group for this user
647 647 """
648 648 from rhodecode.model.repo_group import RepoGroupModel
649 649
650 650 _ = self.request.translate
651 651 c = self.load_default_context()
652 652
653 653 user_id = self.db_user_id
654 654 c.user = self.db_user
655 655
656 656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
657 657 c.user.user_id)
658 658 if personal_repo_group:
659 659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
660 660
661 661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
662 662 c.user)
663 663 named_personal_group = RepoGroup.get_by_group_name(
664 664 personal_repo_group_name)
665 665 try:
666 666
667 667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
668 668 # migrate the same named group, and mark it as personal
669 669 named_personal_group.personal = True
670 670 Session().add(named_personal_group)
671 671 Session().commit()
672 672 msg = _('Linked repository group `%s` as personal' % (
673 673 personal_repo_group_name,))
674 674 h.flash(msg, category='success')
675 675 elif not named_personal_group:
676 676 RepoGroupModel().create_personal_repo_group(c.user)
677 677
678 678 msg = _('Created repository group `%s`' % (
679 679 personal_repo_group_name,))
680 680 h.flash(msg, category='success')
681 681 else:
682 682 msg = _('Repository group `%s` is already taken' % (
683 683 personal_repo_group_name,))
684 684 h.flash(msg, category='warning')
685 685 except Exception:
686 686 log.exception("Exception during repository group creation")
687 687 msg = _(
688 688 'An error occurred during repository group creation for user')
689 689 h.flash(msg, category='error')
690 690 Session().rollback()
691 691
692 692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
693 693
694 694 @LoginRequired()
695 695 @HasPermissionAllDecorator('hg.admin')
696 696 @view_config(
697 697 route_name='edit_user_auth_tokens', request_method='GET',
698 698 renderer='rhodecode:templates/admin/users/user_edit.mako')
699 699 def auth_tokens(self):
700 700 _ = self.request.translate
701 701 c = self.load_default_context()
702 702 c.user = self.db_user
703 703
704 704 c.active = 'auth_tokens'
705 705
706 706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
707 707 c.role_values = [
708 708 (x, AuthTokenModel.cls._get_role_name(x))
709 709 for x in AuthTokenModel.cls.ROLES]
710 710 c.role_options = [(c.role_values, _("Role"))]
711 711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
712 712 c.user.user_id, show_expired=True)
713 713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
714 714 return self._get_template_context(c)
715 715
716 716 def maybe_attach_token_scope(self, token):
717 717 # implemented in EE edition
718 718 pass
719 719
720 720 @LoginRequired()
721 721 @HasPermissionAllDecorator('hg.admin')
722 722 @CSRFRequired()
723 723 @view_config(
724 724 route_name='edit_user_auth_tokens_add', request_method='POST')
725 725 def auth_tokens_add(self):
726 726 _ = self.request.translate
727 727 c = self.load_default_context()
728 728
729 729 user_id = self.db_user_id
730 730 c.user = self.db_user
731 731
732 732 user_data = c.user.get_api_data()
733 733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
734 734 description = self.request.POST.get('description')
735 735 role = self.request.POST.get('role')
736 736
737 737 token = AuthTokenModel().create(
738 738 c.user.user_id, description, lifetime, role)
739 739 token_data = token.get_api_data()
740 740
741 741 self.maybe_attach_token_scope(token)
742 742 audit_logger.store_web(
743 743 'user.edit.token.add', action_data={
744 744 'data': {'token': token_data, 'user': user_data}},
745 745 user=self._rhodecode_user, )
746 746 Session().commit()
747 747
748 748 h.flash(_("Auth token successfully created"), category='success')
749 749 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
750 750
751 751 @LoginRequired()
752 752 @HasPermissionAllDecorator('hg.admin')
753 753 @CSRFRequired()
754 754 @view_config(
755 755 route_name='edit_user_auth_tokens_delete', request_method='POST')
756 756 def auth_tokens_delete(self):
757 757 _ = self.request.translate
758 758 c = self.load_default_context()
759 759
760 760 user_id = self.db_user_id
761 761 c.user = self.db_user
762 762
763 763 user_data = c.user.get_api_data()
764 764
765 765 del_auth_token = self.request.POST.get('del_auth_token')
766 766
767 767 if del_auth_token:
768 768 token = UserApiKeys.get_or_404(del_auth_token)
769 769 token_data = token.get_api_data()
770 770
771 771 AuthTokenModel().delete(del_auth_token, c.user.user_id)
772 772 audit_logger.store_web(
773 773 'user.edit.token.delete', action_data={
774 774 'data': {'token': token_data, 'user': user_data}},
775 775 user=self._rhodecode_user,)
776 776 Session().commit()
777 777 h.flash(_("Auth token successfully deleted"), category='success')
778 778
779 779 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
780 780
781 781 @LoginRequired()
782 782 @HasPermissionAllDecorator('hg.admin')
783 783 @view_config(
784 784 route_name='edit_user_ssh_keys', request_method='GET',
785 785 renderer='rhodecode:templates/admin/users/user_edit.mako')
786 786 def ssh_keys(self):
787 787 _ = self.request.translate
788 788 c = self.load_default_context()
789 789 c.user = self.db_user
790 790
791 791 c.active = 'ssh_keys'
792 792 c.default_key = self.request.GET.get('default_key')
793 793 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
794 794 return self._get_template_context(c)
795 795
796 796 @LoginRequired()
797 797 @HasPermissionAllDecorator('hg.admin')
798 798 @view_config(
799 799 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
800 800 renderer='rhodecode:templates/admin/users/user_edit.mako')
801 801 def ssh_keys_generate_keypair(self):
802 802 _ = self.request.translate
803 803 c = self.load_default_context()
804 804
805 805 c.user = self.db_user
806 806
807 807 c.active = 'ssh_keys_generate'
808 808 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
809 809 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
810 810
811 811 return self._get_template_context(c)
812 812
813 813 @LoginRequired()
814 814 @HasPermissionAllDecorator('hg.admin')
815 815 @CSRFRequired()
816 816 @view_config(
817 817 route_name='edit_user_ssh_keys_add', request_method='POST')
818 818 def ssh_keys_add(self):
819 819 _ = self.request.translate
820 820 c = self.load_default_context()
821 821
822 822 user_id = self.db_user_id
823 823 c.user = self.db_user
824 824
825 825 user_data = c.user.get_api_data()
826 826 key_data = self.request.POST.get('key_data')
827 827 description = self.request.POST.get('description')
828 828
829 829 fingerprint = 'unknown'
830 830 try:
831 831 if not key_data:
832 832 raise ValueError('Please add a valid public key')
833 833
834 834 key = SshKeyModel().parse_key(key_data.strip())
835 835 fingerprint = key.hash_md5()
836 836
837 837 ssh_key = SshKeyModel().create(
838 838 c.user.user_id, fingerprint, key.keydata, description)
839 839 ssh_key_data = ssh_key.get_api_data()
840 840
841 841 audit_logger.store_web(
842 842 'user.edit.ssh_key.add', action_data={
843 843 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
844 844 user=self._rhodecode_user, )
845 845 Session().commit()
846 846
847 847 # Trigger an event on change of keys.
848 848 trigger(SshKeyFileChangeEvent(), self.request.registry)
849 849
850 850 h.flash(_("Ssh Key successfully created"), category='success')
851 851
852 852 except IntegrityError:
853 853 log.exception("Exception during ssh key saving")
854 854 err = 'Such key with fingerprint `{}` already exists, ' \
855 855 'please use a different one'.format(fingerprint)
856 856 h.flash(_('An error occurred during ssh key saving: {}').format(err),
857 857 category='error')
858 858 except Exception as e:
859 859 log.exception("Exception during ssh key saving")
860 860 h.flash(_('An error occurred during ssh key saving: {}').format(e),
861 861 category='error')
862 862
863 863 return HTTPFound(
864 864 h.route_path('edit_user_ssh_keys', user_id=user_id))
865 865
866 866 @LoginRequired()
867 867 @HasPermissionAllDecorator('hg.admin')
868 868 @CSRFRequired()
869 869 @view_config(
870 870 route_name='edit_user_ssh_keys_delete', request_method='POST')
871 871 def ssh_keys_delete(self):
872 872 _ = self.request.translate
873 873 c = self.load_default_context()
874 874
875 875 user_id = self.db_user_id
876 876 c.user = self.db_user
877 877
878 878 user_data = c.user.get_api_data()
879 879
880 880 del_ssh_key = self.request.POST.get('del_ssh_key')
881 881
882 882 if del_ssh_key:
883 883 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
884 884 ssh_key_data = ssh_key.get_api_data()
885 885
886 886 SshKeyModel().delete(del_ssh_key, c.user.user_id)
887 887 audit_logger.store_web(
888 888 'user.edit.ssh_key.delete', action_data={
889 889 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
890 890 user=self._rhodecode_user,)
891 891 Session().commit()
892 892 # Trigger an event on change of keys.
893 893 trigger(SshKeyFileChangeEvent(), self.request.registry)
894 894 h.flash(_("Ssh key successfully deleted"), category='success')
895 895
896 896 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
897 897
898 898 @LoginRequired()
899 899 @HasPermissionAllDecorator('hg.admin')
900 900 @view_config(
901 901 route_name='edit_user_emails', request_method='GET',
902 902 renderer='rhodecode:templates/admin/users/user_edit.mako')
903 903 def emails(self):
904 904 _ = self.request.translate
905 905 c = self.load_default_context()
906 906 c.user = self.db_user
907 907
908 908 c.active = 'emails'
909 909 c.user_email_map = UserEmailMap.query() \
910 910 .filter(UserEmailMap.user == c.user).all()
911 911
912 912 return self._get_template_context(c)
913 913
914 914 @LoginRequired()
915 915 @HasPermissionAllDecorator('hg.admin')
916 916 @CSRFRequired()
917 917 @view_config(
918 918 route_name='edit_user_emails_add', request_method='POST')
919 919 def emails_add(self):
920 920 _ = self.request.translate
921 921 c = self.load_default_context()
922 922
923 923 user_id = self.db_user_id
924 924 c.user = self.db_user
925 925
926 926 email = self.request.POST.get('new_email')
927 927 user_data = c.user.get_api_data()
928 928 try:
929 929
930 930 form = UserExtraEmailForm(self.request.translate)()
931 931 data = form.to_python({'email': email})
932 932 email = data['email']
933 933
934 934 UserModel().add_extra_email(c.user.user_id, email)
935 935 audit_logger.store_web(
936 936 'user.edit.email.add',
937 937 action_data={'email': email, 'user': user_data},
938 938 user=self._rhodecode_user)
939 939 Session().commit()
940 940 h.flash(_("Added new email address `%s` for user account") % email,
941 941 category='success')
942 942 except formencode.Invalid as error:
943 943 h.flash(h.escape(error.error_dict['email']), category='error')
944 944 except IntegrityError:
945 945 log.warning("Email %s already exists", email)
946 946 h.flash(_('Email `{}` is already registered for another user.').format(email),
947 947 category='error')
948 948 except Exception:
949 949 log.exception("Exception during email saving")
950 950 h.flash(_('An error occurred during email saving'),
951 951 category='error')
952 952 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
953 953
954 954 @LoginRequired()
955 955 @HasPermissionAllDecorator('hg.admin')
956 956 @CSRFRequired()
957 957 @view_config(
958 958 route_name='edit_user_emails_delete', request_method='POST')
959 959 def emails_delete(self):
960 960 _ = self.request.translate
961 961 c = self.load_default_context()
962 962
963 963 user_id = self.db_user_id
964 964 c.user = self.db_user
965 965
966 966 email_id = self.request.POST.get('del_email_id')
967 967 user_model = UserModel()
968 968
969 969 email = UserEmailMap.query().get(email_id).email
970 970 user_data = c.user.get_api_data()
971 971 user_model.delete_extra_email(c.user.user_id, email_id)
972 972 audit_logger.store_web(
973 973 'user.edit.email.delete',
974 974 action_data={'email': email, 'user': user_data},
975 975 user=self._rhodecode_user)
976 976 Session().commit()
977 977 h.flash(_("Removed email address from user account"),
978 978 category='success')
979 979 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
980 980
981 981 @LoginRequired()
982 982 @HasPermissionAllDecorator('hg.admin')
983 983 @view_config(
984 984 route_name='edit_user_ips', request_method='GET',
985 985 renderer='rhodecode:templates/admin/users/user_edit.mako')
986 986 def ips(self):
987 987 _ = self.request.translate
988 988 c = self.load_default_context()
989 989 c.user = self.db_user
990 990
991 991 c.active = 'ips'
992 992 c.user_ip_map = UserIpMap.query() \
993 993 .filter(UserIpMap.user == c.user).all()
994 994
995 995 c.inherit_default_ips = c.user.inherit_default_permissions
996 996 c.default_user_ip_map = UserIpMap.query() \
997 997 .filter(UserIpMap.user == User.get_default_user()).all()
998 998
999 999 return self._get_template_context(c)
1000 1000
1001 1001 @LoginRequired()
1002 1002 @HasPermissionAllDecorator('hg.admin')
1003 1003 @CSRFRequired()
1004 1004 @view_config(
1005 1005 route_name='edit_user_ips_add', request_method='POST')
1006 1006 # NOTE(marcink): this view is allowed for default users, as we can
1007 1007 # edit their IP white list
1008 1008 def ips_add(self):
1009 1009 _ = self.request.translate
1010 1010 c = self.load_default_context()
1011 1011
1012 1012 user_id = self.db_user_id
1013 1013 c.user = self.db_user
1014 1014
1015 1015 user_model = UserModel()
1016 1016 desc = self.request.POST.get('description')
1017 1017 try:
1018 1018 ip_list = user_model.parse_ip_range(
1019 1019 self.request.POST.get('new_ip'))
1020 1020 except Exception as e:
1021 1021 ip_list = []
1022 1022 log.exception("Exception during ip saving")
1023 1023 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1024 1024 category='error')
1025 1025 added = []
1026 1026 user_data = c.user.get_api_data()
1027 1027 for ip in ip_list:
1028 1028 try:
1029 1029 form = UserExtraIpForm(self.request.translate)()
1030 1030 data = form.to_python({'ip': ip})
1031 1031 ip = data['ip']
1032 1032
1033 1033 user_model.add_extra_ip(c.user.user_id, ip, desc)
1034 1034 audit_logger.store_web(
1035 1035 'user.edit.ip.add',
1036 1036 action_data={'ip': ip, 'user': user_data},
1037 1037 user=self._rhodecode_user)
1038 1038 Session().commit()
1039 1039 added.append(ip)
1040 1040 except formencode.Invalid as error:
1041 1041 msg = error.error_dict['ip']
1042 1042 h.flash(msg, category='error')
1043 1043 except Exception:
1044 1044 log.exception("Exception during ip saving")
1045 1045 h.flash(_('An error occurred during ip saving'),
1046 1046 category='error')
1047 1047 if added:
1048 1048 h.flash(
1049 1049 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1050 1050 category='success')
1051 1051 if 'default_user' in self.request.POST:
1052 1052 # case for editing global IP list we do it for 'DEFAULT' user
1053 1053 raise HTTPFound(h.route_path('admin_permissions_ips'))
1054 1054 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1055 1055
1056 1056 @LoginRequired()
1057 1057 @HasPermissionAllDecorator('hg.admin')
1058 1058 @CSRFRequired()
1059 1059 @view_config(
1060 1060 route_name='edit_user_ips_delete', request_method='POST')
1061 1061 # NOTE(marcink): this view is allowed for default users, as we can
1062 1062 # edit their IP white list
1063 1063 def ips_delete(self):
1064 1064 _ = self.request.translate
1065 1065 c = self.load_default_context()
1066 1066
1067 1067 user_id = self.db_user_id
1068 1068 c.user = self.db_user
1069 1069
1070 1070 ip_id = self.request.POST.get('del_ip_id')
1071 1071 user_model = UserModel()
1072 1072 user_data = c.user.get_api_data()
1073 1073 ip = UserIpMap.query().get(ip_id).ip_addr
1074 1074 user_model.delete_extra_ip(c.user.user_id, ip_id)
1075 1075 audit_logger.store_web(
1076 1076 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1077 1077 user=self._rhodecode_user)
1078 1078 Session().commit()
1079 1079 h.flash(_("Removed ip address from user whitelist"), category='success')
1080 1080
1081 1081 if 'default_user' in self.request.POST:
1082 1082 # case for editing global IP list we do it for 'DEFAULT' user
1083 1083 raise HTTPFound(h.route_path('admin_permissions_ips'))
1084 1084 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1085 1085
1086 1086 @LoginRequired()
1087 1087 @HasPermissionAllDecorator('hg.admin')
1088 1088 @view_config(
1089 1089 route_name='edit_user_groups_management', request_method='GET',
1090 1090 renderer='rhodecode:templates/admin/users/user_edit.mako')
1091 1091 def groups_management(self):
1092 1092 c = self.load_default_context()
1093 1093 c.user = self.db_user
1094 1094 c.data = c.user.group_member
1095 1095
1096 1096 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1097 1097 for group in c.user.group_member]
1098 1098 c.groups = json.dumps(groups)
1099 1099 c.active = 'groups'
1100 1100
1101 1101 return self._get_template_context(c)
1102 1102
1103 1103 @LoginRequired()
1104 1104 @HasPermissionAllDecorator('hg.admin')
1105 1105 @CSRFRequired()
1106 1106 @view_config(
1107 1107 route_name='edit_user_groups_management_updates', request_method='POST')
1108 1108 def groups_management_updates(self):
1109 1109 _ = self.request.translate
1110 1110 c = self.load_default_context()
1111 1111
1112 1112 user_id = self.db_user_id
1113 1113 c.user = self.db_user
1114 1114
1115 1115 user_groups = set(self.request.POST.getall('users_group_id'))
1116 1116 user_groups_objects = []
1117 1117
1118 1118 for ugid in user_groups:
1119 1119 user_groups_objects.append(
1120 1120 UserGroupModel().get_group(safe_int(ugid)))
1121 1121 user_group_model = UserGroupModel()
1122 1122 added_to_groups, removed_from_groups = \
1123 1123 user_group_model.change_groups(c.user, user_groups_objects)
1124 1124
1125 1125 user_data = c.user.get_api_data()
1126 1126 for user_group_id in added_to_groups:
1127 1127 user_group = UserGroup.get(user_group_id)
1128 1128 old_values = user_group.get_api_data()
1129 1129 audit_logger.store_web(
1130 1130 'user_group.edit.member.add',
1131 1131 action_data={'user': user_data, 'old_data': old_values},
1132 1132 user=self._rhodecode_user)
1133 1133
1134 1134 for user_group_id in removed_from_groups:
1135 1135 user_group = UserGroup.get(user_group_id)
1136 1136 old_values = user_group.get_api_data()
1137 1137 audit_logger.store_web(
1138 1138 'user_group.edit.member.delete',
1139 1139 action_data={'user': user_data, 'old_data': old_values},
1140 1140 user=self._rhodecode_user)
1141 1141
1142 1142 Session().commit()
1143 1143 c.active = 'user_groups_management'
1144 1144 h.flash(_("Groups successfully changed"), category='success')
1145 1145
1146 1146 return HTTPFound(h.route_path(
1147 1147 'edit_user_groups_management', user_id=user_id))
1148 1148
1149 1149 @LoginRequired()
1150 1150 @HasPermissionAllDecorator('hg.admin')
1151 1151 @view_config(
1152 1152 route_name='edit_user_audit_logs', request_method='GET',
1153 1153 renderer='rhodecode:templates/admin/users/user_edit.mako')
1154 1154 def user_audit_logs(self):
1155 1155 _ = self.request.translate
1156 1156 c = self.load_default_context()
1157 1157 c.user = self.db_user
1158 1158
1159 1159 c.active = 'audit'
1160 1160
1161 1161 p = safe_int(self.request.GET.get('page', 1), 1)
1162 1162
1163 1163 filter_term = self.request.GET.get('filter')
1164 1164 user_log = UserModel().get_user_log(c.user, filter_term)
1165 1165
1166 1166 def url_generator(**kw):
1167 1167 if filter_term:
1168 1168 kw['filter'] = filter_term
1169 1169 return self.request.current_route_path(_query=kw)
1170 1170
1171 1171 c.audit_logs = h.Page(
1172 1172 user_log, page=p, items_per_page=10, url=url_generator)
1173 1173 c.filter_term = filter_term
1174 1174 return self._get_template_context(c)
1175 1175
1176 1176 @LoginRequired()
1177 1177 @HasPermissionAllDecorator('hg.admin')
1178 1178 @view_config(
1179 1179 route_name='edit_user_perms_summary', request_method='GET',
1180 1180 renderer='rhodecode:templates/admin/users/user_edit.mako')
1181 1181 def user_perms_summary(self):
1182 1182 _ = self.request.translate
1183 1183 c = self.load_default_context()
1184 1184 c.user = self.db_user
1185 1185
1186 1186 c.active = 'perms_summary'
1187 1187 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1188 1188
1189 1189 return self._get_template_context(c)
1190 1190
1191 1191 @LoginRequired()
1192 1192 @HasPermissionAllDecorator('hg.admin')
1193 1193 @view_config(
1194 1194 route_name='edit_user_perms_summary_json', request_method='GET',
1195 1195 renderer='json_ext')
1196 1196 def user_perms_summary_json(self):
1197 1197 self.load_default_context()
1198 1198 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1199 1199
1200 1200 return perm_user.permissions
1201
1202 def _get_user_cache_keys(self, cache_namespace_uid, keys):
1203 user_keys = []
1204 for k in sorted(keys):
1205 if k.startswith(cache_namespace_uid):
1206 user_keys.append(k)
1207 return user_keys
1208
1209 @LoginRequired()
1210 @HasPermissionAllDecorator('hg.admin')
1211 @view_config(
1212 route_name='edit_user_caches', request_method='GET',
1213 renderer='rhodecode:templates/admin/users/user_edit.mako')
1214 def user_caches(self):
1215 _ = self.request.translate
1216 c = self.load_default_context()
1217 c.user = self.db_user
1218
1219 c.active = 'caches'
1220 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1221
1222 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1223 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1224 c.backend = c.region.backend
1225 c.user_keys = self._get_user_cache_keys(
1226 cache_namespace_uid, c.region.backend.list_keys())
1227
1228 return self._get_template_context(c)
1229
1230 @LoginRequired()
1231 @HasPermissionAllDecorator('hg.admin')
1232 @CSRFRequired()
1233 @view_config(
1234 route_name='edit_user_caches_update', request_method='POST')
1235 def user_caches_update(self):
1236 _ = self.request.translate
1237 c = self.load_default_context()
1238 c.user = self.db_user
1239
1240 c.active = 'caches'
1241 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1242
1243 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1244 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1245
1246 c.user_keys = self._get_user_cache_keys(
1247 cache_namespace_uid, c.region.backend.list_keys())
1248 for k in c.user_keys:
1249 c.region.delete(k)
1250
1251 h.flash(_("Deleted {} cache keys").format(len(c.user_keys)), category='success')
1252
1253 return HTTPFound(h.route_path(
1254 'edit_user_caches', user_id=c.user.user_id))
@@ -1,783 +1,757 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 Authentication modules
23 23 """
24 24 import socket
25 25 import string
26 26 import colander
27 27 import copy
28 28 import logging
29 29 import time
30 30 import traceback
31 31 import warnings
32 32 import functools
33 33
34 34 from pyramid.threadlocal import get_current_registry
35 35
36 36 from rhodecode.authentication.interface import IAuthnPluginRegistry
37 37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.lib import caches
38 from rhodecode.lib import caches, rc_cache
39 39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
40 40 from rhodecode.lib.utils2 import safe_int, safe_str
41 41 from rhodecode.lib.exceptions import LdapConnectionError
42 42 from rhodecode.model.db import User
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.settings import SettingsModel
45 45 from rhodecode.model.user import UserModel
46 46 from rhodecode.model.user_group import UserGroupModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51 # auth types that authenticate() function can receive
52 52 VCS_TYPE = 'vcs'
53 53 HTTP_TYPE = 'http'
54 54
55 55
56 56 class hybrid_property(object):
57 57 """
58 58 a property decorator that works both for instance and class
59 59 """
60 60 def __init__(self, fget, fset=None, fdel=None, expr=None):
61 61 self.fget = fget
62 62 self.fset = fset
63 63 self.fdel = fdel
64 64 self.expr = expr or fget
65 65 functools.update_wrapper(self, fget)
66 66
67 67 def __get__(self, instance, owner):
68 68 if instance is None:
69 69 return self.expr(owner)
70 70 else:
71 71 return self.fget(instance)
72 72
73 73 def __set__(self, instance, value):
74 74 self.fset(instance, value)
75 75
76 76 def __delete__(self, instance):
77 77 self.fdel(instance)
78 78
79 79
80 80 class LazyFormencode(object):
81 81 def __init__(self, formencode_obj, *args, **kwargs):
82 82 self.formencode_obj = formencode_obj
83 83 self.args = args
84 84 self.kwargs = kwargs
85 85
86 86 def __call__(self, *args, **kwargs):
87 87 from inspect import isfunction
88 88 formencode_obj = self.formencode_obj
89 89 if isfunction(formencode_obj):
90 90 # case we wrap validators into functions
91 91 formencode_obj = self.formencode_obj(*args, **kwargs)
92 92 return formencode_obj(*self.args, **self.kwargs)
93 93
94 94
95 95 class RhodeCodeAuthPluginBase(object):
96 96 # cache the authentication request for N amount of seconds. Some kind
97 97 # of authentication methods are very heavy and it's very efficient to cache
98 98 # the result of a call. If it's set to None (default) cache is off
99 99 AUTH_CACHE_TTL = None
100 100 AUTH_CACHE = {}
101 101
102 102 auth_func_attrs = {
103 103 "username": "unique username",
104 104 "firstname": "first name",
105 105 "lastname": "last name",
106 106 "email": "email address",
107 107 "groups": '["list", "of", "groups"]',
108 108 "user_group_sync":
109 109 'True|False defines if returned user groups should be synced',
110 110 "extern_name": "name in external source of record",
111 111 "extern_type": "type of external source of record",
112 112 "admin": 'True|False defines if user should be RhodeCode super admin',
113 113 "active":
114 114 'True|False defines active state of user internally for RhodeCode',
115 115 "active_from_extern":
116 116 "True|False\None, active state from the external auth, "
117 117 "None means use definition from RhodeCode extern_type active value"
118 118
119 119 }
120 120 # set on authenticate() method and via set_auth_type func.
121 121 auth_type = None
122 122
123 123 # set on authenticate() method and via set_calling_scope_repo, this is a
124 124 # calling scope repository when doing authentication most likely on VCS
125 125 # operations
126 126 acl_repo_name = None
127 127
128 128 # List of setting names to store encrypted. Plugins may override this list
129 129 # to store settings encrypted.
130 130 _settings_encrypted = []
131 131
132 132 # Mapping of python to DB settings model types. Plugins may override or
133 133 # extend this mapping.
134 134 _settings_type_map = {
135 135 colander.String: 'unicode',
136 136 colander.Integer: 'int',
137 137 colander.Boolean: 'bool',
138 138 colander.List: 'list',
139 139 }
140 140
141 141 # list of keys in settings that are unsafe to be logged, should be passwords
142 142 # or other crucial credentials
143 143 _settings_unsafe_keys = []
144 144
145 145 def __init__(self, plugin_id):
146 146 self._plugin_id = plugin_id
147 147
148 148 def __str__(self):
149 149 return self.get_id()
150 150
151 151 def _get_setting_full_name(self, name):
152 152 """
153 153 Return the full setting name used for storing values in the database.
154 154 """
155 155 # TODO: johbo: Using the name here is problematic. It would be good to
156 156 # introduce either new models in the database to hold Plugin and
157 157 # PluginSetting or to use the plugin id here.
158 158 return 'auth_{}_{}'.format(self.name, name)
159 159
160 160 def _get_setting_type(self, name):
161 161 """
162 162 Return the type of a setting. This type is defined by the SettingsModel
163 163 and determines how the setting is stored in DB. Optionally the suffix
164 164 `.encrypted` is appended to instruct SettingsModel to store it
165 165 encrypted.
166 166 """
167 167 schema_node = self.get_settings_schema().get(name)
168 168 db_type = self._settings_type_map.get(
169 169 type(schema_node.typ), 'unicode')
170 170 if name in self._settings_encrypted:
171 171 db_type = '{}.encrypted'.format(db_type)
172 172 return db_type
173 173
174 174 def is_enabled(self):
175 175 """
176 176 Returns true if this plugin is enabled. An enabled plugin can be
177 177 configured in the admin interface but it is not consulted during
178 178 authentication.
179 179 """
180 180 auth_plugins = SettingsModel().get_auth_plugins()
181 181 return self.get_id() in auth_plugins
182 182
183 183 def is_active(self, plugin_cached_settings=None):
184 184 """
185 185 Returns true if the plugin is activated. An activated plugin is
186 186 consulted during authentication, assumed it is also enabled.
187 187 """
188 188 return self.get_setting_by_name(
189 189 'enabled', plugin_cached_settings=plugin_cached_settings)
190 190
191 191 def get_id(self):
192 192 """
193 193 Returns the plugin id.
194 194 """
195 195 return self._plugin_id
196 196
197 197 def get_display_name(self):
198 198 """
199 199 Returns a translation string for displaying purposes.
200 200 """
201 201 raise NotImplementedError('Not implemented in base class')
202 202
203 203 def get_settings_schema(self):
204 204 """
205 205 Returns a colander schema, representing the plugin settings.
206 206 """
207 207 return AuthnPluginSettingsSchemaBase()
208 208
209 209 def get_settings(self):
210 210 """
211 211 Returns the plugin settings as dictionary.
212 212 """
213 213 settings = {}
214 214 raw_settings = SettingsModel().get_all_settings()
215 215 for node in self.get_settings_schema():
216 216 settings[node.name] = self.get_setting_by_name(
217 217 node.name, plugin_cached_settings=raw_settings)
218 218 return settings
219 219
220 220 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
221 221 """
222 222 Returns a plugin setting by name.
223 223 """
224 224 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
225 225 if plugin_cached_settings:
226 226 plugin_settings = plugin_cached_settings
227 227 else:
228 228 plugin_settings = SettingsModel().get_all_settings()
229 229
230 230 if full_name in plugin_settings:
231 231 return plugin_settings[full_name]
232 232 else:
233 233 return default
234 234
235 235 def create_or_update_setting(self, name, value):
236 236 """
237 237 Create or update a setting for this plugin in the persistent storage.
238 238 """
239 239 full_name = self._get_setting_full_name(name)
240 240 type_ = self._get_setting_type(name)
241 241 db_setting = SettingsModel().create_or_update_setting(
242 242 full_name, value, type_)
243 243 return db_setting.app_settings_value
244 244
245 245 def log_safe_settings(self, settings):
246 246 """
247 247 returns a log safe representation of settings, without any secrets
248 248 """
249 249 settings_copy = copy.deepcopy(settings)
250 250 for k in self._settings_unsafe_keys:
251 251 if k in settings_copy:
252 252 del settings_copy[k]
253 253 return settings_copy
254 254
255 255 @hybrid_property
256 256 def name(self):
257 257 """
258 258 Returns the name of this authentication plugin.
259 259
260 260 :returns: string
261 261 """
262 262 raise NotImplementedError("Not implemented in base class")
263 263
264 264 def get_url_slug(self):
265 265 """
266 266 Returns a slug which should be used when constructing URLs which refer
267 267 to this plugin. By default it returns the plugin name. If the name is
268 268 not suitable for using it in an URL the plugin should override this
269 269 method.
270 270 """
271 271 return self.name
272 272
273 273 @property
274 274 def is_headers_auth(self):
275 275 """
276 276 Returns True if this authentication plugin uses HTTP headers as
277 277 authentication method.
278 278 """
279 279 return False
280 280
281 281 @hybrid_property
282 282 def is_container_auth(self):
283 283 """
284 284 Deprecated method that indicates if this authentication plugin uses
285 285 HTTP headers as authentication method.
286 286 """
287 287 warnings.warn(
288 288 'Use is_headers_auth instead.', category=DeprecationWarning)
289 289 return self.is_headers_auth
290 290
291 291 @hybrid_property
292 292 def allows_creating_users(self):
293 293 """
294 294 Defines if Plugin allows users to be created on-the-fly when
295 295 authentication is called. Controls how external plugins should behave
296 296 in terms if they are allowed to create new users, or not. Base plugins
297 297 should not be allowed to, but External ones should be !
298 298
299 299 :return: bool
300 300 """
301 301 return False
302 302
303 303 def set_auth_type(self, auth_type):
304 304 self.auth_type = auth_type
305 305
306 306 def set_calling_scope_repo(self, acl_repo_name):
307 307 self.acl_repo_name = acl_repo_name
308 308
309 309 def allows_authentication_from(
310 310 self, user, allows_non_existing_user=True,
311 311 allowed_auth_plugins=None, allowed_auth_sources=None):
312 312 """
313 313 Checks if this authentication module should accept a request for
314 314 the current user.
315 315
316 316 :param user: user object fetched using plugin's get_user() method.
317 317 :param allows_non_existing_user: if True, don't allow the
318 318 user to be empty, meaning not existing in our database
319 319 :param allowed_auth_plugins: if provided, users extern_type will be
320 320 checked against a list of provided extern types, which are plugin
321 321 auth_names in the end
322 322 :param allowed_auth_sources: authentication type allowed,
323 323 `http` or `vcs` default is both.
324 324 defines if plugin will accept only http authentication vcs
325 325 authentication(git/hg) or both
326 326 :returns: boolean
327 327 """
328 328 if not user and not allows_non_existing_user:
329 329 log.debug('User is empty but plugin does not allow empty users,'
330 330 'not allowed to authenticate')
331 331 return False
332 332
333 333 expected_auth_plugins = allowed_auth_plugins or [self.name]
334 334 if user and (user.extern_type and
335 335 user.extern_type not in expected_auth_plugins):
336 336 log.debug(
337 337 'User `%s` is bound to `%s` auth type. Plugin allows only '
338 338 '%s, skipping', user, user.extern_type, expected_auth_plugins)
339 339
340 340 return False
341 341
342 342 # by default accept both
343 343 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
344 344 if self.auth_type not in expected_auth_from:
345 345 log.debug('Current auth source is %s but plugin only allows %s',
346 346 self.auth_type, expected_auth_from)
347 347 return False
348 348
349 349 return True
350 350
351 351 def get_user(self, username=None, **kwargs):
352 352 """
353 353 Helper method for user fetching in plugins, by default it's using
354 354 simple fetch by username, but this method can be custimized in plugins
355 355 eg. headers auth plugin to fetch user by environ params
356 356
357 357 :param username: username if given to fetch from database
358 358 :param kwargs: extra arguments needed for user fetching.
359 359 """
360 360 user = None
361 361 log.debug(
362 362 'Trying to fetch user `%s` from RhodeCode database', username)
363 363 if username:
364 364 user = User.get_by_username(username)
365 365 if not user:
366 366 log.debug('User not found, fallback to fetch user in '
367 367 'case insensitive mode')
368 368 user = User.get_by_username(username, case_insensitive=True)
369 369 else:
370 370 log.debug('provided username:`%s` is empty skipping...', username)
371 371 if not user:
372 372 log.debug('User `%s` not found in database', username)
373 373 else:
374 374 log.debug('Got DB user:%s', user)
375 375 return user
376 376
377 377 def user_activation_state(self):
378 378 """
379 379 Defines user activation state when creating new users
380 380
381 381 :returns: boolean
382 382 """
383 383 raise NotImplementedError("Not implemented in base class")
384 384
385 385 def auth(self, userobj, username, passwd, settings, **kwargs):
386 386 """
387 387 Given a user object (which may be null), username, a plaintext
388 388 password, and a settings object (containing all the keys needed as
389 389 listed in settings()), authenticate this user's login attempt.
390 390
391 391 Return None on failure. On success, return a dictionary of the form:
392 392
393 393 see: RhodeCodeAuthPluginBase.auth_func_attrs
394 394 This is later validated for correctness
395 395 """
396 396 raise NotImplementedError("not implemented in base class")
397 397
398 398 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
399 399 """
400 400 Wrapper to call self.auth() that validates call on it
401 401
402 402 :param userobj: userobj
403 403 :param username: username
404 404 :param passwd: plaintext password
405 405 :param settings: plugin settings
406 406 """
407 407 auth = self.auth(userobj, username, passwd, settings, **kwargs)
408 408 if auth:
409 409 auth['_plugin'] = self.name
410 410 auth['_ttl_cache'] = self.get_ttl_cache(settings)
411 411 # check if hash should be migrated ?
412 412 new_hash = auth.get('_hash_migrate')
413 413 if new_hash:
414 414 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
415 415 if 'user_group_sync' not in auth:
416 416 auth['user_group_sync'] = False
417 417 return self._validate_auth_return(auth)
418 418 return auth
419 419
420 420 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
421 421 new_hash_cypher = _RhodeCodeCryptoBCrypt()
422 422 # extra checks, so make sure new hash is correct.
423 423 password_encoded = safe_str(password)
424 424 if new_hash and new_hash_cypher.hash_check(
425 425 password_encoded, new_hash):
426 426 cur_user = User.get_by_username(username)
427 427 cur_user.password = new_hash
428 428 Session().add(cur_user)
429 429 Session().flush()
430 430 log.info('Migrated user %s hash to bcrypt', cur_user)
431 431
432 432 def _validate_auth_return(self, ret):
433 433 if not isinstance(ret, dict):
434 434 raise Exception('returned value from auth must be a dict')
435 435 for k in self.auth_func_attrs:
436 436 if k not in ret:
437 437 raise Exception('Missing %s attribute from returned data' % k)
438 438 return ret
439 439
440 440 def get_ttl_cache(self, settings=None):
441 441 plugin_settings = settings or self.get_settings()
442 442 cache_ttl = 0
443 443
444 444 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
445 445 # plugin cache set inside is more important than the settings value
446 446 cache_ttl = self.AUTH_CACHE_TTL
447 447 elif plugin_settings.get('cache_ttl'):
448 448 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
449 449
450 450 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
451 451 return plugin_cache_active, cache_ttl
452 452
453 453
454 454 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
455 455
456 456 @hybrid_property
457 457 def allows_creating_users(self):
458 458 return True
459 459
460 460 def use_fake_password(self):
461 461 """
462 462 Return a boolean that indicates whether or not we should set the user's
463 463 password to a random value when it is authenticated by this plugin.
464 464 If your plugin provides authentication, then you will generally
465 465 want this.
466 466
467 467 :returns: boolean
468 468 """
469 469 raise NotImplementedError("Not implemented in base class")
470 470
471 471 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
472 472 # at this point _authenticate calls plugin's `auth()` function
473 473 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
474 474 userobj, username, passwd, settings, **kwargs)
475 475
476 476 if auth:
477 477 # maybe plugin will clean the username ?
478 478 # we should use the return value
479 479 username = auth['username']
480 480
481 481 # if external source tells us that user is not active, we should
482 482 # skip rest of the process. This can prevent from creating users in
483 483 # RhodeCode when using external authentication, but if it's
484 484 # inactive user we shouldn't create that user anyway
485 485 if auth['active_from_extern'] is False:
486 486 log.warning(
487 487 "User %s authenticated against %s, but is inactive",
488 488 username, self.__module__)
489 489 return None
490 490
491 491 cur_user = User.get_by_username(username, case_insensitive=True)
492 492 is_user_existing = cur_user is not None
493 493
494 494 if is_user_existing:
495 495 log.debug('Syncing user `%s` from '
496 496 '`%s` plugin', username, self.name)
497 497 else:
498 498 log.debug('Creating non existing user `%s` from '
499 499 '`%s` plugin', username, self.name)
500 500
501 501 if self.allows_creating_users:
502 502 log.debug('Plugin `%s` allows to '
503 503 'create new users', self.name)
504 504 else:
505 505 log.debug('Plugin `%s` does not allow to '
506 506 'create new users', self.name)
507 507
508 508 user_parameters = {
509 509 'username': username,
510 510 'email': auth["email"],
511 511 'firstname': auth["firstname"],
512 512 'lastname': auth["lastname"],
513 513 'active': auth["active"],
514 514 'admin': auth["admin"],
515 515 'extern_name': auth["extern_name"],
516 516 'extern_type': self.name,
517 517 'plugin': self,
518 518 'allow_to_create_user': self.allows_creating_users,
519 519 }
520 520
521 521 if not is_user_existing:
522 522 if self.use_fake_password():
523 523 # Randomize the PW because we don't need it, but don't want
524 524 # them blank either
525 525 passwd = PasswordGenerator().gen_password(length=16)
526 526 user_parameters['password'] = passwd
527 527 else:
528 528 # Since the password is required by create_or_update method of
529 529 # UserModel, we need to set it explicitly.
530 530 # The create_or_update method is smart and recognises the
531 531 # password hashes as well.
532 532 user_parameters['password'] = cur_user.password
533 533
534 534 # we either create or update users, we also pass the flag
535 535 # that controls if this method can actually do that.
536 536 # raises NotAllowedToCreateUserError if it cannot, and we try to.
537 537 user = UserModel().create_or_update(**user_parameters)
538 538 Session().flush()
539 539 # enforce user is just in given groups, all of them has to be ones
540 540 # created from plugins. We store this info in _group_data JSON
541 541 # field
542 542
543 543 if auth['user_group_sync']:
544 544 try:
545 545 groups = auth['groups'] or []
546 546 log.debug(
547 547 'Performing user_group sync based on set `%s` '
548 548 'returned by `%s` plugin', groups, self.name)
549 549 UserGroupModel().enforce_groups(user, groups, self.name)
550 550 except Exception:
551 551 # for any reason group syncing fails, we should
552 552 # proceed with login
553 553 log.error(traceback.format_exc())
554 554
555 555 Session().commit()
556 556 return auth
557 557
558 558
559 559 class AuthLdapBase(object):
560 560
561 561 @classmethod
562 562 def _build_servers(cls, ldap_server_type, ldap_server, port):
563 563 def host_resolver(host, port, full_resolve=True):
564 564 """
565 565 Main work for this function is to prevent ldap connection issues,
566 566 and detect them early using a "greenified" sockets
567 567 """
568 568 host = host.strip()
569 569 if not full_resolve:
570 570 return '{}:{}'.format(host, port)
571 571
572 572 log.debug('LDAP: Resolving IP for LDAP host %s', host)
573 573 try:
574 574 ip = socket.gethostbyname(host)
575 575 log.debug('Got LDAP server %s ip %s', host, ip)
576 576 except Exception:
577 577 raise LdapConnectionError(
578 578 'Failed to resolve host: `{}`'.format(host))
579 579
580 580 log.debug('LDAP: Checking if IP %s is accessible', ip)
581 581 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
582 582 try:
583 583 s.connect((ip, int(port)))
584 584 s.shutdown(socket.SHUT_RD)
585 585 except Exception:
586 586 raise LdapConnectionError(
587 587 'Failed to connect to host: `{}:{}`'.format(host, port))
588 588
589 589 return '{}:{}'.format(host, port)
590 590
591 591 if len(ldap_server) == 1:
592 592 # in case of single server use resolver to detect potential
593 593 # connection issues
594 594 full_resolve = True
595 595 else:
596 596 full_resolve = False
597 597
598 598 return ', '.join(
599 599 ["{}://{}".format(
600 600 ldap_server_type,
601 601 host_resolver(host, port, full_resolve=full_resolve))
602 602 for host in ldap_server])
603 603
604 604 @classmethod
605 605 def _get_server_list(cls, servers):
606 606 return map(string.strip, servers.split(','))
607 607
608 608 @classmethod
609 609 def get_uid(cls, username, server_addresses):
610 610 uid = username
611 611 for server_addr in server_addresses:
612 612 uid = chop_at(username, "@%s" % server_addr)
613 613 return uid
614 614
615 615
616 616 def loadplugin(plugin_id):
617 617 """
618 618 Loads and returns an instantiated authentication plugin.
619 619 Returns the RhodeCodeAuthPluginBase subclass on success,
620 620 or None on failure.
621 621 """
622 622 # TODO: Disusing pyramids thread locals to retrieve the registry.
623 623 authn_registry = get_authn_registry()
624 624 plugin = authn_registry.get_plugin(plugin_id)
625 625 if plugin is None:
626 626 log.error('Authentication plugin not found: "%s"', plugin_id)
627 627 return plugin
628 628
629 629
630 630 def get_authn_registry(registry=None):
631 631 registry = registry or get_current_registry()
632 632 authn_registry = registry.getUtility(IAuthnPluginRegistry)
633 633 return authn_registry
634 634
635 635
636 def get_auth_cache_manager(custom_ttl=None, suffix=None):
637 cache_name = 'rhodecode.authentication'
638 if suffix:
639 cache_name = 'rhodecode.authentication.{}'.format(suffix)
640 return caches.get_cache_manager(
641 'auth_plugins', cache_name, custom_ttl)
642
643
644 def get_perms_cache_manager(custom_ttl=None, suffix=None):
645 cache_name = 'rhodecode.permissions'
646 if suffix:
647 cache_name = 'rhodecode.permissions.{}'.format(suffix)
648 return caches.get_cache_manager(
649 'auth_plugins', cache_name, custom_ttl)
650
651
652 636 def authenticate(username, password, environ=None, auth_type=None,
653 637 skip_missing=False, registry=None, acl_repo_name=None):
654 638 """
655 639 Authentication function used for access control,
656 640 It tries to authenticate based on enabled authentication modules.
657 641
658 642 :param username: username can be empty for headers auth
659 643 :param password: password can be empty for headers auth
660 644 :param environ: environ headers passed for headers auth
661 645 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
662 646 :param skip_missing: ignores plugins that are in db but not in environment
663 647 :returns: None if auth failed, plugin_user dict if auth is correct
664 648 """
665 649 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
666 650 raise ValueError('auth type must be on of http, vcs got "%s" instead'
667 651 % auth_type)
668 652 headers_only = environ and not (username and password)
669 653
670 654 authn_registry = get_authn_registry(registry)
671 655 plugins_to_check = authn_registry.get_plugins_for_authentication()
672 656 log.debug('Starting ordered authentication chain using %s plugins',
673 657 [x.name for x in plugins_to_check])
674 658 for plugin in plugins_to_check:
675 659 plugin.set_auth_type(auth_type)
676 660 plugin.set_calling_scope_repo(acl_repo_name)
677 661
678 662 if headers_only and not plugin.is_headers_auth:
679 663 log.debug('Auth type is for headers only and plugin `%s` is not '
680 664 'headers plugin, skipping...', plugin.get_id())
681 665 continue
682 666
683 667 log.debug('Trying authentication using ** %s **', plugin.get_id())
684 668
685 669 # load plugin settings from RhodeCode database
686 670 plugin_settings = plugin.get_settings()
687 671 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
688 672 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
689 673
690 674 # use plugin's method of user extraction.
691 675 user = plugin.get_user(username, environ=environ,
692 676 settings=plugin_settings)
693 677 display_user = user.username if user else username
694 678 log.debug(
695 679 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
696 680
697 681 if not plugin.allows_authentication_from(user):
698 682 log.debug('Plugin %s does not accept user `%s` for authentication',
699 683 plugin.get_id(), display_user)
700 684 continue
701 685 else:
702 686 log.debug('Plugin %s accepted user `%s` for authentication',
703 687 plugin.get_id(), display_user)
704 688
705 689 log.info('Authenticating user `%s` using %s plugin',
706 690 display_user, plugin.get_id())
707 691
708 692 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
709 693
710 # get instance of cache manager configured for a namespace
711 cache_manager = get_auth_cache_manager(
712 custom_ttl=cache_ttl, suffix=user.user_id if user else '')
713
714 694 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
715 695 plugin.get_id(), plugin_cache_active, cache_ttl)
716 696
717 # for environ based password can be empty, but then the validation is
718 # on the server that fills in the env data needed for authentication
719
720 _password_hash = caches.compute_key_from_params(
721 plugin.name, username, (password or ''))
697 user_id = user.user_id
698 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
699 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
722 700
723 # _authenticate is a wrapper for .auth() method of plugin.
724 # it checks if .auth() sends proper data.
725 # For RhodeCodeExternalAuthPlugin it also maps users to
726 # Database and maps the attributes returned from .auth()
727 # to RhodeCode database. If this function returns data
728 # then auth is correct.
729 start = time.time()
730 log.debug('Running plugin `%s` _authenticate method', plugin.get_id())
701 @region.cache_on_arguments(namespace=cache_namespace_uid,
702 expiration_time=cache_ttl,
703 should_cache_fn=lambda v: plugin_cache_active)
704 def compute_auth(
705 cache_name, plugin_name, username, password):
731 706
732 def auth_func():
733 """
734 This function is used internally in Cache of Beaker to calculate
735 Results
736 """
737 log.debug('auth: calculating password access now...')
707 # _authenticate is a wrapper for .auth() method of plugin.
708 # it checks if .auth() sends proper data.
709 # For RhodeCodeExternalAuthPlugin it also maps users to
710 # Database and maps the attributes returned from .auth()
711 # to RhodeCode database. If this function returns data
712 # then auth is correct.
713 log.debug('Running plugin `%s` _authenticate method '
714 'using username and password', plugin.get_id())
738 715 return plugin._authenticate(
739 716 user, username, password, plugin_settings,
740 717 environ=environ or {})
741 718
742 if plugin_cache_active:
743 log.debug('Trying to fetch cached auth by pwd hash `...%s`',
744 _password_hash[:6])
745 plugin_user = cache_manager.get(
746 _password_hash, createfunc=auth_func)
747 else:
748 plugin_user = auth_func()
719 start = time.time()
720 # for environ based auth, password can be empty, but then the validation is
721 # on the server that fills in the env data needed for authentication
722 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
749 723
750 724 auth_time = time.time() - start
751 725 log.debug('Authentication for plugin `%s` completed in %.3fs, '
752 726 'expiration time of fetched cache %.1fs.',
753 727 plugin.get_id(), auth_time, cache_ttl)
754 728
755 729 log.debug('PLUGIN USER DATA: %s', plugin_user)
756 730
757 731 if plugin_user:
758 732 log.debug('Plugin returned proper authentication data')
759 733 return plugin_user
760 734 # we failed to Auth because .auth() method didn't return proper user
761 735 log.debug("User `%s` failed to authenticate against %s",
762 736 display_user, plugin.get_id())
763 737
764 738 # case when we failed to authenticate against all defined plugins
765 739 return None
766 740
767 741
768 742 def chop_at(s, sub, inclusive=False):
769 743 """Truncate string ``s`` at the first occurrence of ``sub``.
770 744
771 745 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
772 746
773 747 >>> chop_at("plutocratic brats", "rat")
774 748 'plutoc'
775 749 >>> chop_at("plutocratic brats", "rat", True)
776 750 'plutocrat'
777 751 """
778 752 pos = s.find(sub)
779 753 if pos == -1:
780 754 return s
781 755 if inclusive:
782 756 return s[:pos+len(sub)]
783 757 return s[:pos]
@@ -1,191 +1,180 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2018 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 colander
22 22 import formencode.htmlfill
23 23 import logging
24 24
25 25 from pyramid.httpexceptions import HTTPFound
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.apps._base import BaseAppView
30 from rhodecode.authentication.base import (
31 get_auth_cache_manager, get_perms_cache_manager, get_authn_registry)
30 from rhodecode.authentication.base import get_authn_registry
32 31 from rhodecode.lib import helpers as h
33 32 from rhodecode.lib.auth import (
34 33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
35 34 from rhodecode.lib.caches import clear_cache_manager
36 35 from rhodecode.model.forms import AuthSettingsForm
37 36 from rhodecode.model.meta import Session
38 37 from rhodecode.model.settings import SettingsModel
39 38
40 39 log = logging.getLogger(__name__)
41 40
42 41
43 42 class AuthnPluginViewBase(BaseAppView):
44 43
45 44 def load_default_context(self):
46 45 c = self._get_local_tmpl_context()
47 46 self.plugin = self.context.plugin
48 47 return c
49 48
50 49 @LoginRequired()
51 50 @HasPermissionAllDecorator('hg.admin')
52 51 def settings_get(self, defaults=None, errors=None):
53 52 """
54 53 View that displays the plugin settings as a form.
55 54 """
56 55 c = self.load_default_context()
57 56 defaults = defaults or {}
58 57 errors = errors or {}
59 58 schema = self.plugin.get_settings_schema()
60 59
61 60 # Compute default values for the form. Priority is:
62 61 # 1. Passed to this method 2. DB value 3. Schema default
63 62 for node in schema:
64 63 if node.name not in defaults:
65 64 defaults[node.name] = self.plugin.get_setting_by_name(
66 65 node.name, node.default)
67 66
68 67 template_context = {
69 68 'defaults': defaults,
70 69 'errors': errors,
71 70 'plugin': self.context.plugin,
72 71 'resource': self.context,
73 72 }
74 73
75 74 return self._get_template_context(c, **template_context)
76 75
77 76 @LoginRequired()
78 77 @HasPermissionAllDecorator('hg.admin')
79 78 @CSRFRequired()
80 79 def settings_post(self):
81 80 """
82 81 View that validates and stores the plugin settings.
83 82 """
84 83 _ = self.request.translate
85 84 self.load_default_context()
86 85 schema = self.plugin.get_settings_schema()
87 86 data = self.request.params
88 87
89 88 try:
90 89 valid_data = schema.deserialize(data)
91 90 except colander.Invalid as e:
92 91 # Display error message and display form again.
93 92 h.flash(
94 93 _('Errors exist when saving plugin settings. '
95 94 'Please check the form inputs.'),
96 95 category='error')
97 96 defaults = {key: data[key] for key in data if key in schema}
98 97 return self.settings_get(errors=e.asdict(), defaults=defaults)
99 98
100 99 # Store validated data.
101 100 for name, value in valid_data.items():
102 101 self.plugin.create_or_update_setting(name, value)
103 102 Session().commit()
104 103
105 # cleanup cache managers in case of change for plugin
106 # TODO(marcink): because we can register multiple namespaces
107 # we should at some point figure out how to retrieve ALL namespace
108 # cache managers and clear them...
109 cache_manager = get_auth_cache_manager()
110 clear_cache_manager(cache_manager)
111
112 cache_manager = get_perms_cache_manager()
113 clear_cache_manager(cache_manager)
114
115 104 # Display success message and redirect.
116 105 h.flash(_('Auth settings updated successfully.'), category='success')
117 106 redirect_to = self.request.resource_path(
118 107 self.context, route_name='auth_home')
119 108 return HTTPFound(redirect_to)
120 109
121 110
122 111 class AuthSettingsView(BaseAppView):
123 112 def load_default_context(self):
124 113 c = self._get_local_tmpl_context()
125 114 return c
126 115
127 116 @LoginRequired()
128 117 @HasPermissionAllDecorator('hg.admin')
129 118 def index(self, defaults=None, errors=None, prefix_error=False):
130 119 c = self.load_default_context()
131 120
132 121 defaults = defaults or {}
133 122 authn_registry = get_authn_registry(self.request.registry)
134 123 enabled_plugins = SettingsModel().get_auth_plugins()
135 124
136 125 # Create template context and render it.
137 126 template_context = {
138 127 'resource': self.context,
139 128 'available_plugins': authn_registry.get_plugins(),
140 129 'enabled_plugins': enabled_plugins,
141 130 }
142 131 html = render('rhodecode:templates/admin/auth/auth_settings.mako',
143 132 self._get_template_context(c, **template_context),
144 133 self.request)
145 134
146 135 # Create form default values and fill the form.
147 136 form_defaults = {
148 137 'auth_plugins': ',\n'.join(enabled_plugins)
149 138 }
150 139 form_defaults.update(defaults)
151 140 html = formencode.htmlfill.render(
152 141 html,
153 142 defaults=form_defaults,
154 143 errors=errors,
155 144 prefix_error=prefix_error,
156 145 encoding="UTF-8",
157 146 force_defaults=False)
158 147
159 148 return Response(html)
160 149
161 150 @LoginRequired()
162 151 @HasPermissionAllDecorator('hg.admin')
163 152 @CSRFRequired()
164 153 def auth_settings(self):
165 154 _ = self.request.translate
166 155 try:
167 156 form = AuthSettingsForm(self.request.translate)()
168 157 form_result = form.to_python(self.request.POST)
169 158 plugins = ','.join(form_result['auth_plugins'])
170 159 setting = SettingsModel().create_or_update_setting(
171 160 'auth_plugins', plugins)
172 161 Session().add(setting)
173 162 Session().commit()
174 163
175 164 h.flash(_('Auth settings updated successfully.'), category='success')
176 165 except formencode.Invalid as errors:
177 166 e = errors.error_dict or {}
178 167 h.flash(_('Errors exist when saving plugin setting. '
179 168 'Please check the form inputs.'), category='error')
180 169 return self.index(
181 170 defaults=errors.value,
182 171 errors=e,
183 172 prefix_error=False)
184 173 except Exception:
185 174 log.exception('Exception in auth_settings')
186 175 h.flash(_('Error occurred during update of auth settings.'),
187 176 category='error')
188 177
189 178 redirect_to = self.request.resource_path(
190 179 self.context, route_name='auth_home')
191 180 return HTTPFound(redirect_to)
@@ -1,461 +1,476 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import logging
23 23 import traceback
24 24 import collections
25 import tempfile
25 26
26 27 from paste.gzipper import make_gzip_middleware
27 28 from pyramid.wsgi import wsgiapp
28 29 from pyramid.authorization import ACLAuthorizationPolicy
29 30 from pyramid.config import Configurator
30 31 from pyramid.settings import asbool, aslist
31 32 from pyramid.httpexceptions import (
32 33 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
33 34 from pyramid.events import ApplicationCreated
34 35 from pyramid.renderers import render_to_response
35 36
36 37 from rhodecode.model import meta
37 38 from rhodecode.config import patches
38 39 from rhodecode.config import utils as config_utils
39 40 from rhodecode.config.environment import load_pyramid_environment
40 41
41 42 from rhodecode.lib.middleware.vcs import VCSMiddleware
42 43 from rhodecode.lib.request import Request
43 44 from rhodecode.lib.vcs import VCSCommunicationError
44 45 from rhodecode.lib.exceptions import VCSServerUnavailable
45 46 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
46 47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
47 48 from rhodecode.lib.celerylib.loader import configure_celery
48 49 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
49 50 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
50 51 from rhodecode.subscribers import (
51 52 scan_repositories_if_enabled, write_js_routes_if_enabled,
52 53 write_metadata_if_needed, inject_app_settings)
53 54
54 55
55 56 log = logging.getLogger(__name__)
56 57
57 58
58 59 def is_http_error(response):
59 60 # error which should have traceback
60 61 return response.status_code > 499
61 62
62 63
63 64 def make_pyramid_app(global_config, **settings):
64 65 """
65 66 Constructs the WSGI application based on Pyramid.
66 67
67 68 Specials:
68 69
69 70 * The application can also be integrated like a plugin via the call to
70 71 `includeme`. This is accompanied with the other utility functions which
71 72 are called. Changing this should be done with great care to not break
72 73 cases when these fragments are assembled from another place.
73 74
74 75 """
75 76
76 77 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
77 78 # will be replaced by the value of the environment variable "NAME" in this case.
78 79 environ = {
79 80 'ENV_{}'.format(key): value for key, value in os.environ.items()}
80 81
81 82 global_config = _substitute_values(global_config, environ)
82 83 settings = _substitute_values(settings, environ)
83 84
84 85 sanitize_settings_and_apply_defaults(settings)
85 86
86 87 config = Configurator(settings=settings)
87 88
88 89 # Apply compatibility patches
89 90 patches.inspect_getargspec()
90 91
91 92 load_pyramid_environment(global_config, settings)
92 93
93 94 # Static file view comes first
94 95 includeme_first(config)
95 96
96 97 includeme(config)
97 98
98 99 pyramid_app = config.make_wsgi_app()
99 100 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
100 101 pyramid_app.config = config
101 102
102 103 config.configure_celery(global_config['__file__'])
103 104 # creating the app uses a connection - return it after we are done
104 105 meta.Session.remove()
105 106
106 107 log.info('Pyramid app %s created and configured.', pyramid_app)
107 108 return pyramid_app
108 109
109 110
110 111 def not_found_view(request):
111 112 """
112 113 This creates the view which should be registered as not-found-view to
113 114 pyramid.
114 115 """
115 116
116 117 if not getattr(request, 'vcs_call', None):
117 118 # handle like regular case with our error_handler
118 119 return error_handler(HTTPNotFound(), request)
119 120
120 121 # handle not found view as a vcs call
121 122 settings = request.registry.settings
122 123 ae_client = getattr(request, 'ae_client', None)
123 124 vcs_app = VCSMiddleware(
124 125 HTTPNotFound(), request.registry, settings,
125 126 appenlight_client=ae_client)
126 127
127 128 return wsgiapp(vcs_app)(None, request)
128 129
129 130
130 131 def error_handler(exception, request):
131 132 import rhodecode
132 133 from rhodecode.lib import helpers
133 134
134 135 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
135 136
136 137 base_response = HTTPInternalServerError()
137 138 # prefer original exception for the response since it may have headers set
138 139 if isinstance(exception, HTTPException):
139 140 base_response = exception
140 141 elif isinstance(exception, VCSCommunicationError):
141 142 base_response = VCSServerUnavailable()
142 143
143 144 if is_http_error(base_response):
144 145 log.exception(
145 146 'error occurred handling this request for path: %s', request.path)
146 147
147 148 error_explanation = base_response.explanation or str(base_response)
148 149 if base_response.status_code == 404:
149 150 error_explanation += " Or you don't have permission to access it."
150 151 c = AttributeDict()
151 152 c.error_message = base_response.status
152 153 c.error_explanation = error_explanation
153 154 c.visual = AttributeDict()
154 155
155 156 c.visual.rhodecode_support_url = (
156 157 request.registry.settings.get('rhodecode_support_url') or
157 158 request.route_url('rhodecode_support')
158 159 )
159 160 c.redirect_time = 0
160 161 c.rhodecode_name = rhodecode_title
161 162 if not c.rhodecode_name:
162 163 c.rhodecode_name = 'Rhodecode'
163 164
164 165 c.causes = []
165 166 if is_http_error(base_response):
166 167 c.causes.append('Server is overloaded.')
167 168 c.causes.append('Server database connection is lost.')
168 169 c.causes.append('Server expected unhandled error.')
169 170
170 171 if hasattr(base_response, 'causes'):
171 172 c.causes = base_response.causes
172 173
173 174 c.messages = helpers.flash.pop_messages(request=request)
174 175 c.traceback = traceback.format_exc()
175 176 response = render_to_response(
176 177 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
177 178 response=base_response)
178 179
179 180 return response
180 181
181 182
182 183 def includeme_first(config):
183 184 # redirect automatic browser favicon.ico requests to correct place
184 185 def favicon_redirect(context, request):
185 186 return HTTPFound(
186 187 request.static_path('rhodecode:public/images/favicon.ico'))
187 188
188 189 config.add_view(favicon_redirect, route_name='favicon')
189 190 config.add_route('favicon', '/favicon.ico')
190 191
191 192 def robots_redirect(context, request):
192 193 return HTTPFound(
193 194 request.static_path('rhodecode:public/robots.txt'))
194 195
195 196 config.add_view(robots_redirect, route_name='robots')
196 197 config.add_route('robots', '/robots.txt')
197 198
198 199 config.add_static_view(
199 200 '_static/deform', 'deform:static')
200 201 config.add_static_view(
201 202 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
202 203
203 204
204 205 def includeme(config):
205 206 settings = config.registry.settings
206 207 config.set_request_factory(Request)
207 208
208 209 # plugin information
209 210 config.registry.rhodecode_plugins = collections.OrderedDict()
210 211
211 212 config.add_directive(
212 213 'register_rhodecode_plugin', register_rhodecode_plugin)
213 214
214 215 config.add_directive('configure_celery', configure_celery)
215 216
216 217 if asbool(settings.get('appenlight', 'false')):
217 218 config.include('appenlight_client.ext.pyramid_tween')
218 219
219 220 # Includes which are required. The application would fail without them.
220 221 config.include('pyramid_mako')
221 222 config.include('pyramid_beaker')
222 223 config.include('rhodecode.lib.caches')
224 config.include('rhodecode.lib.rc_cache')
223 225
224 226 config.include('rhodecode.authentication')
225 227 config.include('rhodecode.integrations')
226 228
227 229 # apps
228 230 config.include('rhodecode.apps._base')
229 231 config.include('rhodecode.apps.ops')
230 232
231 233 config.include('rhodecode.apps.admin')
232 234 config.include('rhodecode.apps.channelstream')
233 235 config.include('rhodecode.apps.login')
234 236 config.include('rhodecode.apps.home')
235 237 config.include('rhodecode.apps.journal')
236 238 config.include('rhodecode.apps.repository')
237 239 config.include('rhodecode.apps.repo_group')
238 240 config.include('rhodecode.apps.user_group')
239 241 config.include('rhodecode.apps.search')
240 242 config.include('rhodecode.apps.user_profile')
241 243 config.include('rhodecode.apps.user_group_profile')
242 244 config.include('rhodecode.apps.my_account')
243 245 config.include('rhodecode.apps.svn_support')
244 246 config.include('rhodecode.apps.ssh_support')
245 247 config.include('rhodecode.apps.gist')
246 248
247 249 config.include('rhodecode.apps.debug_style')
248 250 config.include('rhodecode.tweens')
249 251 config.include('rhodecode.api')
250 252
251 253 config.add_route(
252 254 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
253 255
254 256 config.add_translation_dirs('rhodecode:i18n/')
255 257 settings['default_locale_name'] = settings.get('lang', 'en')
256 258
257 259 # Add subscribers.
258 260 config.add_subscriber(inject_app_settings, ApplicationCreated)
259 261 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
260 262 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
261 263 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
262 264
263 265 # events
264 266 # TODO(marcink): this should be done when pyramid migration is finished
265 267 # config.add_subscriber(
266 268 # 'rhodecode.integrations.integrations_event_handler',
267 269 # 'rhodecode.events.RhodecodeEvent')
268 270
269 271 # request custom methods
270 272 config.add_request_method(
271 273 'rhodecode.lib.partial_renderer.get_partial_renderer',
272 274 'get_partial_renderer')
273 275
274 276 # Set the authorization policy.
275 277 authz_policy = ACLAuthorizationPolicy()
276 278 config.set_authorization_policy(authz_policy)
277 279
278 280 # Set the default renderer for HTML templates to mako.
279 281 config.add_mako_renderer('.html')
280 282
281 283 config.add_renderer(
282 284 name='json_ext',
283 285 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
284 286
285 287 # include RhodeCode plugins
286 288 includes = aslist(settings.get('rhodecode.includes', []))
287 289 for inc in includes:
288 290 config.include(inc)
289 291
290 292 # custom not found view, if our pyramid app doesn't know how to handle
291 293 # the request pass it to potential VCS handling ap
292 294 config.add_notfound_view(not_found_view)
293 295 if not settings.get('debugtoolbar.enabled', False):
294 296 # disabled debugtoolbar handle all exceptions via the error_handlers
295 297 config.add_view(error_handler, context=Exception)
296 298
297 299 # all errors including 403/404/50X
298 300 config.add_view(error_handler, context=HTTPError)
299 301
300 302
301 303 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
302 304 """
303 305 Apply outer WSGI middlewares around the application.
304 306 """
305 307 settings = config.registry.settings
306 308
307 309 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
308 310 pyramid_app = HttpsFixup(pyramid_app, settings)
309 311
310 312 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
311 313 pyramid_app, settings)
312 314 config.registry.ae_client = _ae_client
313 315
314 316 if settings['gzip_responses']:
315 317 pyramid_app = make_gzip_middleware(
316 318 pyramid_app, settings, compress_level=1)
317 319
318 320 # this should be the outer most middleware in the wsgi stack since
319 321 # middleware like Routes make database calls
320 322 def pyramid_app_with_cleanup(environ, start_response):
321 323 try:
322 324 return pyramid_app(environ, start_response)
323 325 finally:
324 326 # Dispose current database session and rollback uncommitted
325 327 # transactions.
326 328 meta.Session.remove()
327 329
328 330 # In a single threaded mode server, on non sqlite db we should have
329 331 # '0 Current Checked out connections' at the end of a request,
330 332 # if not, then something, somewhere is leaving a connection open
331 333 pool = meta.Base.metadata.bind.engine.pool
332 334 log.debug('sa pool status: %s', pool.status())
333 335
334 336 return pyramid_app_with_cleanup
335 337
336 338
337 339 def sanitize_settings_and_apply_defaults(settings):
338 340 """
339 341 Applies settings defaults and does all type conversion.
340 342
341 343 We would move all settings parsing and preparation into this place, so that
342 344 we have only one place left which deals with this part. The remaining parts
343 345 of the application would start to rely fully on well prepared settings.
344 346
345 347 This piece would later be split up per topic to avoid a big fat monster
346 348 function.
347 349 """
348 350
349 351 settings.setdefault('rhodecode.edition', 'Community Edition')
350 352
351 353 if 'mako.default_filters' not in settings:
352 354 # set custom default filters if we don't have it defined
353 355 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
354 356 settings['mako.default_filters'] = 'h_filter'
355 357
356 358 if 'mako.directories' not in settings:
357 359 mako_directories = settings.setdefault('mako.directories', [
358 360 # Base templates of the original application
359 361 'rhodecode:templates',
360 362 ])
361 363 log.debug(
362 364 "Using the following Mako template directories: %s",
363 365 mako_directories)
364 366
365 367 # Default includes, possible to change as a user
366 368 pyramid_includes = settings.setdefault('pyramid.includes', [
367 369 'rhodecode.lib.middleware.request_wrapper',
368 370 ])
369 371 log.debug(
370 372 "Using the following pyramid.includes: %s",
371 373 pyramid_includes)
372 374
373 375 # TODO: johbo: Re-think this, usually the call to config.include
374 376 # should allow to pass in a prefix.
375 377 settings.setdefault('rhodecode.api.url', '/_admin/api')
376 378
377 379 # Sanitize generic settings.
378 380 _list_setting(settings, 'default_encoding', 'UTF-8')
379 381 _bool_setting(settings, 'is_test', 'false')
380 382 _bool_setting(settings, 'gzip_responses', 'false')
381 383
382 384 # Call split out functions that sanitize settings for each topic.
383 385 _sanitize_appenlight_settings(settings)
384 386 _sanitize_vcs_settings(settings)
387 _sanitize_cache_settings(settings)
385 388
386 389 # configure instance id
387 390 config_utils.set_instance_id(settings)
388 391
389 392 return settings
390 393
391 394
392 395 def _sanitize_appenlight_settings(settings):
393 396 _bool_setting(settings, 'appenlight', 'false')
394 397
395 398
396 399 def _sanitize_vcs_settings(settings):
397 400 """
398 401 Applies settings defaults and does type conversion for all VCS related
399 402 settings.
400 403 """
401 404 _string_setting(settings, 'vcs.svn.compatible_version', '')
402 405 _string_setting(settings, 'git_rev_filter', '--all')
403 406 _string_setting(settings, 'vcs.hooks.protocol', 'http')
404 407 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
405 408 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
406 409 _string_setting(settings, 'vcs.server', '')
407 410 _string_setting(settings, 'vcs.server.log_level', 'debug')
408 411 _string_setting(settings, 'vcs.server.protocol', 'http')
409 412 _bool_setting(settings, 'startup.import_repos', 'false')
410 413 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
411 414 _bool_setting(settings, 'vcs.server.enable', 'true')
412 415 _bool_setting(settings, 'vcs.start_server', 'false')
413 416 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
414 417 _int_setting(settings, 'vcs.connection_timeout', 3600)
415 418
416 419 # Support legacy values of vcs.scm_app_implementation. Legacy
417 420 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
418 421 # which is now mapped to 'http'.
419 422 scm_app_impl = settings['vcs.scm_app_implementation']
420 423 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
421 424 settings['vcs.scm_app_implementation'] = 'http'
422 425
423 426
427 def _sanitize_cache_settings(settings):
428 _string_setting(settings, 'cache_dir',
429 os.path.join(tempfile.gettempdir(), 'rc_cache'))
430
431 _string_setting(settings, 'rc_cache.cache_perms.backend',
432 'dogpile.cache.rc.file_namespace')
433 _int_setting(settings, 'rc_cache.cache_perms.expiration_time',
434 60)
435 _string_setting(settings, 'rc_cache.cache_perms.arguments.filename',
436 os.path.join(tempfile.gettempdir(), 'rc_cache_1'))
437
438
424 439 def _int_setting(settings, name, default):
425 440 settings[name] = int(settings.get(name, default))
426 441
427 442
428 443 def _bool_setting(settings, name, default):
429 444 input_val = settings.get(name, default)
430 445 if isinstance(input_val, unicode):
431 446 input_val = input_val.encode('utf8')
432 447 settings[name] = asbool(input_val)
433 448
434 449
435 450 def _list_setting(settings, name, default):
436 451 raw_value = settings.get(name, default)
437 452
438 453 old_separator = ','
439 454 if old_separator in raw_value:
440 455 # If we get a comma separated list, pass it to our own function.
441 456 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
442 457 else:
443 458 # Otherwise we assume it uses pyramids space/newline separation.
444 459 settings[name] = aslist(raw_value)
445 460
446 461
447 462 def _string_setting(settings, name, default, lower=True):
448 463 value = settings.get(name, default)
449 464 if lower:
450 465 value = value.lower()
451 466 settings[name] = value
452 467
453 468
454 469 def _substitute_values(mapping, substitutions):
455 470 result = {
456 471 # Note: Cannot use regular replacements, since they would clash
457 472 # with the implementation of ConfigParser. Using "format" instead.
458 473 key: value.format(**substitutions)
459 474 for key, value in mapping.items()
460 475 }
461 476 return result
@@ -1,2202 +1,2202 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import inspect
28 28 import collections
29 29 import fnmatch
30 30 import hashlib
31 31 import itertools
32 32 import logging
33 33 import random
34 34 import traceback
35 35 from functools import wraps
36 36
37 37 import ipaddress
38 38
39 39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 40 from sqlalchemy.orm.exc import ObjectDeletedError
41 41 from sqlalchemy.orm import joinedload
42 42 from zope.cachedescriptors.property import Lazy as LazyProperty
43 43
44 44 import rhodecode
45 45 from rhodecode.model import meta
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.user import UserModel
48 48 from rhodecode.model.db import (
49 49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 from rhodecode.lib import caches
51 from rhodecode.lib import rc_cache
52 52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 53 from rhodecode.lib.utils import (
54 54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 55 from rhodecode.lib.caching_query import FromCache
56 56
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71
72 72 passwd_gen = PasswordGenerator()
73 73 #print 8-letter password containing only big and small letters
74 74 of alphabet
75 75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
76 76 """
77 77 ALPHABETS_NUM = r'''1234567890'''
78 78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
79 79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
80 80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
81 81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
82 82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
83 83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
84 84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
85 85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
86 86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
87 87
88 88 def __init__(self, passwd=''):
89 89 self.passwd = passwd
90 90
91 91 def gen_password(self, length, type_=None):
92 92 if type_ is None:
93 93 type_ = self.ALPHABETS_FULL
94 94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
95 95 return self.passwd
96 96
97 97
98 98 class _RhodeCodeCryptoBase(object):
99 99 ENC_PREF = None
100 100
101 101 def hash_create(self, str_):
102 102 """
103 103 hash the string using
104 104
105 105 :param str_: password to hash
106 106 """
107 107 raise NotImplementedError
108 108
109 109 def hash_check_with_upgrade(self, password, hashed):
110 110 """
111 111 Returns tuple in which first element is boolean that states that
112 112 given password matches it's hashed version, and the second is new hash
113 113 of the password, in case this password should be migrated to new
114 114 cipher.
115 115 """
116 116 checked_hash = self.hash_check(password, hashed)
117 117 return checked_hash, None
118 118
119 119 def hash_check(self, password, hashed):
120 120 """
121 121 Checks matching password with it's hashed value.
122 122
123 123 :param password: password
124 124 :param hashed: password in hashed form
125 125 """
126 126 raise NotImplementedError
127 127
128 128 def _assert_bytes(self, value):
129 129 """
130 130 Passing in an `unicode` object can lead to hard to detect issues
131 131 if passwords contain non-ascii characters. Doing a type check
132 132 during runtime, so that such mistakes are detected early on.
133 133 """
134 134 if not isinstance(value, str):
135 135 raise TypeError(
136 136 "Bytestring required as input, got %r." % (value, ))
137 137
138 138
139 139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 140 ENC_PREF = ('$2a$10', '$2b$10')
141 141
142 142 def hash_create(self, str_):
143 143 self._assert_bytes(str_)
144 144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
145 145
146 146 def hash_check_with_upgrade(self, password, hashed):
147 147 """
148 148 Returns tuple in which first element is boolean that states that
149 149 given password matches it's hashed version, and the second is new hash
150 150 of the password, in case this password should be migrated to new
151 151 cipher.
152 152
153 153 This implements special upgrade logic which works like that:
154 154 - check if the given password == bcrypted hash, if yes then we
155 155 properly used password and it was already in bcrypt. Proceed
156 156 without any changes
157 157 - if bcrypt hash check is not working try with sha256. If hash compare
158 158 is ok, it means we using correct but old hashed password. indicate
159 159 hash change and proceed
160 160 """
161 161
162 162 new_hash = None
163 163
164 164 # regular pw check
165 165 password_match_bcrypt = self.hash_check(password, hashed)
166 166
167 167 # now we want to know if the password was maybe from sha256
168 168 # basically calling _RhodeCodeCryptoSha256().hash_check()
169 169 if not password_match_bcrypt:
170 170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
171 171 new_hash = self.hash_create(password) # make new bcrypt hash
172 172 password_match_bcrypt = True
173 173
174 174 return password_match_bcrypt, new_hash
175 175
176 176 def hash_check(self, password, hashed):
177 177 """
178 178 Checks matching password with it's hashed value.
179 179
180 180 :param password: password
181 181 :param hashed: password in hashed form
182 182 """
183 183 self._assert_bytes(password)
184 184 try:
185 185 return bcrypt.hashpw(password, hashed) == hashed
186 186 except ValueError as e:
187 187 # we're having a invalid salt here probably, we should not crash
188 188 # just return with False as it would be a wrong password.
189 189 log.debug('Failed to check password hash using bcrypt %s',
190 190 safe_str(e))
191 191
192 192 return False
193 193
194 194
195 195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196 196 ENC_PREF = '_'
197 197
198 198 def hash_create(self, str_):
199 199 self._assert_bytes(str_)
200 200 return hashlib.sha256(str_).hexdigest()
201 201
202 202 def hash_check(self, password, hashed):
203 203 """
204 204 Checks matching password with it's hashed value.
205 205
206 206 :param password: password
207 207 :param hashed: password in hashed form
208 208 """
209 209 self._assert_bytes(password)
210 210 return hashlib.sha256(password).hexdigest() == hashed
211 211
212 212
213 213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
214 214 ENC_PREF = '_'
215 215
216 216 def hash_create(self, str_):
217 217 self._assert_bytes(str_)
218 218 return sha1(str_)
219 219
220 220 def hash_check(self, password, hashed):
221 221 """
222 222 Checks matching password with it's hashed value.
223 223
224 224 :param password: password
225 225 :param hashed: password in hashed form
226 226 """
227 227 self._assert_bytes(password)
228 228 return sha1(password) == hashed
229 229
230 230
231 231 def crypto_backend():
232 232 """
233 233 Return the matching crypto backend.
234 234
235 235 Selection is based on if we run tests or not, we pick sha1-test backend to run
236 236 tests faster since BCRYPT is expensive to calculate
237 237 """
238 238 if rhodecode.is_test:
239 239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
240 240 else:
241 241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
242 242
243 243 return RhodeCodeCrypto
244 244
245 245
246 246 def get_crypt_password(password):
247 247 """
248 248 Create the hash of `password` with the active crypto backend.
249 249
250 250 :param password: The cleartext password.
251 251 :type password: unicode
252 252 """
253 253 password = safe_str(password)
254 254 return crypto_backend().hash_create(password)
255 255
256 256
257 257 def check_password(password, hashed):
258 258 """
259 259 Check if the value in `password` matches the hash in `hashed`.
260 260
261 261 :param password: The cleartext password.
262 262 :type password: unicode
263 263
264 264 :param hashed: The expected hashed version of the password.
265 265 :type hashed: The hash has to be passed in in text representation.
266 266 """
267 267 password = safe_str(password)
268 268 return crypto_backend().hash_check(password, hashed)
269 269
270 270
271 271 def generate_auth_token(data, salt=None):
272 272 """
273 273 Generates API KEY from given string
274 274 """
275 275
276 276 if salt is None:
277 277 salt = os.urandom(16)
278 278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
279 279
280 280
281 281 def get_came_from(request):
282 282 """
283 283 get query_string+path from request sanitized after removing auth_token
284 284 """
285 285 _req = request
286 286
287 287 path = _req.path
288 288 if 'auth_token' in _req.GET:
289 289 # sanitize the request and remove auth_token for redirection
290 290 _req.GET.pop('auth_token')
291 291 qs = _req.query_string
292 292 if qs:
293 293 path += '?' + qs
294 294
295 295 return path
296 296
297 297
298 298 class CookieStoreWrapper(object):
299 299
300 300 def __init__(self, cookie_store):
301 301 self.cookie_store = cookie_store
302 302
303 303 def __repr__(self):
304 304 return 'CookieStore<%s>' % (self.cookie_store)
305 305
306 306 def get(self, key, other=None):
307 307 if isinstance(self.cookie_store, dict):
308 308 return self.cookie_store.get(key, other)
309 309 elif isinstance(self.cookie_store, AuthUser):
310 310 return self.cookie_store.__dict__.get(key, other)
311 311
312 312
313 313 def _cached_perms_data(user_id, scope, user_is_admin,
314 314 user_inherit_default_permissions, explicit, algo,
315 315 calculate_super_admin):
316 316
317 317 permissions = PermissionCalculator(
318 318 user_id, scope, user_is_admin, user_inherit_default_permissions,
319 319 explicit, algo, calculate_super_admin)
320 320 return permissions.calculate()
321 321
322 322
323 323 class PermOrigin(object):
324 324 SUPER_ADMIN = 'superadmin'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 355 >>> perms['resource'] = 'read', 'default'
356 356 >>> perms['resource']
357 357 'read'
358 358 >>> perms['resource'] = 'write', 'admin'
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 367 self.perm_origin_stack = collections.OrderedDict()
368 368
369 369 def __setitem__(self, key, (perm, origin)):
370 370 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
371 371 dict.__setitem__(self, key, perm)
372 372
373 373
374 374 class PermissionCalculator(object):
375 375
376 376 def __init__(
377 377 self, user_id, scope, user_is_admin,
378 378 user_inherit_default_permissions, explicit, algo,
379 379 calculate_super_admin=False):
380 380
381 381 self.user_id = user_id
382 382 self.user_is_admin = user_is_admin
383 383 self.inherit_default_permissions = user_inherit_default_permissions
384 384 self.explicit = explicit
385 385 self.algo = algo
386 386 self.calculate_super_admin = calculate_super_admin
387 387
388 388 scope = scope or {}
389 389 self.scope_repo_id = scope.get('repo_id')
390 390 self.scope_repo_group_id = scope.get('repo_group_id')
391 391 self.scope_user_group_id = scope.get('user_group_id')
392 392
393 393 self.default_user_id = User.get_default_user(cache=True).user_id
394 394
395 395 self.permissions_repositories = PermOriginDict()
396 396 self.permissions_repository_groups = PermOriginDict()
397 397 self.permissions_user_groups = PermOriginDict()
398 398 self.permissions_global = set()
399 399
400 400 self.default_repo_perms = Permission.get_default_repo_perms(
401 401 self.default_user_id, self.scope_repo_id)
402 402 self.default_repo_groups_perms = Permission.get_default_group_perms(
403 403 self.default_user_id, self.scope_repo_group_id)
404 404 self.default_user_group_perms = \
405 405 Permission.get_default_user_group_perms(
406 406 self.default_user_id, self.scope_user_group_id)
407 407
408 408 def calculate(self):
409 409 if self.user_is_admin and not self.calculate_super_admin:
410 410 return self._admin_permissions()
411 411
412 412 self._calculate_global_default_permissions()
413 413 self._calculate_global_permissions()
414 414 self._calculate_default_permissions()
415 415 self._calculate_repository_permissions()
416 416 self._calculate_repository_group_permissions()
417 417 self._calculate_user_group_permissions()
418 418 return self._permission_structure()
419 419
420 420 def _admin_permissions(self):
421 421 """
422 422 admin user have all default rights for repositories
423 423 and groups set to admin
424 424 """
425 425 self.permissions_global.add('hg.admin')
426 426 self.permissions_global.add('hg.create.write_on_repogroup.true')
427 427
428 428 # repositories
429 429 for perm in self.default_repo_perms:
430 430 r_k = perm.UserRepoToPerm.repository.repo_name
431 431 p = 'repository.admin'
432 432 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
433 433
434 434 # repository groups
435 435 for perm in self.default_repo_groups_perms:
436 436 rg_k = perm.UserRepoGroupToPerm.group.group_name
437 437 p = 'group.admin'
438 438 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
439 439
440 440 # user groups
441 441 for perm in self.default_user_group_perms:
442 442 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
443 443 p = 'usergroup.admin'
444 444 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
445 445
446 446 return self._permission_structure()
447 447
448 448 def _calculate_global_default_permissions(self):
449 449 """
450 450 global permissions taken from the default user
451 451 """
452 452 default_global_perms = UserToPerm.query()\
453 453 .filter(UserToPerm.user_id == self.default_user_id)\
454 454 .options(joinedload(UserToPerm.permission))
455 455
456 456 for perm in default_global_perms:
457 457 self.permissions_global.add(perm.permission.permission_name)
458 458
459 459 if self.user_is_admin:
460 460 self.permissions_global.add('hg.admin')
461 461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462 462
463 463 def _calculate_global_permissions(self):
464 464 """
465 465 Set global system permissions with user permissions or permissions
466 466 taken from the user groups of the current user.
467 467
468 468 The permissions include repo creating, repo group creating, forking
469 469 etc.
470 470 """
471 471
472 472 # now we read the defined permissions and overwrite what we have set
473 473 # before those can be configured from groups or users explicitly.
474 474
475 475 # TODO: johbo: This seems to be out of sync, find out the reason
476 476 # for the comment below and update it.
477 477
478 478 # In case we want to extend this list we should be always in sync with
479 479 # User.DEFAULT_USER_PERMISSIONS definitions
480 480 _configurable = frozenset([
481 481 'hg.fork.none', 'hg.fork.repository',
482 482 'hg.create.none', 'hg.create.repository',
483 483 'hg.usergroup.create.false', 'hg.usergroup.create.true',
484 484 'hg.repogroup.create.false', 'hg.repogroup.create.true',
485 485 'hg.create.write_on_repogroup.false',
486 486 'hg.create.write_on_repogroup.true',
487 487 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
488 488 ])
489 489
490 490 # USER GROUPS comes first user group global permissions
491 491 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
492 492 .options(joinedload(UserGroupToPerm.permission))\
493 493 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
494 494 UserGroupMember.users_group_id))\
495 495 .filter(UserGroupMember.user_id == self.user_id)\
496 496 .order_by(UserGroupToPerm.users_group_id)\
497 497 .all()
498 498
499 499 # need to group here by groups since user can be in more than
500 500 # one group, so we get all groups
501 501 _explicit_grouped_perms = [
502 502 [x, list(y)] for x, y in
503 503 itertools.groupby(user_perms_from_users_groups,
504 504 lambda _x: _x.users_group)]
505 505
506 506 for gr, perms in _explicit_grouped_perms:
507 507 # since user can be in multiple groups iterate over them and
508 508 # select the lowest permissions first (more explicit)
509 509 # TODO: marcink: do this^^
510 510
511 511 # group doesn't inherit default permissions so we actually set them
512 512 if not gr.inherit_default_permissions:
513 513 # NEED TO IGNORE all previously set configurable permissions
514 514 # and replace them with explicitly set from this user
515 515 # group permissions
516 516 self.permissions_global = self.permissions_global.difference(
517 517 _configurable)
518 518 for perm in perms:
519 519 self.permissions_global.add(perm.permission.permission_name)
520 520
521 521 # user explicit global permissions
522 522 user_perms = Session().query(UserToPerm)\
523 523 .options(joinedload(UserToPerm.permission))\
524 524 .filter(UserToPerm.user_id == self.user_id).all()
525 525
526 526 if not self.inherit_default_permissions:
527 527 # NEED TO IGNORE all configurable permissions and
528 528 # replace them with explicitly set from this user permissions
529 529 self.permissions_global = self.permissions_global.difference(
530 530 _configurable)
531 531 for perm in user_perms:
532 532 self.permissions_global.add(perm.permission.permission_name)
533 533
534 534 def _calculate_default_permissions(self):
535 535 """
536 536 Set default user permissions for repositories, repository groups
537 537 taken from the default user.
538 538
539 539 Calculate inheritance of object permissions based on what we have now
540 540 in GLOBAL permissions. We check if .false is in GLOBAL since this is
541 541 explicitly set. Inherit is the opposite of .false being there.
542 542
543 543 .. note::
544 544
545 545 the syntax is little bit odd but what we need to check here is
546 546 the opposite of .false permission being in the list so even for
547 547 inconsistent state when both .true/.false is there
548 548 .false is more important
549 549
550 550 """
551 551 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
552 552 in self.permissions_global)
553 553
554 554 # defaults for repositories, taken from `default` user permissions
555 555 # on given repo
556 556 for perm in self.default_repo_perms:
557 557 r_k = perm.UserRepoToPerm.repository.repo_name
558 558 p = perm.Permission.permission_name
559 559 o = PermOrigin.REPO_DEFAULT
560 560 self.permissions_repositories[r_k] = p, o
561 561
562 562 # if we decide this user isn't inheriting permissions from
563 563 # default user we set him to .none so only explicit
564 564 # permissions work
565 565 if not user_inherit_object_permissions:
566 566 p = 'repository.none'
567 567 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
568 568 self.permissions_repositories[r_k] = p, o
569 569
570 570 if perm.Repository.private and not (
571 571 perm.Repository.user_id == self.user_id):
572 572 # disable defaults for private repos,
573 573 p = 'repository.none'
574 574 o = PermOrigin.REPO_PRIVATE
575 575 self.permissions_repositories[r_k] = p, o
576 576
577 577 elif perm.Repository.user_id == self.user_id:
578 578 # set admin if owner
579 579 p = 'repository.admin'
580 580 o = PermOrigin.REPO_OWNER
581 581 self.permissions_repositories[r_k] = p, o
582 582
583 583 if self.user_is_admin:
584 584 p = 'repository.admin'
585 585 o = PermOrigin.SUPER_ADMIN
586 586 self.permissions_repositories[r_k] = p, o
587 587
588 588 # defaults for repository groups taken from `default` user permission
589 589 # on given group
590 590 for perm in self.default_repo_groups_perms:
591 591 rg_k = perm.UserRepoGroupToPerm.group.group_name
592 592 p = perm.Permission.permission_name
593 593 o = PermOrigin.REPOGROUP_DEFAULT
594 594 self.permissions_repository_groups[rg_k] = p, o
595 595
596 596 # if we decide this user isn't inheriting permissions from default
597 597 # user we set him to .none so only explicit permissions work
598 598 if not user_inherit_object_permissions:
599 599 p = 'group.none'
600 600 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
601 601 self.permissions_repository_groups[rg_k] = p, o
602 602
603 603 if perm.RepoGroup.user_id == self.user_id:
604 604 # set admin if owner
605 605 p = 'group.admin'
606 606 o = PermOrigin.REPOGROUP_OWNER
607 607 self.permissions_repository_groups[rg_k] = p, o
608 608
609 609 if self.user_is_admin:
610 610 p = 'group.admin'
611 611 o = PermOrigin.SUPER_ADMIN
612 612 self.permissions_repository_groups[rg_k] = p, o
613 613
614 614 # defaults for user groups taken from `default` user permission
615 615 # on given user group
616 616 for perm in self.default_user_group_perms:
617 617 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
618 618 p = perm.Permission.permission_name
619 619 o = PermOrigin.USERGROUP_DEFAULT
620 620 self.permissions_user_groups[u_k] = p, o
621 621
622 622 # if we decide this user isn't inheriting permissions from default
623 623 # user we set him to .none so only explicit permissions work
624 624 if not user_inherit_object_permissions:
625 625 p = 'usergroup.none'
626 626 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
627 627 self.permissions_user_groups[u_k] = p, o
628 628
629 629 if perm.UserGroup.user_id == self.user_id:
630 630 # set admin if owner
631 631 p = 'usergroup.admin'
632 632 o = PermOrigin.USERGROUP_OWNER
633 633 self.permissions_user_groups[u_k] = p, o
634 634
635 635 if self.user_is_admin:
636 636 p = 'usergroup.admin'
637 637 o = PermOrigin.SUPER_ADMIN
638 638 self.permissions_user_groups[u_k] = p, o
639 639
640 640 def _calculate_repository_permissions(self):
641 641 """
642 642 Repository permissions for the current user.
643 643
644 644 Check if the user is part of user groups for this repository and
645 645 fill in the permission from it. `_choose_permission` decides of which
646 646 permission should be selected based on selected method.
647 647 """
648 648
649 649 # user group for repositories permissions
650 650 user_repo_perms_from_user_group = Permission\
651 651 .get_default_repo_perms_from_user_group(
652 652 self.user_id, self.scope_repo_id)
653 653
654 654 multiple_counter = collections.defaultdict(int)
655 655 for perm in user_repo_perms_from_user_group:
656 656 r_k = perm.UserGroupRepoToPerm.repository.repo_name
657 657 multiple_counter[r_k] += 1
658 658 p = perm.Permission.permission_name
659 659 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
660 660 .users_group.users_group_name
661 661
662 662 if multiple_counter[r_k] > 1:
663 663 cur_perm = self.permissions_repositories[r_k]
664 664 p = self._choose_permission(p, cur_perm)
665 665
666 666 self.permissions_repositories[r_k] = p, o
667 667
668 668 if perm.Repository.user_id == self.user_id:
669 669 # set admin if owner
670 670 p = 'repository.admin'
671 671 o = PermOrigin.REPO_OWNER
672 672 self.permissions_repositories[r_k] = p, o
673 673
674 674 if self.user_is_admin:
675 675 p = 'repository.admin'
676 676 o = PermOrigin.SUPER_ADMIN
677 677 self.permissions_repositories[r_k] = p, o
678 678
679 679 # user explicit permissions for repositories, overrides any specified
680 680 # by the group permission
681 681 user_repo_perms = Permission.get_default_repo_perms(
682 682 self.user_id, self.scope_repo_id)
683 683 for perm in user_repo_perms:
684 684 r_k = perm.UserRepoToPerm.repository.repo_name
685 685 p = perm.Permission.permission_name
686 686 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
687 687
688 688 if not self.explicit:
689 689 cur_perm = self.permissions_repositories.get(
690 690 r_k, 'repository.none')
691 691 p = self._choose_permission(p, cur_perm)
692 692
693 693 self.permissions_repositories[r_k] = p, o
694 694
695 695 if perm.Repository.user_id == self.user_id:
696 696 # set admin if owner
697 697 p = 'repository.admin'
698 698 o = PermOrigin.REPO_OWNER
699 699 self.permissions_repositories[r_k] = p, o
700 700
701 701 if self.user_is_admin:
702 702 p = 'repository.admin'
703 703 o = PermOrigin.SUPER_ADMIN
704 704 self.permissions_repositories[r_k] = p, o
705 705
706 706 def _calculate_repository_group_permissions(self):
707 707 """
708 708 Repository group permissions for the current user.
709 709
710 710 Check if the user is part of user groups for repository groups and
711 711 fill in the permissions from it. `_choose_permission` decides of which
712 712 permission should be selected based on selected method.
713 713 """
714 714 # user group for repo groups permissions
715 715 user_repo_group_perms_from_user_group = Permission\
716 716 .get_default_group_perms_from_user_group(
717 717 self.user_id, self.scope_repo_group_id)
718 718
719 719 multiple_counter = collections.defaultdict(int)
720 720 for perm in user_repo_group_perms_from_user_group:
721 721 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
722 722 multiple_counter[rg_k] += 1
723 723 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
724 724 .users_group.users_group_name
725 725 p = perm.Permission.permission_name
726 726
727 727 if multiple_counter[rg_k] > 1:
728 728 cur_perm = self.permissions_repository_groups[rg_k]
729 729 p = self._choose_permission(p, cur_perm)
730 730 self.permissions_repository_groups[rg_k] = p, o
731 731
732 732 if perm.RepoGroup.user_id == self.user_id:
733 733 # set admin if owner, even for member of other user group
734 734 p = 'group.admin'
735 735 o = PermOrigin.REPOGROUP_OWNER
736 736 self.permissions_repository_groups[rg_k] = p, o
737 737
738 738 if self.user_is_admin:
739 739 p = 'group.admin'
740 740 o = PermOrigin.SUPER_ADMIN
741 741 self.permissions_repository_groups[rg_k] = p, o
742 742
743 743 # user explicit permissions for repository groups
744 744 user_repo_groups_perms = Permission.get_default_group_perms(
745 745 self.user_id, self.scope_repo_group_id)
746 746 for perm in user_repo_groups_perms:
747 747 rg_k = perm.UserRepoGroupToPerm.group.group_name
748 748 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
749 749 .user.username
750 750 p = perm.Permission.permission_name
751 751
752 752 if not self.explicit:
753 753 cur_perm = self.permissions_repository_groups.get(
754 754 rg_k, 'group.none')
755 755 p = self._choose_permission(p, cur_perm)
756 756
757 757 self.permissions_repository_groups[rg_k] = p, o
758 758
759 759 if perm.RepoGroup.user_id == self.user_id:
760 760 # set admin if owner
761 761 p = 'group.admin'
762 762 o = PermOrigin.REPOGROUP_OWNER
763 763 self.permissions_repository_groups[rg_k] = p, o
764 764
765 765 if self.user_is_admin:
766 766 p = 'group.admin'
767 767 o = PermOrigin.SUPER_ADMIN
768 768 self.permissions_repository_groups[rg_k] = p, o
769 769
770 770 def _calculate_user_group_permissions(self):
771 771 """
772 772 User group permissions for the current user.
773 773 """
774 774 # user group for user group permissions
775 775 user_group_from_user_group = Permission\
776 776 .get_default_user_group_perms_from_user_group(
777 777 self.user_id, self.scope_user_group_id)
778 778
779 779 multiple_counter = collections.defaultdict(int)
780 780 for perm in user_group_from_user_group:
781 781 ug_k = perm.UserGroupUserGroupToPerm\
782 782 .target_user_group.users_group_name
783 783 multiple_counter[ug_k] += 1
784 784 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
785 785 .user_group.users_group_name
786 786 p = perm.Permission.permission_name
787 787
788 788 if multiple_counter[ug_k] > 1:
789 789 cur_perm = self.permissions_user_groups[ug_k]
790 790 p = self._choose_permission(p, cur_perm)
791 791
792 792 self.permissions_user_groups[ug_k] = p, o
793 793
794 794 if perm.UserGroup.user_id == self.user_id:
795 795 # set admin if owner, even for member of other user group
796 796 p = 'usergroup.admin'
797 797 o = PermOrigin.USERGROUP_OWNER
798 798 self.permissions_user_groups[ug_k] = p, o
799 799
800 800 if self.user_is_admin:
801 801 p = 'usergroup.admin'
802 802 o = PermOrigin.SUPER_ADMIN
803 803 self.permissions_user_groups[ug_k] = p, o
804 804
805 805 # user explicit permission for user groups
806 806 user_user_groups_perms = Permission.get_default_user_group_perms(
807 807 self.user_id, self.scope_user_group_id)
808 808 for perm in user_user_groups_perms:
809 809 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
810 810 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
811 811 .user.username
812 812 p = perm.Permission.permission_name
813 813
814 814 if not self.explicit:
815 815 cur_perm = self.permissions_user_groups.get(
816 816 ug_k, 'usergroup.none')
817 817 p = self._choose_permission(p, cur_perm)
818 818
819 819 self.permissions_user_groups[ug_k] = p, o
820 820
821 821 if perm.UserGroup.user_id == self.user_id:
822 822 # set admin if owner
823 823 p = 'usergroup.admin'
824 824 o = PermOrigin.USERGROUP_OWNER
825 825 self.permissions_user_groups[ug_k] = p, o
826 826
827 827 if self.user_is_admin:
828 828 p = 'usergroup.admin'
829 829 o = PermOrigin.SUPER_ADMIN
830 830 self.permissions_user_groups[ug_k] = p, o
831 831
832 832 def _choose_permission(self, new_perm, cur_perm):
833 833 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
834 834 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
835 835 if self.algo == 'higherwin':
836 836 if new_perm_val > cur_perm_val:
837 837 return new_perm
838 838 return cur_perm
839 839 elif self.algo == 'lowerwin':
840 840 if new_perm_val < cur_perm_val:
841 841 return new_perm
842 842 return cur_perm
843 843
844 844 def _permission_structure(self):
845 845 return {
846 846 'global': self.permissions_global,
847 847 'repositories': self.permissions_repositories,
848 848 'repositories_groups': self.permissions_repository_groups,
849 849 'user_groups': self.permissions_user_groups,
850 850 }
851 851
852 852
853 853 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
854 854 """
855 855 Check if given controller_name is in whitelist of auth token access
856 856 """
857 857 if not whitelist:
858 858 from rhodecode import CONFIG
859 859 whitelist = aslist(
860 860 CONFIG.get('api_access_controllers_whitelist'), sep=',')
861 861 # backward compat translation
862 862 compat = {
863 863 # old controller, new VIEW
864 864 'ChangesetController:*': 'RepoCommitsView:*',
865 865 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
866 866 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
867 867 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
868 868 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
869 869 'GistsController:*': 'GistView:*',
870 870 }
871 871
872 872 log.debug(
873 873 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
874 874 auth_token_access_valid = False
875 875
876 876 for entry in whitelist:
877 877 token_match = True
878 878 if entry in compat:
879 879 # translate from old Controllers to Pyramid Views
880 880 entry = compat[entry]
881 881
882 882 if '@' in entry:
883 883 # specific AuthToken
884 884 entry, allowed_token = entry.split('@', 1)
885 885 token_match = auth_token == allowed_token
886 886
887 887 if fnmatch.fnmatch(view_name, entry) and token_match:
888 888 auth_token_access_valid = True
889 889 break
890 890
891 891 if auth_token_access_valid:
892 892 log.debug('view: `%s` matches entry in whitelist: %s'
893 893 % (view_name, whitelist))
894 894 else:
895 895 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
896 896 % (view_name, whitelist))
897 897 if auth_token:
898 898 # if we use auth token key and don't have access it's a warning
899 899 log.warning(msg)
900 900 else:
901 901 log.debug(msg)
902 902
903 903 return auth_token_access_valid
904 904
905 905
906 906 class AuthUser(object):
907 907 """
908 908 A simple object that handles all attributes of user in RhodeCode
909 909
910 910 It does lookup based on API key,given user, or user present in session
911 911 Then it fills all required information for such user. It also checks if
912 912 anonymous access is enabled and if so, it returns default user as logged in
913 913 """
914 914 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
915 915
916 916 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
917 917
918 918 self.user_id = user_id
919 919 self._api_key = api_key
920 920
921 921 self.api_key = None
922 922 self.username = username
923 923 self.ip_addr = ip_addr
924 924 self.name = ''
925 925 self.lastname = ''
926 926 self.first_name = ''
927 927 self.last_name = ''
928 928 self.email = ''
929 929 self.is_authenticated = False
930 930 self.admin = False
931 931 self.inherit_default_permissions = False
932 932 self.password = ''
933 933
934 934 self.anonymous_user = None # propagated on propagate_data
935 935 self.propagate_data()
936 936 self._instance = None
937 937 self._permissions_scoped_cache = {} # used to bind scoped calculation
938 938
939 939 @LazyProperty
940 940 def permissions(self):
941 941 return self.get_perms(user=self, cache=False)
942 942
943 943 @LazyProperty
944 944 def permissions_safe(self):
945 945 """
946 946 Filtered permissions excluding not allowed repositories
947 947 """
948 948 perms = self.get_perms(user=self, cache=False)
949 949
950 950 perms['repositories'] = {
951 951 k: v for k, v in perms['repositories'].items()
952 952 if v != 'repository.none'}
953 953 perms['repositories_groups'] = {
954 954 k: v for k, v in perms['repositories_groups'].items()
955 955 if v != 'group.none'}
956 956 perms['user_groups'] = {
957 957 k: v for k, v in perms['user_groups'].items()
958 958 if v != 'usergroup.none'}
959 959 return perms
960 960
961 961 @LazyProperty
962 962 def permissions_full_details(self):
963 963 return self.get_perms(
964 964 user=self, cache=False, calculate_super_admin=True)
965 965
966 966 def permissions_with_scope(self, scope):
967 967 """
968 968 Call the get_perms function with scoped data. The scope in that function
969 969 narrows the SQL calls to the given ID of objects resulting in fetching
970 970 Just particular permission we want to obtain. If scope is an empty dict
971 971 then it basically narrows the scope to GLOBAL permissions only.
972 972
973 973 :param scope: dict
974 974 """
975 975 if 'repo_name' in scope:
976 976 obj = Repository.get_by_repo_name(scope['repo_name'])
977 977 if obj:
978 978 scope['repo_id'] = obj.repo_id
979 _scope = {
980 'repo_id': -1,
981 'user_group_id': -1,
982 'repo_group_id': -1,
983 }
984 _scope.update(scope)
985 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
986 _scope.items())))
987 if cache_key not in self._permissions_scoped_cache:
988 # store in cache to mimic how the @LazyProperty works,
989 # the difference here is that we use the unique key calculated
990 # from params and values
991 res = self.get_perms(user=self, cache=False, scope=_scope)
992 self._permissions_scoped_cache[cache_key] = res
993 return self._permissions_scoped_cache[cache_key]
979 _scope = collections.OrderedDict()
980 _scope['repo_id'] = -1
981 _scope['user_group_id'] = -1
982 _scope['repo_group_id'] = -1
983
984 for k in sorted(scope.keys()):
985 _scope[k] = scope[k]
986
987 # store in cache to mimic how the @LazyProperty works,
988 # the difference here is that we use the unique key calculated
989 # from params and values
990 return self.get_perms(user=self, cache=False, scope=_scope)
994 991
995 992 def get_instance(self):
996 993 return User.get(self.user_id)
997 994
998 995 def update_lastactivity(self):
999 996 if self.user_id:
1000 997 User.get(self.user_id).update_lastactivity()
1001 998
1002 999 def propagate_data(self):
1003 1000 """
1004 1001 Fills in user data and propagates values to this instance. Maps fetched
1005 1002 user attributes to this class instance attributes
1006 1003 """
1007 1004 log.debug('AuthUser: starting data propagation for new potential user')
1008 1005 user_model = UserModel()
1009 1006 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1010 1007 is_user_loaded = False
1011 1008
1012 1009 # lookup by userid
1013 1010 if self.user_id is not None and self.user_id != anon_user.user_id:
1014 1011 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1015 1012 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1016 1013
1017 1014 # try go get user by api key
1018 1015 elif self._api_key and self._api_key != anon_user.api_key:
1019 1016 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1020 1017 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1021 1018
1022 1019 # lookup by username
1023 1020 elif self.username:
1024 1021 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1025 1022 is_user_loaded = user_model.fill_data(self, username=self.username)
1026 1023 else:
1027 1024 log.debug('No data in %s that could been used to log in', self)
1028 1025
1029 1026 if not is_user_loaded:
1030 1027 log.debug(
1031 1028 'Failed to load user. Fallback to default user %s', anon_user)
1032 1029 # if we cannot authenticate user try anonymous
1033 1030 if anon_user.active:
1034 1031 log.debug('default user is active, using it as a session user')
1035 1032 user_model.fill_data(self, user_id=anon_user.user_id)
1036 1033 # then we set this user is logged in
1037 1034 self.is_authenticated = True
1038 1035 else:
1039 1036 log.debug('default user is NOT active')
1040 1037 # in case of disabled anonymous user we reset some of the
1041 1038 # parameters so such user is "corrupted", skipping the fill_data
1042 1039 for attr in ['user_id', 'username', 'admin', 'active']:
1043 1040 setattr(self, attr, None)
1044 1041 self.is_authenticated = False
1045 1042
1046 1043 if not self.username:
1047 1044 self.username = 'None'
1048 1045
1049 1046 log.debug('AuthUser: propagated user is now %s', self)
1050 1047
1051 1048 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1052 1049 calculate_super_admin=False, cache=False):
1053 1050 """
1054 1051 Fills user permission attribute with permissions taken from database
1055 1052 works for permissions given for repositories, and for permissions that
1056 1053 are granted to groups
1057 1054
1058 1055 :param user: instance of User object from database
1059 1056 :param explicit: In case there are permissions both for user and a group
1060 1057 that user is part of, explicit flag will defiine if user will
1061 1058 explicitly override permissions from group, if it's False it will
1062 1059 make decision based on the algo
1063 1060 :param algo: algorithm to decide what permission should be choose if
1064 1061 it's multiple defined, eg user in two different groups. It also
1065 1062 decides if explicit flag is turned off how to specify the permission
1066 1063 for case when user is in a group + have defined separate permission
1067 1064 """
1068 1065 user_id = user.user_id
1069 1066 user_is_admin = user.is_admin
1070 1067
1071 1068 # inheritance of global permissions like create repo/fork repo etc
1072 1069 user_inherit_default_permissions = user.inherit_default_permissions
1073 1070
1074 1071 cache_seconds = safe_int(
1075 rhodecode.CONFIG.get('beaker.cache.short_term.expire'))
1072 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1073
1076 1074 cache_on = cache or cache_seconds > 0
1077 1075 log.debug(
1078 1076 'Computing PERMISSION tree for user %s scope `%s` '
1079 'with caching: %s[%ss]' % (user, scope, cache_on, cache_seconds))
1077 'with caching: %s[TTL: %ss]' % (user, scope, cache_on, cache_seconds or 0))
1078
1079 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1080 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1081
1082 @region.cache_on_arguments(namespace=cache_namespace_uid,
1083 should_cache_fn=lambda v: cache_on)
1084 def compute_perm_tree(cache_name,
1085 user_id, scope, user_is_admin,user_inherit_default_permissions,
1086 explicit, algo, calculate_super_admin):
1087 return _cached_perms_data(
1088 user_id, scope, user_is_admin, user_inherit_default_permissions,
1089 explicit, algo, calculate_super_admin)
1090
1080 1091 start = time.time()
1081 compute = caches.conditional_cache(
1082 'short_term', 'cache_desc.{}'.format(user_id),
1083 condition=cache_on, func=_cached_perms_data)
1084 result = compute(user_id, scope, user_is_admin,
1092 result = compute_perm_tree('permissions', user_id, scope, user_is_admin,
1085 1093 user_inherit_default_permissions, explicit, algo,
1086 1094 calculate_super_admin)
1087 1095
1088 1096 result_repr = []
1089 1097 for k in result:
1090 1098 result_repr.append((k, len(result[k])))
1091 1099 total = time.time() - start
1092 1100 log.debug('PERMISSION tree for user %s computed in %.3fs: %s' % (
1093 1101 user, total, result_repr))
1102
1094 1103 return result
1095 1104
1096 1105 @property
1097 1106 def is_default(self):
1098 1107 return self.username == User.DEFAULT_USER
1099 1108
1100 1109 @property
1101 1110 def is_admin(self):
1102 1111 return self.admin
1103 1112
1104 1113 @property
1105 1114 def is_user_object(self):
1106 1115 return self.user_id is not None
1107 1116
1108 1117 @property
1109 1118 def repositories_admin(self):
1110 1119 """
1111 1120 Returns list of repositories you're an admin of
1112 1121 """
1113 1122 return [
1114 1123 x[0] for x in self.permissions['repositories'].items()
1115 1124 if x[1] == 'repository.admin']
1116 1125
1117 1126 @property
1118 1127 def repository_groups_admin(self):
1119 1128 """
1120 1129 Returns list of repository groups you're an admin of
1121 1130 """
1122 1131 return [
1123 1132 x[0] for x in self.permissions['repositories_groups'].items()
1124 1133 if x[1] == 'group.admin']
1125 1134
1126 1135 @property
1127 1136 def user_groups_admin(self):
1128 1137 """
1129 1138 Returns list of user groups you're an admin of
1130 1139 """
1131 1140 return [
1132 1141 x[0] for x in self.permissions['user_groups'].items()
1133 1142 if x[1] == 'usergroup.admin']
1134 1143
1135 1144 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1136 1145 """
1137 1146 Returns list of repository ids that user have access to based on given
1138 1147 perms. The cache flag should be only used in cases that are used for
1139 1148 display purposes, NOT IN ANY CASE for permission checks.
1140 1149 """
1141 1150 from rhodecode.model.scm import RepoList
1142 1151 if not perms:
1143 1152 perms = [
1144 1153 'repository.read', 'repository.write', 'repository.admin']
1145 1154
1146 1155 def _cached_repo_acl(user_id, perm_def, _name_filter):
1147 1156 qry = Repository.query()
1148 1157 if _name_filter:
1149 1158 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1150 1159 qry = qry.filter(
1151 1160 Repository.repo_name.ilike(ilike_expression))
1152 1161
1153 1162 return [x.repo_id for x in
1154 1163 RepoList(qry, perm_set=perm_def)]
1155 1164
1156 compute = caches.conditional_cache(
1157 'short_term', 'repo_acl_ids.{}'.format(self.user_id),
1158 condition=cache, func=_cached_repo_acl)
1159 return compute(self.user_id, perms, name_filter)
1165 return _cached_repo_acl(self.user_id, perms, name_filter)
1160 1166
1161 1167 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1162 1168 """
1163 1169 Returns list of repository group ids that user have access to based on given
1164 1170 perms. The cache flag should be only used in cases that are used for
1165 1171 display purposes, NOT IN ANY CASE for permission checks.
1166 1172 """
1167 1173 from rhodecode.model.scm import RepoGroupList
1168 1174 if not perms:
1169 1175 perms = [
1170 1176 'group.read', 'group.write', 'group.admin']
1171 1177
1172 1178 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1173 1179 qry = RepoGroup.query()
1174 1180 if _name_filter:
1175 1181 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1176 1182 qry = qry.filter(
1177 1183 RepoGroup.group_name.ilike(ilike_expression))
1178 1184
1179 1185 return [x.group_id for x in
1180 1186 RepoGroupList(qry, perm_set=perm_def)]
1181 1187
1182 compute = caches.conditional_cache(
1183 'short_term', 'repo_group_acl_ids.{}'.format(self.user_id),
1184 condition=cache, func=_cached_repo_group_acl)
1185 return compute(self.user_id, perms, name_filter)
1188 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1186 1189
1187 1190 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1188 1191 """
1189 1192 Returns list of user group ids that user have access to based on given
1190 1193 perms. The cache flag should be only used in cases that are used for
1191 1194 display purposes, NOT IN ANY CASE for permission checks.
1192 1195 """
1193 1196 from rhodecode.model.scm import UserGroupList
1194 1197 if not perms:
1195 1198 perms = [
1196 1199 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1197 1200
1198 1201 def _cached_user_group_acl(user_id, perm_def, name_filter):
1199 1202 qry = UserGroup.query()
1200 1203 if name_filter:
1201 1204 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1202 1205 qry = qry.filter(
1203 1206 UserGroup.users_group_name.ilike(ilike_expression))
1204 1207
1205 1208 return [x.users_group_id for x in
1206 1209 UserGroupList(qry, perm_set=perm_def)]
1207 1210
1208 compute = caches.conditional_cache(
1209 'short_term', 'user_group_acl_ids.{}'.format(self.user_id),
1210 condition=cache, func=_cached_user_group_acl)
1211 return compute(self.user_id, perms, name_filter)
1211 return _cached_user_group_acl(self.user_id, perms, name_filter)
1212 1212
1213 1213 @property
1214 1214 def ip_allowed(self):
1215 1215 """
1216 1216 Checks if ip_addr used in constructor is allowed from defined list of
1217 1217 allowed ip_addresses for user
1218 1218
1219 1219 :returns: boolean, True if ip is in allowed ip range
1220 1220 """
1221 1221 # check IP
1222 1222 inherit = self.inherit_default_permissions
1223 1223 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1224 1224 inherit_from_default=inherit)
1225 1225 @property
1226 1226 def personal_repo_group(self):
1227 1227 return RepoGroup.get_user_personal_repo_group(self.user_id)
1228 1228
1229 1229 @LazyProperty
1230 1230 def feed_token(self):
1231 1231 return self.get_instance().feed_token
1232 1232
1233 1233 @classmethod
1234 1234 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1235 1235 allowed_ips = AuthUser.get_allowed_ips(
1236 1236 user_id, cache=True, inherit_from_default=inherit_from_default)
1237 1237 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1238 1238 log.debug('IP:%s for user %s is in range of %s' % (
1239 1239 ip_addr, user_id, allowed_ips))
1240 1240 return True
1241 1241 else:
1242 1242 log.info('Access for IP:%s forbidden for user %s, '
1243 1243 'not in %s' % (ip_addr, user_id, allowed_ips))
1244 1244 return False
1245 1245
1246 1246 def __repr__(self):
1247 1247 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1248 1248 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1249 1249
1250 1250 def set_authenticated(self, authenticated=True):
1251 1251 if self.user_id != self.anonymous_user.user_id:
1252 1252 self.is_authenticated = authenticated
1253 1253
1254 1254 def get_cookie_store(self):
1255 1255 return {
1256 1256 'username': self.username,
1257 1257 'password': md5(self.password or ''),
1258 1258 'user_id': self.user_id,
1259 1259 'is_authenticated': self.is_authenticated
1260 1260 }
1261 1261
1262 1262 @classmethod
1263 1263 def from_cookie_store(cls, cookie_store):
1264 1264 """
1265 1265 Creates AuthUser from a cookie store
1266 1266
1267 1267 :param cls:
1268 1268 :param cookie_store:
1269 1269 """
1270 1270 user_id = cookie_store.get('user_id')
1271 1271 username = cookie_store.get('username')
1272 1272 api_key = cookie_store.get('api_key')
1273 1273 return AuthUser(user_id, api_key, username)
1274 1274
1275 1275 @classmethod
1276 1276 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1277 1277 _set = set()
1278 1278
1279 1279 if inherit_from_default:
1280 1280 default_ips = UserIpMap.query().filter(
1281 1281 UserIpMap.user == User.get_default_user(cache=True))
1282 1282 if cache:
1283 1283 default_ips = default_ips.options(
1284 1284 FromCache("sql_cache_short", "get_user_ips_default"))
1285 1285
1286 1286 # populate from default user
1287 1287 for ip in default_ips:
1288 1288 try:
1289 1289 _set.add(ip.ip_addr)
1290 1290 except ObjectDeletedError:
1291 1291 # since we use heavy caching sometimes it happens that
1292 1292 # we get deleted objects here, we just skip them
1293 1293 pass
1294 1294
1295 1295 # NOTE:(marcink) we don't want to load any rules for empty
1296 1296 # user_id which is the case of access of non logged users when anonymous
1297 1297 # access is disabled
1298 1298 user_ips = []
1299 1299 if user_id:
1300 1300 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1301 1301 if cache:
1302 1302 user_ips = user_ips.options(
1303 1303 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1304 1304
1305 1305 for ip in user_ips:
1306 1306 try:
1307 1307 _set.add(ip.ip_addr)
1308 1308 except ObjectDeletedError:
1309 1309 # since we use heavy caching sometimes it happens that we get
1310 1310 # deleted objects here, we just skip them
1311 1311 pass
1312 1312 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1313 1313
1314 1314
1315 1315 def set_available_permissions(settings):
1316 1316 """
1317 1317 This function will propagate pyramid settings with all available defined
1318 1318 permission given in db. We don't want to check each time from db for new
1319 1319 permissions since adding a new permission also requires application restart
1320 1320 ie. to decorate new views with the newly created permission
1321 1321
1322 1322 :param settings: current pyramid registry.settings
1323 1323
1324 1324 """
1325 1325 log.debug('auth: getting information about all available permissions')
1326 1326 try:
1327 1327 sa = meta.Session
1328 1328 all_perms = sa.query(Permission).all()
1329 1329 settings.setdefault('available_permissions',
1330 1330 [x.permission_name for x in all_perms])
1331 1331 log.debug('auth: set available permissions')
1332 1332 except Exception:
1333 1333 log.exception('Failed to fetch permissions from the database.')
1334 1334 raise
1335 1335
1336 1336
1337 1337 def get_csrf_token(session, force_new=False, save_if_missing=True):
1338 1338 """
1339 1339 Return the current authentication token, creating one if one doesn't
1340 1340 already exist and the save_if_missing flag is present.
1341 1341
1342 1342 :param session: pass in the pyramid session, else we use the global ones
1343 1343 :param force_new: force to re-generate the token and store it in session
1344 1344 :param save_if_missing: save the newly generated token if it's missing in
1345 1345 session
1346 1346 """
1347 1347 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1348 1348 # from pyramid.csrf import get_csrf_token
1349 1349
1350 1350 if (csrf_token_key not in session and save_if_missing) or force_new:
1351 1351 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1352 1352 session[csrf_token_key] = token
1353 1353 if hasattr(session, 'save'):
1354 1354 session.save()
1355 1355 return session.get(csrf_token_key)
1356 1356
1357 1357
1358 1358 def get_request(perm_class_instance):
1359 1359 from pyramid.threadlocal import get_current_request
1360 1360 pyramid_request = get_current_request()
1361 1361 return pyramid_request
1362 1362
1363 1363
1364 1364 # CHECK DECORATORS
1365 1365 class CSRFRequired(object):
1366 1366 """
1367 1367 Decorator for authenticating a form
1368 1368
1369 1369 This decorator uses an authorization token stored in the client's
1370 1370 session for prevention of certain Cross-site request forgery (CSRF)
1371 1371 attacks (See
1372 1372 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1373 1373 information).
1374 1374
1375 1375 For use with the ``webhelpers.secure_form`` helper functions.
1376 1376
1377 1377 """
1378 1378 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1379 1379 except_methods=None):
1380 1380 self.token = token
1381 1381 self.header = header
1382 1382 self.except_methods = except_methods or []
1383 1383
1384 1384 def __call__(self, func):
1385 1385 return get_cython_compat_decorator(self.__wrapper, func)
1386 1386
1387 1387 def _get_csrf(self, _request):
1388 1388 return _request.POST.get(self.token, _request.headers.get(self.header))
1389 1389
1390 1390 def check_csrf(self, _request, cur_token):
1391 1391 supplied_token = self._get_csrf(_request)
1392 1392 return supplied_token and supplied_token == cur_token
1393 1393
1394 1394 def _get_request(self):
1395 1395 return get_request(self)
1396 1396
1397 1397 def __wrapper(self, func, *fargs, **fkwargs):
1398 1398 request = self._get_request()
1399 1399
1400 1400 if request.method in self.except_methods:
1401 1401 return func(*fargs, **fkwargs)
1402 1402
1403 1403 cur_token = get_csrf_token(request.session, save_if_missing=False)
1404 1404 if self.check_csrf(request, cur_token):
1405 1405 if request.POST.get(self.token):
1406 1406 del request.POST[self.token]
1407 1407 return func(*fargs, **fkwargs)
1408 1408 else:
1409 1409 reason = 'token-missing'
1410 1410 supplied_token = self._get_csrf(request)
1411 1411 if supplied_token and cur_token != supplied_token:
1412 1412 reason = 'token-mismatch [%s:%s]' % (
1413 1413 cur_token or ''[:6], supplied_token or ''[:6])
1414 1414
1415 1415 csrf_message = \
1416 1416 ("Cross-site request forgery detected, request denied. See "
1417 1417 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1418 1418 "more information.")
1419 1419 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1420 1420 'REMOTE_ADDR:%s, HEADERS:%s' % (
1421 1421 request, reason, request.remote_addr, request.headers))
1422 1422
1423 1423 raise HTTPForbidden(explanation=csrf_message)
1424 1424
1425 1425
1426 1426 class LoginRequired(object):
1427 1427 """
1428 1428 Must be logged in to execute this function else
1429 1429 redirect to login page
1430 1430
1431 1431 :param api_access: if enabled this checks only for valid auth token
1432 1432 and grants access based on valid token
1433 1433 """
1434 1434 def __init__(self, auth_token_access=None):
1435 1435 self.auth_token_access = auth_token_access
1436 1436
1437 1437 def __call__(self, func):
1438 1438 return get_cython_compat_decorator(self.__wrapper, func)
1439 1439
1440 1440 def _get_request(self):
1441 1441 return get_request(self)
1442 1442
1443 1443 def __wrapper(self, func, *fargs, **fkwargs):
1444 1444 from rhodecode.lib import helpers as h
1445 1445 cls = fargs[0]
1446 1446 user = cls._rhodecode_user
1447 1447 request = self._get_request()
1448 1448 _ = request.translate
1449 1449
1450 1450 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1451 1451 log.debug('Starting login restriction checks for user: %s' % (user,))
1452 1452 # check if our IP is allowed
1453 1453 ip_access_valid = True
1454 1454 if not user.ip_allowed:
1455 1455 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1456 1456 category='warning')
1457 1457 ip_access_valid = False
1458 1458
1459 1459 # check if we used an APIKEY and it's a valid one
1460 1460 # defined white-list of controllers which API access will be enabled
1461 1461 _auth_token = request.GET.get(
1462 1462 'auth_token', '') or request.GET.get('api_key', '')
1463 1463 auth_token_access_valid = allowed_auth_token_access(
1464 1464 loc, auth_token=_auth_token)
1465 1465
1466 1466 # explicit controller is enabled or API is in our whitelist
1467 1467 if self.auth_token_access or auth_token_access_valid:
1468 1468 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1469 1469 db_user = user.get_instance()
1470 1470
1471 1471 if db_user:
1472 1472 if self.auth_token_access:
1473 1473 roles = self.auth_token_access
1474 1474 else:
1475 1475 roles = [UserApiKeys.ROLE_HTTP]
1476 1476 token_match = db_user.authenticate_by_token(
1477 1477 _auth_token, roles=roles)
1478 1478 else:
1479 1479 log.debug('Unable to fetch db instance for auth user: %s', user)
1480 1480 token_match = False
1481 1481
1482 1482 if _auth_token and token_match:
1483 1483 auth_token_access_valid = True
1484 1484 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1485 1485 else:
1486 1486 auth_token_access_valid = False
1487 1487 if not _auth_token:
1488 1488 log.debug("AUTH TOKEN *NOT* present in request")
1489 1489 else:
1490 1490 log.warning(
1491 1491 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1492 1492
1493 1493 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1494 1494 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1495 1495 else 'AUTH_TOKEN_AUTH'
1496 1496
1497 1497 if ip_access_valid and (
1498 1498 user.is_authenticated or auth_token_access_valid):
1499 1499 log.info(
1500 1500 'user %s authenticating with:%s IS authenticated on func %s'
1501 1501 % (user, reason, loc))
1502 1502
1503 1503 # update user data to check last activity
1504 1504 user.update_lastactivity()
1505 1505 Session().commit()
1506 1506 return func(*fargs, **fkwargs)
1507 1507 else:
1508 1508 log.warning(
1509 1509 'user %s authenticating with:%s NOT authenticated on '
1510 1510 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1511 1511 % (user, reason, loc, ip_access_valid,
1512 1512 auth_token_access_valid))
1513 1513 # we preserve the get PARAM
1514 1514 came_from = get_came_from(request)
1515 1515
1516 1516 log.debug('redirecting to login page with %s' % (came_from,))
1517 1517 raise HTTPFound(
1518 1518 h.route_path('login', _query={'came_from': came_from}))
1519 1519
1520 1520
1521 1521 class NotAnonymous(object):
1522 1522 """
1523 1523 Must be logged in to execute this function else
1524 1524 redirect to login page
1525 1525 """
1526 1526
1527 1527 def __call__(self, func):
1528 1528 return get_cython_compat_decorator(self.__wrapper, func)
1529 1529
1530 1530 def _get_request(self):
1531 1531 return get_request(self)
1532 1532
1533 1533 def __wrapper(self, func, *fargs, **fkwargs):
1534 1534 import rhodecode.lib.helpers as h
1535 1535 cls = fargs[0]
1536 1536 self.user = cls._rhodecode_user
1537 1537 request = self._get_request()
1538 1538 _ = request.translate
1539 1539 log.debug('Checking if user is not anonymous @%s' % cls)
1540 1540
1541 1541 anonymous = self.user.username == User.DEFAULT_USER
1542 1542
1543 1543 if anonymous:
1544 1544 came_from = get_came_from(request)
1545 1545 h.flash(_('You need to be a registered user to '
1546 1546 'perform this action'),
1547 1547 category='warning')
1548 1548 raise HTTPFound(
1549 1549 h.route_path('login', _query={'came_from': came_from}))
1550 1550 else:
1551 1551 return func(*fargs, **fkwargs)
1552 1552
1553 1553
1554 1554 class PermsDecorator(object):
1555 1555 """
1556 1556 Base class for controller decorators, we extract the current user from
1557 1557 the class itself, which has it stored in base controllers
1558 1558 """
1559 1559
1560 1560 def __init__(self, *required_perms):
1561 1561 self.required_perms = set(required_perms)
1562 1562
1563 1563 def __call__(self, func):
1564 1564 return get_cython_compat_decorator(self.__wrapper, func)
1565 1565
1566 1566 def _get_request(self):
1567 1567 return get_request(self)
1568 1568
1569 1569 def __wrapper(self, func, *fargs, **fkwargs):
1570 1570 import rhodecode.lib.helpers as h
1571 1571 cls = fargs[0]
1572 1572 _user = cls._rhodecode_user
1573 1573 request = self._get_request()
1574 1574 _ = request.translate
1575 1575
1576 1576 log.debug('checking %s permissions %s for %s %s',
1577 1577 self.__class__.__name__, self.required_perms, cls, _user)
1578 1578
1579 1579 if self.check_permissions(_user):
1580 1580 log.debug('Permission granted for %s %s', cls, _user)
1581 1581 return func(*fargs, **fkwargs)
1582 1582
1583 1583 else:
1584 1584 log.debug('Permission denied for %s %s', cls, _user)
1585 1585 anonymous = _user.username == User.DEFAULT_USER
1586 1586
1587 1587 if anonymous:
1588 1588 came_from = get_came_from(self._get_request())
1589 1589 h.flash(_('You need to be signed in to view this page'),
1590 1590 category='warning')
1591 1591 raise HTTPFound(
1592 1592 h.route_path('login', _query={'came_from': came_from}))
1593 1593
1594 1594 else:
1595 1595 # redirect with 404 to prevent resource discovery
1596 1596 raise HTTPNotFound()
1597 1597
1598 1598 def check_permissions(self, user):
1599 1599 """Dummy function for overriding"""
1600 1600 raise NotImplementedError(
1601 1601 'You have to write this function in child class')
1602 1602
1603 1603
1604 1604 class HasPermissionAllDecorator(PermsDecorator):
1605 1605 """
1606 1606 Checks for access permission for all given predicates. All of them
1607 1607 have to be meet in order to fulfill the request
1608 1608 """
1609 1609
1610 1610 def check_permissions(self, user):
1611 1611 perms = user.permissions_with_scope({})
1612 1612 if self.required_perms.issubset(perms['global']):
1613 1613 return True
1614 1614 return False
1615 1615
1616 1616
1617 1617 class HasPermissionAnyDecorator(PermsDecorator):
1618 1618 """
1619 1619 Checks for access permission for any of given predicates. In order to
1620 1620 fulfill the request any of predicates must be meet
1621 1621 """
1622 1622
1623 1623 def check_permissions(self, user):
1624 1624 perms = user.permissions_with_scope({})
1625 1625 if self.required_perms.intersection(perms['global']):
1626 1626 return True
1627 1627 return False
1628 1628
1629 1629
1630 1630 class HasRepoPermissionAllDecorator(PermsDecorator):
1631 1631 """
1632 1632 Checks for access permission for all given predicates for specific
1633 1633 repository. All of them have to be meet in order to fulfill the request
1634 1634 """
1635 1635 def _get_repo_name(self):
1636 1636 _request = self._get_request()
1637 1637 return get_repo_slug(_request)
1638 1638
1639 1639 def check_permissions(self, user):
1640 1640 perms = user.permissions
1641 1641 repo_name = self._get_repo_name()
1642 1642
1643 1643 try:
1644 1644 user_perms = {perms['repositories'][repo_name]}
1645 1645 except KeyError:
1646 1646 log.debug('cannot locate repo with name: `%s` in permissions defs',
1647 1647 repo_name)
1648 1648 return False
1649 1649
1650 1650 log.debug('checking `%s` permissions for repo `%s`',
1651 1651 user_perms, repo_name)
1652 1652 if self.required_perms.issubset(user_perms):
1653 1653 return True
1654 1654 return False
1655 1655
1656 1656
1657 1657 class HasRepoPermissionAnyDecorator(PermsDecorator):
1658 1658 """
1659 1659 Checks for access permission for any of given predicates for specific
1660 1660 repository. In order to fulfill the request any of predicates must be meet
1661 1661 """
1662 1662 def _get_repo_name(self):
1663 1663 _request = self._get_request()
1664 1664 return get_repo_slug(_request)
1665 1665
1666 1666 def check_permissions(self, user):
1667 1667 perms = user.permissions
1668 1668 repo_name = self._get_repo_name()
1669 1669
1670 1670 try:
1671 1671 user_perms = {perms['repositories'][repo_name]}
1672 1672 except KeyError:
1673 1673 log.debug(
1674 1674 'cannot locate repo with name: `%s` in permissions defs',
1675 1675 repo_name)
1676 1676 return False
1677 1677
1678 1678 log.debug('checking `%s` permissions for repo `%s`',
1679 1679 user_perms, repo_name)
1680 1680 if self.required_perms.intersection(user_perms):
1681 1681 return True
1682 1682 return False
1683 1683
1684 1684
1685 1685 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1686 1686 """
1687 1687 Checks for access permission for all given predicates for specific
1688 1688 repository group. All of them have to be meet in order to
1689 1689 fulfill the request
1690 1690 """
1691 1691 def _get_repo_group_name(self):
1692 1692 _request = self._get_request()
1693 1693 return get_repo_group_slug(_request)
1694 1694
1695 1695 def check_permissions(self, user):
1696 1696 perms = user.permissions
1697 1697 group_name = self._get_repo_group_name()
1698 1698 try:
1699 1699 user_perms = {perms['repositories_groups'][group_name]}
1700 1700 except KeyError:
1701 1701 log.debug(
1702 1702 'cannot locate repo group with name: `%s` in permissions defs',
1703 1703 group_name)
1704 1704 return False
1705 1705
1706 1706 log.debug('checking `%s` permissions for repo group `%s`',
1707 1707 user_perms, group_name)
1708 1708 if self.required_perms.issubset(user_perms):
1709 1709 return True
1710 1710 return False
1711 1711
1712 1712
1713 1713 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1714 1714 """
1715 1715 Checks for access permission for any of given predicates for specific
1716 1716 repository group. In order to fulfill the request any
1717 1717 of predicates must be met
1718 1718 """
1719 1719 def _get_repo_group_name(self):
1720 1720 _request = self._get_request()
1721 1721 return get_repo_group_slug(_request)
1722 1722
1723 1723 def check_permissions(self, user):
1724 1724 perms = user.permissions
1725 1725 group_name = self._get_repo_group_name()
1726 1726
1727 1727 try:
1728 1728 user_perms = {perms['repositories_groups'][group_name]}
1729 1729 except KeyError:
1730 1730 log.debug(
1731 1731 'cannot locate repo group with name: `%s` in permissions defs',
1732 1732 group_name)
1733 1733 return False
1734 1734
1735 1735 log.debug('checking `%s` permissions for repo group `%s`',
1736 1736 user_perms, group_name)
1737 1737 if self.required_perms.intersection(user_perms):
1738 1738 return True
1739 1739 return False
1740 1740
1741 1741
1742 1742 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1743 1743 """
1744 1744 Checks for access permission for all given predicates for specific
1745 1745 user group. All of them have to be meet in order to fulfill the request
1746 1746 """
1747 1747 def _get_user_group_name(self):
1748 1748 _request = self._get_request()
1749 1749 return get_user_group_slug(_request)
1750 1750
1751 1751 def check_permissions(self, user):
1752 1752 perms = user.permissions
1753 1753 group_name = self._get_user_group_name()
1754 1754 try:
1755 1755 user_perms = {perms['user_groups'][group_name]}
1756 1756 except KeyError:
1757 1757 return False
1758 1758
1759 1759 if self.required_perms.issubset(user_perms):
1760 1760 return True
1761 1761 return False
1762 1762
1763 1763
1764 1764 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1765 1765 """
1766 1766 Checks for access permission for any of given predicates for specific
1767 1767 user group. In order to fulfill the request any of predicates must be meet
1768 1768 """
1769 1769 def _get_user_group_name(self):
1770 1770 _request = self._get_request()
1771 1771 return get_user_group_slug(_request)
1772 1772
1773 1773 def check_permissions(self, user):
1774 1774 perms = user.permissions
1775 1775 group_name = self._get_user_group_name()
1776 1776 try:
1777 1777 user_perms = {perms['user_groups'][group_name]}
1778 1778 except KeyError:
1779 1779 return False
1780 1780
1781 1781 if self.required_perms.intersection(user_perms):
1782 1782 return True
1783 1783 return False
1784 1784
1785 1785
1786 1786 # CHECK FUNCTIONS
1787 1787 class PermsFunction(object):
1788 1788 """Base function for other check functions"""
1789 1789
1790 1790 def __init__(self, *perms):
1791 1791 self.required_perms = set(perms)
1792 1792 self.repo_name = None
1793 1793 self.repo_group_name = None
1794 1794 self.user_group_name = None
1795 1795
1796 1796 def __bool__(self):
1797 1797 frame = inspect.currentframe()
1798 1798 stack_trace = traceback.format_stack(frame)
1799 1799 log.error('Checking bool value on a class instance of perm '
1800 1800 'function is not allowed: %s' % ''.join(stack_trace))
1801 1801 # rather than throwing errors, here we always return False so if by
1802 1802 # accident someone checks truth for just an instance it will always end
1803 1803 # up in returning False
1804 1804 return False
1805 1805 __nonzero__ = __bool__
1806 1806
1807 1807 def __call__(self, check_location='', user=None):
1808 1808 if not user:
1809 1809 log.debug('Using user attribute from global request')
1810 1810 # TODO: remove this someday,put as user as attribute here
1811 1811 request = self._get_request()
1812 1812 user = request.user
1813 1813
1814 1814 # init auth user if not already given
1815 1815 if not isinstance(user, AuthUser):
1816 1816 log.debug('Wrapping user %s into AuthUser', user)
1817 1817 user = AuthUser(user.user_id)
1818 1818
1819 1819 cls_name = self.__class__.__name__
1820 1820 check_scope = self._get_check_scope(cls_name)
1821 1821 check_location = check_location or 'unspecified location'
1822 1822
1823 1823 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1824 1824 self.required_perms, user, check_scope, check_location)
1825 1825 if not user:
1826 1826 log.warning('Empty user given for permission check')
1827 1827 return False
1828 1828
1829 1829 if self.check_permissions(user):
1830 1830 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1831 1831 check_scope, user, check_location)
1832 1832 return True
1833 1833
1834 1834 else:
1835 1835 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1836 1836 check_scope, user, check_location)
1837 1837 return False
1838 1838
1839 1839 def _get_request(self):
1840 1840 return get_request(self)
1841 1841
1842 1842 def _get_check_scope(self, cls_name):
1843 1843 return {
1844 1844 'HasPermissionAll': 'GLOBAL',
1845 1845 'HasPermissionAny': 'GLOBAL',
1846 1846 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1847 1847 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1848 1848 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1849 1849 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1850 1850 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1851 1851 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1852 1852 }.get(cls_name, '?:%s' % cls_name)
1853 1853
1854 1854 def check_permissions(self, user):
1855 1855 """Dummy function for overriding"""
1856 1856 raise Exception('You have to write this function in child class')
1857 1857
1858 1858
1859 1859 class HasPermissionAll(PermsFunction):
1860 1860 def check_permissions(self, user):
1861 1861 perms = user.permissions_with_scope({})
1862 1862 if self.required_perms.issubset(perms.get('global')):
1863 1863 return True
1864 1864 return False
1865 1865
1866 1866
1867 1867 class HasPermissionAny(PermsFunction):
1868 1868 def check_permissions(self, user):
1869 1869 perms = user.permissions_with_scope({})
1870 1870 if self.required_perms.intersection(perms.get('global')):
1871 1871 return True
1872 1872 return False
1873 1873
1874 1874
1875 1875 class HasRepoPermissionAll(PermsFunction):
1876 1876 def __call__(self, repo_name=None, check_location='', user=None):
1877 1877 self.repo_name = repo_name
1878 1878 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1879 1879
1880 1880 def _get_repo_name(self):
1881 1881 if not self.repo_name:
1882 1882 _request = self._get_request()
1883 1883 self.repo_name = get_repo_slug(_request)
1884 1884 return self.repo_name
1885 1885
1886 1886 def check_permissions(self, user):
1887 1887 self.repo_name = self._get_repo_name()
1888 1888 perms = user.permissions
1889 1889 try:
1890 1890 user_perms = {perms['repositories'][self.repo_name]}
1891 1891 except KeyError:
1892 1892 return False
1893 1893 if self.required_perms.issubset(user_perms):
1894 1894 return True
1895 1895 return False
1896 1896
1897 1897
1898 1898 class HasRepoPermissionAny(PermsFunction):
1899 1899 def __call__(self, repo_name=None, check_location='', user=None):
1900 1900 self.repo_name = repo_name
1901 1901 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1902 1902
1903 1903 def _get_repo_name(self):
1904 1904 if not self.repo_name:
1905 1905 _request = self._get_request()
1906 1906 self.repo_name = get_repo_slug(_request)
1907 1907 return self.repo_name
1908 1908
1909 1909 def check_permissions(self, user):
1910 1910 self.repo_name = self._get_repo_name()
1911 1911 perms = user.permissions
1912 1912 try:
1913 1913 user_perms = {perms['repositories'][self.repo_name]}
1914 1914 except KeyError:
1915 1915 return False
1916 1916 if self.required_perms.intersection(user_perms):
1917 1917 return True
1918 1918 return False
1919 1919
1920 1920
1921 1921 class HasRepoGroupPermissionAny(PermsFunction):
1922 1922 def __call__(self, group_name=None, check_location='', user=None):
1923 1923 self.repo_group_name = group_name
1924 1924 return super(HasRepoGroupPermissionAny, self).__call__(
1925 1925 check_location, user)
1926 1926
1927 1927 def check_permissions(self, user):
1928 1928 perms = user.permissions
1929 1929 try:
1930 1930 user_perms = {perms['repositories_groups'][self.repo_group_name]}
1931 1931 except KeyError:
1932 1932 return False
1933 1933 if self.required_perms.intersection(user_perms):
1934 1934 return True
1935 1935 return False
1936 1936
1937 1937
1938 1938 class HasRepoGroupPermissionAll(PermsFunction):
1939 1939 def __call__(self, group_name=None, check_location='', user=None):
1940 1940 self.repo_group_name = group_name
1941 1941 return super(HasRepoGroupPermissionAll, self).__call__(
1942 1942 check_location, user)
1943 1943
1944 1944 def check_permissions(self, user):
1945 1945 perms = user.permissions
1946 1946 try:
1947 1947 user_perms = {perms['repositories_groups'][self.repo_group_name]}
1948 1948 except KeyError:
1949 1949 return False
1950 1950 if self.required_perms.issubset(user_perms):
1951 1951 return True
1952 1952 return False
1953 1953
1954 1954
1955 1955 class HasUserGroupPermissionAny(PermsFunction):
1956 1956 def __call__(self, user_group_name=None, check_location='', user=None):
1957 1957 self.user_group_name = user_group_name
1958 1958 return super(HasUserGroupPermissionAny, self).__call__(
1959 1959 check_location, user)
1960 1960
1961 1961 def check_permissions(self, user):
1962 1962 perms = user.permissions
1963 1963 try:
1964 1964 user_perms = {perms['user_groups'][self.user_group_name]}
1965 1965 except KeyError:
1966 1966 return False
1967 1967 if self.required_perms.intersection(user_perms):
1968 1968 return True
1969 1969 return False
1970 1970
1971 1971
1972 1972 class HasUserGroupPermissionAll(PermsFunction):
1973 1973 def __call__(self, user_group_name=None, check_location='', user=None):
1974 1974 self.user_group_name = user_group_name
1975 1975 return super(HasUserGroupPermissionAll, self).__call__(
1976 1976 check_location, user)
1977 1977
1978 1978 def check_permissions(self, user):
1979 1979 perms = user.permissions
1980 1980 try:
1981 1981 user_perms = {perms['user_groups'][self.user_group_name]}
1982 1982 except KeyError:
1983 1983 return False
1984 1984 if self.required_perms.issubset(user_perms):
1985 1985 return True
1986 1986 return False
1987 1987
1988 1988
1989 1989 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1990 1990 class HasPermissionAnyMiddleware(object):
1991 1991 def __init__(self, *perms):
1992 1992 self.required_perms = set(perms)
1993 1993
1994 1994 def __call__(self, user, repo_name):
1995 1995 # repo_name MUST be unicode, since we handle keys in permission
1996 1996 # dict by unicode
1997 1997 repo_name = safe_unicode(repo_name)
1998 1998 user = AuthUser(user.user_id)
1999 1999 log.debug(
2000 2000 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2001 2001 self.required_perms, user, repo_name)
2002 2002
2003 2003 if self.check_permissions(user, repo_name):
2004 2004 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2005 2005 repo_name, user, 'PermissionMiddleware')
2006 2006 return True
2007 2007
2008 2008 else:
2009 2009 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2010 2010 repo_name, user, 'PermissionMiddleware')
2011 2011 return False
2012 2012
2013 2013 def check_permissions(self, user, repo_name):
2014 2014 perms = user.permissions_with_scope({'repo_name': repo_name})
2015 2015
2016 2016 try:
2017 2017 user_perms = {perms['repositories'][repo_name]}
2018 2018 except Exception:
2019 2019 log.exception('Error while accessing user permissions')
2020 2020 return False
2021 2021
2022 2022 if self.required_perms.intersection(user_perms):
2023 2023 return True
2024 2024 return False
2025 2025
2026 2026
2027 2027 # SPECIAL VERSION TO HANDLE API AUTH
2028 2028 class _BaseApiPerm(object):
2029 2029 def __init__(self, *perms):
2030 2030 self.required_perms = set(perms)
2031 2031
2032 2032 def __call__(self, check_location=None, user=None, repo_name=None,
2033 2033 group_name=None, user_group_name=None):
2034 2034 cls_name = self.__class__.__name__
2035 2035 check_scope = 'global:%s' % (self.required_perms,)
2036 2036 if repo_name:
2037 2037 check_scope += ', repo_name:%s' % (repo_name,)
2038 2038
2039 2039 if group_name:
2040 2040 check_scope += ', repo_group_name:%s' % (group_name,)
2041 2041
2042 2042 if user_group_name:
2043 2043 check_scope += ', user_group_name:%s' % (user_group_name,)
2044 2044
2045 2045 log.debug(
2046 2046 'checking cls:%s %s %s @ %s'
2047 2047 % (cls_name, self.required_perms, check_scope, check_location))
2048 2048 if not user:
2049 2049 log.debug('Empty User passed into arguments')
2050 2050 return False
2051 2051
2052 2052 # process user
2053 2053 if not isinstance(user, AuthUser):
2054 2054 user = AuthUser(user.user_id)
2055 2055 if not check_location:
2056 2056 check_location = 'unspecified'
2057 2057 if self.check_permissions(user.permissions, repo_name, group_name,
2058 2058 user_group_name):
2059 2059 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2060 2060 check_scope, user, check_location)
2061 2061 return True
2062 2062
2063 2063 else:
2064 2064 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2065 2065 check_scope, user, check_location)
2066 2066 return False
2067 2067
2068 2068 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2069 2069 user_group_name=None):
2070 2070 """
2071 2071 implement in child class should return True if permissions are ok,
2072 2072 False otherwise
2073 2073
2074 2074 :param perm_defs: dict with permission definitions
2075 2075 :param repo_name: repo name
2076 2076 """
2077 2077 raise NotImplementedError()
2078 2078
2079 2079
2080 2080 class HasPermissionAllApi(_BaseApiPerm):
2081 2081 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2082 2082 user_group_name=None):
2083 2083 if self.required_perms.issubset(perm_defs.get('global')):
2084 2084 return True
2085 2085 return False
2086 2086
2087 2087
2088 2088 class HasPermissionAnyApi(_BaseApiPerm):
2089 2089 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2090 2090 user_group_name=None):
2091 2091 if self.required_perms.intersection(perm_defs.get('global')):
2092 2092 return True
2093 2093 return False
2094 2094
2095 2095
2096 2096 class HasRepoPermissionAllApi(_BaseApiPerm):
2097 2097 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2098 2098 user_group_name=None):
2099 2099 try:
2100 2100 _user_perms = {perm_defs['repositories'][repo_name]}
2101 2101 except KeyError:
2102 2102 log.warning(traceback.format_exc())
2103 2103 return False
2104 2104 if self.required_perms.issubset(_user_perms):
2105 2105 return True
2106 2106 return False
2107 2107
2108 2108
2109 2109 class HasRepoPermissionAnyApi(_BaseApiPerm):
2110 2110 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2111 2111 user_group_name=None):
2112 2112 try:
2113 2113 _user_perms = {perm_defs['repositories'][repo_name]}
2114 2114 except KeyError:
2115 2115 log.warning(traceback.format_exc())
2116 2116 return False
2117 2117 if self.required_perms.intersection(_user_perms):
2118 2118 return True
2119 2119 return False
2120 2120
2121 2121
2122 2122 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2123 2123 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2124 2124 user_group_name=None):
2125 2125 try:
2126 2126 _user_perms = {perm_defs['repositories_groups'][group_name]}
2127 2127 except KeyError:
2128 2128 log.warning(traceback.format_exc())
2129 2129 return False
2130 2130 if self.required_perms.intersection(_user_perms):
2131 2131 return True
2132 2132 return False
2133 2133
2134 2134
2135 2135 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2136 2136 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2137 2137 user_group_name=None):
2138 2138 try:
2139 2139 _user_perms = {perm_defs['repositories_groups'][group_name]}
2140 2140 except KeyError:
2141 2141 log.warning(traceback.format_exc())
2142 2142 return False
2143 2143 if self.required_perms.issubset(_user_perms):
2144 2144 return True
2145 2145 return False
2146 2146
2147 2147
2148 2148 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2149 2149 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2150 2150 user_group_name=None):
2151 2151 try:
2152 2152 _user_perms = {perm_defs['user_groups'][user_group_name]}
2153 2153 except KeyError:
2154 2154 log.warning(traceback.format_exc())
2155 2155 return False
2156 2156 if self.required_perms.intersection(_user_perms):
2157 2157 return True
2158 2158 return False
2159 2159
2160 2160
2161 2161 def check_ip_access(source_ip, allowed_ips=None):
2162 2162 """
2163 2163 Checks if source_ip is a subnet of any of allowed_ips.
2164 2164
2165 2165 :param source_ip:
2166 2166 :param allowed_ips: list of allowed ips together with mask
2167 2167 """
2168 2168 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2169 2169 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2170 2170 if isinstance(allowed_ips, (tuple, list, set)):
2171 2171 for ip in allowed_ips:
2172 2172 ip = safe_unicode(ip)
2173 2173 try:
2174 2174 network_address = ipaddress.ip_network(ip, strict=False)
2175 2175 if source_ip_address in network_address:
2176 2176 log.debug('IP %s is network %s' %
2177 2177 (source_ip_address, network_address))
2178 2178 return True
2179 2179 # for any case we cannot determine the IP, don't crash just
2180 2180 # skip it and log as error, we want to say forbidden still when
2181 2181 # sending bad IP
2182 2182 except Exception:
2183 2183 log.error(traceback.format_exc())
2184 2184 continue
2185 2185 return False
2186 2186
2187 2187
2188 2188 def get_cython_compat_decorator(wrapper, func):
2189 2189 """
2190 2190 Creates a cython compatible decorator. The previously used
2191 2191 decorator.decorator() function seems to be incompatible with cython.
2192 2192
2193 2193 :param wrapper: __wrapper method of the decorator class
2194 2194 :param func: decorated function
2195 2195 """
2196 2196 @wraps(func)
2197 2197 def local_wrapper(*args, **kwds):
2198 2198 return wrapper(func, *args, **kwds)
2199 2199 local_wrapper.__wrapped__ = func
2200 2200 return local_wrapper
2201 2201
2202 2202
@@ -1,546 +1,547 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 The base Controller API
23 23 Provides the BaseController class for subclassing. And usage in different
24 24 controllers
25 25 """
26 26
27 27 import logging
28 28 import socket
29 29
30 30 import markupsafe
31 31 import ipaddress
32 32
33 33 from paste.auth.basic import AuthBasicAuthenticator
34 34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 36
37 37 import rhodecode
38 38 from rhodecode.authentication.base import VCS_TYPE
39 39 from rhodecode.lib import auth, utils2
40 40 from rhodecode.lib import helpers as h
41 41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 42 from rhodecode.lib.exceptions import UserCreationError
43 43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 44 from rhodecode.lib.utils2 import (
45 45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 46 from rhodecode.model.db import Repository, User, ChangesetComment
47 47 from rhodecode.model.notification import NotificationModel
48 48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 def _filter_proxy(ip):
54 54 """
55 55 Passed in IP addresses in HEADERS can be in a special format of multiple
56 56 ips. Those comma separated IPs are passed from various proxies in the
57 57 chain of request processing. The left-most being the original client.
58 58 We only care about the first IP which came from the org. client.
59 59
60 60 :param ip: ip string from headers
61 61 """
62 62 if ',' in ip:
63 63 _ips = ip.split(',')
64 64 _first_ip = _ips[0].strip()
65 65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 66 return _first_ip
67 67 return ip
68 68
69 69
70 70 def _filter_port(ip):
71 71 """
72 72 Removes a port from ip, there are 4 main cases to handle here.
73 73 - ipv4 eg. 127.0.0.1
74 74 - ipv6 eg. ::1
75 75 - ipv4+port eg. 127.0.0.1:8080
76 76 - ipv6+port eg. [::1]:8080
77 77
78 78 :param ip:
79 79 """
80 80 def is_ipv6(ip_addr):
81 81 if hasattr(socket, 'inet_pton'):
82 82 try:
83 83 socket.inet_pton(socket.AF_INET6, ip_addr)
84 84 except socket.error:
85 85 return False
86 86 else:
87 87 # fallback to ipaddress
88 88 try:
89 89 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 90 except Exception:
91 91 return False
92 92 return True
93 93
94 94 if ':' not in ip: # must be ipv4 pure ip
95 95 return ip
96 96
97 97 if '[' in ip and ']' in ip: # ipv6 with port
98 98 return ip.split(']')[0][1:].lower()
99 99
100 100 # must be ipv6 or ipv4 with port
101 101 if is_ipv6(ip):
102 102 return ip
103 103 else:
104 104 ip, _port = ip.split(':')[:2] # means ipv4+port
105 105 return ip
106 106
107 107
108 108 def get_ip_addr(environ):
109 109 proxy_key = 'HTTP_X_REAL_IP'
110 110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 111 def_key = 'REMOTE_ADDR'
112 112 _filters = lambda x: _filter_port(_filter_proxy(x))
113 113
114 114 ip = environ.get(proxy_key)
115 115 if ip:
116 116 return _filters(ip)
117 117
118 118 ip = environ.get(proxy_key2)
119 119 if ip:
120 120 return _filters(ip)
121 121
122 122 ip = environ.get(def_key, '0.0.0.0')
123 123 return _filters(ip)
124 124
125 125
126 126 def get_server_ip_addr(environ, log_errors=True):
127 127 hostname = environ.get('SERVER_NAME')
128 128 try:
129 129 return socket.gethostbyname(hostname)
130 130 except Exception as e:
131 131 if log_errors:
132 132 # in some cases this lookup is not possible, and we don't want to
133 133 # make it an exception in logs
134 134 log.exception('Could not retrieve server ip address: %s', e)
135 135 return hostname
136 136
137 137
138 138 def get_server_port(environ):
139 139 return environ.get('SERVER_PORT')
140 140
141 141
142 142 def get_access_path(environ):
143 143 path = environ.get('PATH_INFO')
144 144 org_req = environ.get('pylons.original_request')
145 145 if org_req:
146 146 path = org_req.environ.get('PATH_INFO')
147 147 return path
148 148
149 149
150 150 def get_user_agent(environ):
151 151 return environ.get('HTTP_USER_AGENT')
152 152
153 153
154 154 def vcs_operation_context(
155 155 environ, repo_name, username, action, scm, check_locking=True,
156 156 is_shadow_repo=False):
157 157 """
158 158 Generate the context for a vcs operation, e.g. push or pull.
159 159
160 160 This context is passed over the layers so that hooks triggered by the
161 161 vcs operation know details like the user, the user's IP address etc.
162 162
163 163 :param check_locking: Allows to switch of the computation of the locking
164 164 data. This serves mainly the need of the simplevcs middleware to be
165 165 able to disable this for certain operations.
166 166
167 167 """
168 168 # Tri-state value: False: unlock, None: nothing, True: lock
169 169 make_lock = None
170 170 locked_by = [None, None, None]
171 171 is_anonymous = username == User.DEFAULT_USER
172 172 user = User.get_by_username(username)
173 173 if not is_anonymous and check_locking:
174 174 log.debug('Checking locking on repository "%s"', repo_name)
175 175 repo = Repository.get_by_repo_name(repo_name)
176 176 make_lock, __, locked_by = repo.get_locking_state(
177 177 action, user.user_id)
178 178 user_id = user.user_id
179 179 settings_model = VcsSettingsModel(repo=repo_name)
180 180 ui_settings = settings_model.get_ui_settings()
181 181
182 182 extras = {
183 183 'ip': get_ip_addr(environ),
184 184 'username': username,
185 185 'user_id': user_id,
186 186 'action': action,
187 187 'repository': repo_name,
188 188 'scm': scm,
189 189 'config': rhodecode.CONFIG['__file__'],
190 190 'make_lock': make_lock,
191 191 'locked_by': locked_by,
192 192 'server_url': utils2.get_server_url(environ),
193 193 'user_agent': get_user_agent(environ),
194 194 'hooks': get_enabled_hook_classes(ui_settings),
195 195 'is_shadow_repo': is_shadow_repo,
196 196 }
197 197 return extras
198 198
199 199
200 200 class BasicAuth(AuthBasicAuthenticator):
201 201
202 202 def __init__(self, realm, authfunc, registry, auth_http_code=None,
203 203 initial_call_detection=False, acl_repo_name=None):
204 204 self.realm = realm
205 205 self.initial_call = initial_call_detection
206 206 self.authfunc = authfunc
207 207 self.registry = registry
208 208 self.acl_repo_name = acl_repo_name
209 209 self._rc_auth_http_code = auth_http_code
210 210
211 211 def _get_response_from_code(self, http_code):
212 212 try:
213 213 return get_exception(safe_int(http_code))
214 214 except Exception:
215 215 log.exception('Failed to fetch response for code %s' % http_code)
216 216 return HTTPForbidden
217 217
218 218 def get_rc_realm(self):
219 219 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
220 220
221 221 def build_authentication(self):
222 222 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
223 223 if self._rc_auth_http_code and not self.initial_call:
224 224 # return alternative HTTP code if alternative http return code
225 225 # is specified in RhodeCode config, but ONLY if it's not the
226 226 # FIRST call
227 227 custom_response_klass = self._get_response_from_code(
228 228 self._rc_auth_http_code)
229 229 return custom_response_klass(headers=head)
230 230 return HTTPUnauthorized(headers=head)
231 231
232 232 def authenticate(self, environ):
233 233 authorization = AUTHORIZATION(environ)
234 234 if not authorization:
235 235 return self.build_authentication()
236 236 (authmeth, auth) = authorization.split(' ', 1)
237 237 if 'basic' != authmeth.lower():
238 238 return self.build_authentication()
239 239 auth = auth.strip().decode('base64')
240 240 _parts = auth.split(':', 1)
241 241 if len(_parts) == 2:
242 242 username, password = _parts
243 243 auth_data = self.authfunc(
244 244 username, password, environ, VCS_TYPE,
245 245 registry=self.registry, acl_repo_name=self.acl_repo_name)
246 246 if auth_data:
247 247 return {'username': username, 'auth_data': auth_data}
248 248 if username and password:
249 249 # we mark that we actually executed authentication once, at
250 250 # that point we can use the alternative auth code
251 251 self.initial_call = False
252 252
253 253 return self.build_authentication()
254 254
255 255 __call__ = authenticate
256 256
257 257
258 258 def calculate_version_hash(config):
259 259 return sha1(
260 260 config.get('beaker.session.secret', '') +
261 261 rhodecode.__version__)[:8]
262 262
263 263
264 264 def get_current_lang(request):
265 265 # NOTE(marcink): remove after pyramid move
266 266 try:
267 267 return translation.get_lang()[0]
268 268 except:
269 269 pass
270 270
271 271 return getattr(request, '_LOCALE_', request.locale_name)
272 272
273 273
274 274 def attach_context_attributes(context, request, user_id):
275 275 """
276 276 Attach variables into template context called `c`.
277 277 """
278 278 config = request.registry.settings
279 279
280 280
281 281 rc_config = SettingsModel().get_all_settings(cache=True)
282 282
283 283 context.rhodecode_version = rhodecode.__version__
284 284 context.rhodecode_edition = config.get('rhodecode.edition')
285 285 # unique secret + version does not leak the version but keep consistency
286 286 context.rhodecode_version_hash = calculate_version_hash(config)
287 287
288 288 # Default language set for the incoming request
289 289 context.language = get_current_lang(request)
290 290
291 291 # Visual options
292 292 context.visual = AttributeDict({})
293 293
294 294 # DB stored Visual Items
295 295 context.visual.show_public_icon = str2bool(
296 296 rc_config.get('rhodecode_show_public_icon'))
297 297 context.visual.show_private_icon = str2bool(
298 298 rc_config.get('rhodecode_show_private_icon'))
299 299 context.visual.stylify_metatags = str2bool(
300 300 rc_config.get('rhodecode_stylify_metatags'))
301 301 context.visual.dashboard_items = safe_int(
302 302 rc_config.get('rhodecode_dashboard_items', 100))
303 303 context.visual.admin_grid_items = safe_int(
304 304 rc_config.get('rhodecode_admin_grid_items', 100))
305 305 context.visual.repository_fields = str2bool(
306 306 rc_config.get('rhodecode_repository_fields'))
307 307 context.visual.show_version = str2bool(
308 308 rc_config.get('rhodecode_show_version'))
309 309 context.visual.use_gravatar = str2bool(
310 310 rc_config.get('rhodecode_use_gravatar'))
311 311 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
312 312 context.visual.default_renderer = rc_config.get(
313 313 'rhodecode_markup_renderer', 'rst')
314 314 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
315 315 context.visual.rhodecode_support_url = \
316 316 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
317 317
318 318 context.visual.affected_files_cut_off = 60
319 319
320 320 context.pre_code = rc_config.get('rhodecode_pre_code')
321 321 context.post_code = rc_config.get('rhodecode_post_code')
322 322 context.rhodecode_name = rc_config.get('rhodecode_title')
323 323 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
324 324 # if we have specified default_encoding in the request, it has more
325 325 # priority
326 326 if request.GET.get('default_encoding'):
327 327 context.default_encodings.insert(0, request.GET.get('default_encoding'))
328 328 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
329 329 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
330 330
331 331 # INI stored
332 332 context.labs_active = str2bool(
333 333 config.get('labs_settings_active', 'false'))
334 334 context.ssh_enabled = str2bool(
335 335 config.get('ssh.generate_authorized_keyfile', 'false'))
336 336
337 337 context.visual.allow_repo_location_change = str2bool(
338 338 config.get('allow_repo_location_change', True))
339 339 context.visual.allow_custom_hooks_settings = str2bool(
340 340 config.get('allow_custom_hooks_settings', True))
341 341 context.debug_style = str2bool(config.get('debug_style', False))
342 342
343 343 context.rhodecode_instanceid = config.get('instance_id')
344 344
345 345 context.visual.cut_off_limit_diff = safe_int(
346 346 config.get('cut_off_limit_diff'))
347 347 context.visual.cut_off_limit_file = safe_int(
348 348 config.get('cut_off_limit_file'))
349 349
350 350 # AppEnlight
351 351 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
352 352 context.appenlight_api_public_key = config.get(
353 353 'appenlight.api_public_key', '')
354 354 context.appenlight_server_url = config.get('appenlight.server_url', '')
355 355
356 356 # JS template context
357 357 context.template_context = {
358 358 'repo_name': None,
359 359 'repo_type': None,
360 360 'repo_landing_commit': None,
361 361 'rhodecode_user': {
362 362 'username': None,
363 363 'email': None,
364 364 'notification_status': False
365 365 },
366 366 'visual': {
367 367 'default_renderer': None
368 368 },
369 369 'commit_data': {
370 370 'commit_id': None
371 371 },
372 372 'pull_request_data': {'pull_request_id': None},
373 373 'timeago': {
374 374 'refresh_time': 120 * 1000,
375 375 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
376 376 },
377 377 'pyramid_dispatch': {
378 378
379 379 },
380 380 'extra': {'plugins': {}}
381 381 }
382 382 # END CONFIG VARS
383 383
384 384 diffmode = 'sideside'
385 385 if request.GET.get('diffmode'):
386 386 if request.GET['diffmode'] == 'unified':
387 387 diffmode = 'unified'
388 388 elif request.session.get('diffmode'):
389 389 diffmode = request.session['diffmode']
390 390
391 391 context.diffmode = diffmode
392 392
393 393 if request.session.get('diffmode') != diffmode:
394 394 request.session['diffmode'] = diffmode
395 395
396 396 context.csrf_token = auth.get_csrf_token(session=request.session)
397 397 context.backends = rhodecode.BACKENDS.keys()
398 398 context.backends.sort()
399 399 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
400 400
401 401 # web case
402 402 if hasattr(request, 'user'):
403 403 context.auth_user = request.user
404 404 context.rhodecode_user = request.user
405 405
406 406 # api case
407 407 if hasattr(request, 'rpc_user'):
408 408 context.auth_user = request.rpc_user
409 409 context.rhodecode_user = request.rpc_user
410 410
411 411 # attach the whole call context to the request
412 412 request.call_context = context
413 413
414 414
415 415 def get_auth_user(request):
416 416 environ = request.environ
417 417 session = request.session
418 418
419 419 ip_addr = get_ip_addr(environ)
420 420 # make sure that we update permissions each time we call controller
421 421 _auth_token = (request.GET.get('auth_token', '') or
422 422 request.GET.get('api_key', ''))
423 423
424 424 if _auth_token:
425 425 # when using API_KEY we assume user exists, and
426 426 # doesn't need auth based on cookies.
427 427 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
428 428 authenticated = False
429 429 else:
430 430 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
431 431 try:
432 432 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
433 433 ip_addr=ip_addr)
434 434 except UserCreationError as e:
435 435 h.flash(e, 'error')
436 436 # container auth or other auth functions that create users
437 437 # on the fly can throw this exception signaling that there's
438 438 # issue with user creation, explanation should be provided
439 439 # in Exception itself. We then create a simple blank
440 440 # AuthUser
441 441 auth_user = AuthUser(ip_addr=ip_addr)
442 442
443 443 # in case someone changes a password for user it triggers session
444 444 # flush and forces a re-login
445 445 if password_changed(auth_user, session):
446 446 session.invalidate()
447 447 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
448 448 auth_user = AuthUser(ip_addr=ip_addr)
449 449
450 450 authenticated = cookie_store.get('is_authenticated')
451 451
452 452 if not auth_user.is_authenticated and auth_user.is_user_object:
453 453 # user is not authenticated and not empty
454 454 auth_user.set_authenticated(authenticated)
455 455
456 456 return auth_user
457 457
458 458
459 459 def h_filter(s):
460 460 """
461 461 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
462 462 we wrap this with additional functionality that converts None to empty
463 463 strings
464 464 """
465 465 if s is None:
466 466 return markupsafe.Markup()
467 467 return markupsafe.escape(s)
468 468
469 469
470 470 def add_events_routes(config):
471 471 """
472 472 Adds routing that can be used in events. Because some events are triggered
473 473 outside of pyramid context, we need to bootstrap request with some
474 474 routing registered
475 475 """
476 476
477 477 from rhodecode.apps._base import ADMIN_PREFIX
478 478
479 479 config.add_route(name='home', pattern='/')
480 480
481 481 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
482 482 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
483 483 config.add_route(name='repo_summary', pattern='/{repo_name}')
484 484 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
485 485 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
486 486
487 487 config.add_route(name='pullrequest_show',
488 488 pattern='/{repo_name}/pull-request/{pull_request_id}')
489 489 config.add_route(name='pull_requests_global',
490 490 pattern='/pull-request/{pull_request_id}')
491 491 config.add_route(name='repo_commit',
492 492 pattern='/{repo_name}/changeset/{commit_id}')
493 493
494 494 config.add_route(name='repo_files',
495 495 pattern='/{repo_name}/files/{commit_id}/{f_path}')
496 496
497 497
498 498 def bootstrap_config(request):
499 499 import pyramid.testing
500 500 registry = pyramid.testing.Registry('RcTestRegistry')
501 501
502 502 config = pyramid.testing.setUp(registry=registry, request=request)
503 503
504 504 # allow pyramid lookup in testing
505 505 config.include('pyramid_mako')
506 506 config.include('pyramid_beaker')
507 507 config.include('rhodecode.lib.caches')
508 config.include('rhodecode.lib.rc_cache')
508 509
509 510 add_events_routes(config)
510 511
511 512 return config
512 513
513 514
514 515 def bootstrap_request(**kwargs):
515 516 import pyramid.testing
516 517
517 518 class TestRequest(pyramid.testing.DummyRequest):
518 519 application_url = kwargs.pop('application_url', 'http://example.com')
519 520 host = kwargs.pop('host', 'example.com:80')
520 521 domain = kwargs.pop('domain', 'example.com')
521 522
522 523 def translate(self, msg):
523 524 return msg
524 525
525 526 def plularize(self, singular, plural, n):
526 527 return singular
527 528
528 529 def get_partial_renderer(self, tmpl_name):
529 530
530 531 from rhodecode.lib.partial_renderer import get_partial_renderer
531 532 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
532 533
533 534 _call_context = {}
534 535 @property
535 536 def call_context(self):
536 537 return self._call_context
537 538
538 539 class TestDummySession(pyramid.testing.DummySession):
539 540 def save(*arg, **kw):
540 541 pass
541 542
542 543 request = TestRequest(**kwargs)
543 544 request.session = TestDummySession()
544 545
545 546 return request
546 547
@@ -1,295 +1,295 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2015-2018 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 import functools
21 21
22 22 import beaker
23 23 import logging
24 24 import threading
25 25
26 26 from beaker.cache import _cache_decorate, cache_regions, region_invalidate
27 27 from sqlalchemy.exc import IntegrityError
28 28
29 29 from rhodecode.lib.utils import safe_str, sha1
30 30 from rhodecode.model.db import Session, CacheKey
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34 FILE_TREE = 'cache_file_tree'
35 35 FILE_TREE_META = 'cache_file_tree_metadata'
36 36 FILE_SEARCH_TREE_META = 'cache_file_search_metadata'
37 37 SUMMARY_STATS = 'cache_summary_stats'
38 38
39 39 # This list of caches gets purged when invalidation happens
40 40 USED_REPO_CACHES = (FILE_TREE, FILE_SEARCH_TREE_META)
41 41
42 42 DEFAULT_CACHE_MANAGER_CONFIG = {
43 43 'type': 'memorylru_base',
44 44 'max_items': 10240,
45 45 'key_length': 256,
46 46 'enabled': True
47 47 }
48 48
49 49
50 50 def get_default_cache_settings(settings):
51 51 cache_settings = {}
52 52 for key in settings.keys():
53 53 for prefix in ['beaker.cache.', 'cache.']:
54 54 if key.startswith(prefix):
55 55 name = key.split(prefix)[1].strip()
56 56 cache_settings[name] = settings[key].strip()
57 57 return cache_settings
58 58
59 59
60 60 # set cache regions for beaker so celery can utilise it
61 61 def configure_caches(settings, default_region_settings=None):
62 62 cache_settings = {'regions': None}
63 63 # main cache settings used as default ...
64 64 cache_settings.update(get_default_cache_settings(settings))
65 65 default_region_settings = default_region_settings or \
66 66 {'type': DEFAULT_CACHE_MANAGER_CONFIG['type']}
67 67 if cache_settings['regions']:
68 68 for region in cache_settings['regions'].split(','):
69 69 region = region.strip()
70 70 region_settings = default_region_settings.copy()
71 71 for key, value in cache_settings.items():
72 72 if key.startswith(region):
73 73 region_settings[key.split(region + '.')[-1]] = value
74 74 log.debug('Configuring cache region `%s` with settings %s',
75 75 region, region_settings)
76 76 configure_cache_region(
77 77 region, region_settings, cache_settings)
78 78
79 79
80 80 def configure_cache_region(
81 81 region_name, region_settings, default_cache_kw, default_expire=60):
82 82 default_type = default_cache_kw.get('type', 'memory')
83 83 default_lock_dir = default_cache_kw.get('lock_dir')
84 84 default_data_dir = default_cache_kw.get('data_dir')
85 85
86 86 region_settings['lock_dir'] = region_settings.get('lock_dir', default_lock_dir)
87 87 region_settings['data_dir'] = region_settings.get('data_dir', default_data_dir)
88 88 region_settings['type'] = region_settings.get('type', default_type)
89 89 region_settings['expire'] = int(region_settings.get('expire', default_expire))
90 90
91 91 beaker.cache.cache_regions[region_name] = region_settings
92 92
93 93
94 94 def get_cache_manager(region_name, cache_name, custom_ttl=None):
95 95 """
96 96 Creates a Beaker cache manager. Such instance can be used like that::
97 97
98 98 _namespace = caches.get_repo_namespace_key(caches.XXX, repo_name)
99 cache_manager = caches.get_cache_manager('repo_cache_long', _namespace)
99 cache_manager = caches.get_cache_manager('some_namespace_name', _namespace)
100 100 _cache_key = caches.compute_key_from_params(repo_name, commit.raw_id)
101 101 def heavy_compute():
102 102 ...
103 103 result = cache_manager.get(_cache_key, createfunc=heavy_compute)
104 104
105 105 :param region_name: region from ini file
106 106 :param cache_name: custom cache name, usually prefix+repo_name. eg
107 107 file_switcher_repo1
108 108 :param custom_ttl: override .ini file timeout on this cache
109 109 :return: instance of cache manager
110 110 """
111 111
112 112 cache_config = cache_regions.get(region_name, DEFAULT_CACHE_MANAGER_CONFIG)
113 113 if custom_ttl:
114 114 log.debug('Updating region %s with custom ttl: %s',
115 115 region_name, custom_ttl)
116 116 cache_config.update({'expire': custom_ttl})
117 117
118 118 return beaker.cache.Cache._get_cache(cache_name, cache_config)
119 119
120 120
121 121 def clear_cache_manager(cache_manager):
122 122 """
123 123 namespace = 'foobar'
124 cache_manager = get_cache_manager('repo_cache_long', namespace)
124 cache_manager = get_cache_manager('some_namespace_name', namespace)
125 125 clear_cache_manager(cache_manager)
126 126 """
127 127
128 128 log.debug('Clearing all values for cache manager %s', cache_manager)
129 129 cache_manager.clear()
130 130
131 131
132 132 def clear_repo_caches(repo_name):
133 133 # invalidate cache manager for this repo
134 134 for prefix in USED_REPO_CACHES:
135 135 namespace = get_repo_namespace_key(prefix, repo_name)
136 136 cache_manager = get_cache_manager('repo_cache_long', namespace)
137 137 clear_cache_manager(cache_manager)
138 138
139 139
140 140 def compute_key_from_params(*args):
141 141 """
142 142 Helper to compute key from given params to be used in cache manager
143 143 """
144 144 return sha1("_".join(map(safe_str, args)))
145 145
146 146
147 147 def get_repo_namespace_key(prefix, repo_name):
148 148 return '{0}_{1}'.format(prefix, compute_key_from_params(repo_name))
149 149
150 150
151 151 def conditional_cache(region, cache_namespace, condition, func):
152 152 """
153 153 Conditional caching function use like::
154 154 def _c(arg):
155 155 # heavy computation function
156 156 return data
157 157
158 158 # depending on the condition the compute is wrapped in cache or not
159 159 compute = conditional_cache('short_term', 'cache_namespace_id',
160 160 condition=True, func=func)
161 161 return compute(arg)
162 162
163 163 :param region: name of cache region
164 164 :param cache_namespace: cache namespace
165 165 :param condition: condition for cache to be triggered, and
166 166 return data cached
167 167 :param func: wrapped heavy function to compute
168 168
169 169 """
170 170 wrapped = func
171 171 if condition:
172 172 log.debug('conditional_cache: True, wrapping call of '
173 173 'func: %s into %s region cache', region, func)
174 174
175 175 def _cache_wrap(region_name, cache_namespace):
176 176 """Return a caching wrapper"""
177 177
178 178 def decorate(func):
179 179 @functools.wraps(func)
180 180 def cached(*args, **kwargs):
181 181 if kwargs:
182 182 raise AttributeError(
183 183 'Usage of kwargs is not allowed. '
184 184 'Use only positional arguments in wrapped function')
185 185 manager = get_cache_manager(region_name, cache_namespace)
186 186 cache_key = compute_key_from_params(*args)
187 187
188 188 def go():
189 189 return func(*args, **kwargs)
190 190
191 191 # save org function name
192 192 go.__name__ = '_cached_%s' % (func.__name__,)
193 193
194 194 return manager.get(cache_key, createfunc=go)
195 195 return cached
196 196
197 197 return decorate
198 198
199 199 cached_region = _cache_wrap(region, cache_namespace)
200 200 wrapped = cached_region(func)
201 201
202 202 return wrapped
203 203
204 204
205 205 class ActiveRegionCache(object):
206 206 def __init__(self, context):
207 207 self.context = context
208 208
209 209 def invalidate(self, *args, **kwargs):
210 210 return False
211 211
212 212 def compute(self):
213 213 log.debug('Context cache: getting obj %s from cache', self.context)
214 214 return self.context.compute_func(self.context.cache_key)
215 215
216 216
217 217 class FreshRegionCache(ActiveRegionCache):
218 218 def invalidate(self):
219 219 log.debug('Context cache: invalidating cache for %s', self.context)
220 220 region_invalidate(
221 221 self.context.compute_func, None, self.context.cache_key)
222 222 return True
223 223
224 224
225 225 class InvalidationContext(object):
226 226 def __repr__(self):
227 227 return '<InvalidationContext:{}[{}]>'.format(
228 228 safe_str(self.repo_name), safe_str(self.cache_type))
229 229
230 230 def __init__(self, compute_func, repo_name, cache_type,
231 231 raise_exception=False, thread_scoped=False):
232 232 self.compute_func = compute_func
233 233 self.repo_name = repo_name
234 234 self.cache_type = cache_type
235 235 self.cache_key = compute_key_from_params(
236 236 repo_name, cache_type)
237 237 self.raise_exception = raise_exception
238 238
239 239 # Append the thread id to the cache key if this invalidation context
240 240 # should be scoped to the current thread.
241 241 if thread_scoped:
242 242 thread_id = threading.current_thread().ident
243 243 self.cache_key = '{cache_key}_{thread_id}'.format(
244 244 cache_key=self.cache_key, thread_id=thread_id)
245 245
246 246 def get_cache_obj(self):
247 247 cache_key = CacheKey.get_cache_key(
248 248 self.repo_name, self.cache_type)
249 249 cache_obj = CacheKey.get_active_cache(cache_key)
250 250 if not cache_obj:
251 251 cache_obj = CacheKey(cache_key, self.repo_name)
252 252 return cache_obj
253 253
254 254 def __enter__(self):
255 255 """
256 256 Test if current object is valid, and return CacheRegion function
257 257 that does invalidation and calculation
258 258 """
259 259
260 260 self.cache_obj = self.get_cache_obj()
261 261 if self.cache_obj.cache_active:
262 262 # means our cache obj is existing and marked as it's
263 263 # cache is not outdated, we return BaseInvalidator
264 264 self.skip_cache_active_change = True
265 265 return ActiveRegionCache(self)
266 266
267 267 # the key is either not existing or set to False, we return
268 268 # the real invalidator which re-computes value. We additionally set
269 269 # the flag to actually update the Database objects
270 270 self.skip_cache_active_change = False
271 271 return FreshRegionCache(self)
272 272
273 273 def __exit__(self, exc_type, exc_val, exc_tb):
274 274
275 275 if self.skip_cache_active_change:
276 276 return
277 277
278 278 try:
279 279 self.cache_obj.cache_active = True
280 280 Session().add(self.cache_obj)
281 281 Session().commit()
282 282 except IntegrityError:
283 283 # if we catch integrity error, it means we inserted this object
284 284 # assumption is that's really an edge race-condition case and
285 285 # it's safe is to skip it
286 286 Session().rollback()
287 287 except Exception:
288 288 log.exception('Failed to commit on cache key update')
289 289 Session().rollback()
290 290 if self.raise_exception:
291 291 raise
292 292
293 293
294 294 def includeme(config):
295 295 configure_caches(config.registry.settings)
@@ -1,674 +1,662 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2018 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 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 23 It's implemented with basic auth function
24 24 """
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import importlib
30 30 from functools import wraps
31 31 from StringIO import StringIO
32 32 from lxml import etree
33 33
34 34 import time
35 35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 36
37 37 from pyramid.httpexceptions import (
38 38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
39 39 from zope.cachedescriptors.property import Lazy as LazyProperty
40 40
41 41 import rhodecode
42 from rhodecode.authentication.base import (
43 authenticate, get_perms_cache_manager, VCS_TYPE, loadplugin)
44 from rhodecode.lib import caches
42 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
43 from rhodecode.lib import caches, rc_cache
45 44 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
46 45 from rhodecode.lib.base import (
47 46 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
48 47 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
49 48 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
50 49 from rhodecode.lib.middleware import appenlight
51 50 from rhodecode.lib.middleware.utils import scm_app_http
52 51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
53 52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool, safe_unicode
54 53 from rhodecode.lib.vcs.conf import settings as vcs_settings
55 54 from rhodecode.lib.vcs.backends import base
56 55
57 56 from rhodecode.model import meta
58 57 from rhodecode.model.db import User, Repository, PullRequest
59 58 from rhodecode.model.scm import ScmModel
60 59 from rhodecode.model.pull_request import PullRequestModel
61 60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
62 61
63 62 log = logging.getLogger(__name__)
64 63
65 64
66 65 def extract_svn_txn_id(acl_repo_name, data):
67 66 """
68 67 Helper method for extraction of svn txn_id from submited XML data during
69 68 POST operations
70 69 """
71 70 try:
72 71 root = etree.fromstring(data)
73 72 pat = re.compile(r'/txn/(?P<txn_id>.*)')
74 73 for el in root:
75 74 if el.tag == '{DAV:}source':
76 75 for sub_el in el:
77 76 if sub_el.tag == '{DAV:}href':
78 77 match = pat.search(sub_el.text)
79 78 if match:
80 79 svn_tx_id = match.groupdict()['txn_id']
81 80 txn_id = caches.compute_key_from_params(
82 81 acl_repo_name, svn_tx_id)
83 82 return txn_id
84 83 except Exception:
85 84 log.exception('Failed to extract txn_id')
86 85
87 86
88 87 def initialize_generator(factory):
89 88 """
90 89 Initializes the returned generator by draining its first element.
91 90
92 91 This can be used to give a generator an initializer, which is the code
93 92 up to the first yield statement. This decorator enforces that the first
94 93 produced element has the value ``"__init__"`` to make its special
95 94 purpose very explicit in the using code.
96 95 """
97 96
98 97 @wraps(factory)
99 98 def wrapper(*args, **kwargs):
100 99 gen = factory(*args, **kwargs)
101 100 try:
102 101 init = gen.next()
103 102 except StopIteration:
104 103 raise ValueError('Generator must yield at least one element.')
105 104 if init != "__init__":
106 105 raise ValueError('First yielded element must be "__init__".')
107 106 return gen
108 107 return wrapper
109 108
110 109
111 110 class SimpleVCS(object):
112 111 """Common functionality for SCM HTTP handlers."""
113 112
114 113 SCM = 'unknown'
115 114
116 115 acl_repo_name = None
117 116 url_repo_name = None
118 117 vcs_repo_name = None
119 118 rc_extras = {}
120 119
121 120 # We have to handle requests to shadow repositories different than requests
122 121 # to normal repositories. Therefore we have to distinguish them. To do this
123 122 # we use this regex which will match only on URLs pointing to shadow
124 123 # repositories.
125 124 shadow_repo_re = re.compile(
126 125 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
127 126 '(?P<target>{slug_pat})/' # target repo
128 127 'pull-request/(?P<pr_id>\d+)/' # pull request
129 128 'repository$' # shadow repo
130 129 .format(slug_pat=SLUG_RE.pattern))
131 130
132 131 def __init__(self, config, registry):
133 132 self.registry = registry
134 133 self.config = config
135 134 # re-populated by specialized middleware
136 135 self.repo_vcs_config = base.Config()
137 136 self.rhodecode_settings = SettingsModel().get_all_settings(cache=True)
138 137
139 138 registry.rhodecode_settings = self.rhodecode_settings
140 139 # authenticate this VCS request using authfunc
141 140 auth_ret_code_detection = \
142 141 str2bool(self.config.get('auth_ret_code_detection', False))
143 142 self.authenticate = BasicAuth(
144 143 '', authenticate, registry, config.get('auth_ret_code'),
145 144 auth_ret_code_detection)
146 145 self.ip_addr = '0.0.0.0'
147 146
148 147 @LazyProperty
149 148 def global_vcs_config(self):
150 149 try:
151 150 return VcsSettingsModel().get_ui_settings_as_config_obj()
152 151 except Exception:
153 152 return base.Config()
154 153
155 154 @property
156 155 def base_path(self):
157 156 settings_path = self.repo_vcs_config.get(
158 157 *VcsSettingsModel.PATH_SETTING)
159 158
160 159 if not settings_path:
161 160 settings_path = self.global_vcs_config.get(
162 161 *VcsSettingsModel.PATH_SETTING)
163 162
164 163 if not settings_path:
165 164 # try, maybe we passed in explicitly as config option
166 165 settings_path = self.config.get('base_path')
167 166
168 167 if not settings_path:
169 168 raise ValueError('FATAL: base_path is empty')
170 169 return settings_path
171 170
172 171 def set_repo_names(self, environ):
173 172 """
174 173 This will populate the attributes acl_repo_name, url_repo_name,
175 174 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
176 175 shadow) repositories all names are equal. In case of requests to a
177 176 shadow repository the acl-name points to the target repo of the pull
178 177 request and the vcs-name points to the shadow repo file system path.
179 178 The url-name is always the URL used by the vcs client program.
180 179
181 180 Example in case of a shadow repo:
182 181 acl_repo_name = RepoGroup/MyRepo
183 182 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
184 183 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
185 184 """
186 185 # First we set the repo name from URL for all attributes. This is the
187 186 # default if handling normal (non shadow) repo requests.
188 187 self.url_repo_name = self._get_repository_name(environ)
189 188 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
190 189 self.is_shadow_repo = False
191 190
192 191 # Check if this is a request to a shadow repository.
193 192 match = self.shadow_repo_re.match(self.url_repo_name)
194 193 if match:
195 194 match_dict = match.groupdict()
196 195
197 196 # Build acl repo name from regex match.
198 197 acl_repo_name = safe_unicode('{groups}{target}'.format(
199 198 groups=match_dict['groups'] or '',
200 199 target=match_dict['target']))
201 200
202 201 # Retrieve pull request instance by ID from regex match.
203 202 pull_request = PullRequest.get(match_dict['pr_id'])
204 203
205 204 # Only proceed if we got a pull request and if acl repo name from
206 205 # URL equals the target repo name of the pull request.
207 206 if pull_request and \
208 207 (acl_repo_name == pull_request.target_repo.repo_name):
209 208 repo_id = pull_request.target_repo.repo_id
210 209 # Get file system path to shadow repository.
211 210 workspace_id = PullRequestModel()._workspace_id(pull_request)
212 211 target_vcs = pull_request.target_repo.scm_instance()
213 212 vcs_repo_name = target_vcs._get_shadow_repository_path(
214 213 repo_id, workspace_id)
215 214
216 215 # Store names for later usage.
217 216 self.vcs_repo_name = vcs_repo_name
218 217 self.acl_repo_name = acl_repo_name
219 218 self.is_shadow_repo = True
220 219
221 220 log.debug('Setting all VCS repository names: %s', {
222 221 'acl_repo_name': self.acl_repo_name,
223 222 'url_repo_name': self.url_repo_name,
224 223 'vcs_repo_name': self.vcs_repo_name,
225 224 })
226 225
227 226 @property
228 227 def scm_app(self):
229 228 custom_implementation = self.config['vcs.scm_app_implementation']
230 229 if custom_implementation == 'http':
231 230 log.info('Using HTTP implementation of scm app.')
232 231 scm_app_impl = scm_app_http
233 232 else:
234 233 log.info('Using custom implementation of scm_app: "{}"'.format(
235 234 custom_implementation))
236 235 scm_app_impl = importlib.import_module(custom_implementation)
237 236 return scm_app_impl
238 237
239 238 def _get_by_id(self, repo_name):
240 239 """
241 240 Gets a special pattern _<ID> from clone url and tries to replace it
242 241 with a repository_name for support of _<ID> non changeable urls
243 242 """
244 243
245 244 data = repo_name.split('/')
246 245 if len(data) >= 2:
247 246 from rhodecode.model.repo import RepoModel
248 247 by_id_match = RepoModel().get_repo_by_id(repo_name)
249 248 if by_id_match:
250 249 data[1] = by_id_match.repo_name
251 250
252 251 return safe_str('/'.join(data))
253 252
254 253 def _invalidate_cache(self, repo_name):
255 254 """
256 255 Set's cache for this repository for invalidation on next access
257 256
258 257 :param repo_name: full repo name, also a cache key
259 258 """
260 259 ScmModel().mark_for_invalidation(repo_name)
261 260
262 261 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
263 262 db_repo = Repository.get_by_repo_name(repo_name)
264 263 if not db_repo:
265 264 log.debug('Repository `%s` not found inside the database.',
266 265 repo_name)
267 266 return False
268 267
269 268 if db_repo.repo_type != scm_type:
270 269 log.warning(
271 270 'Repository `%s` have incorrect scm_type, expected %s got %s',
272 271 repo_name, db_repo.repo_type, scm_type)
273 272 return False
274 273
275 274 config = db_repo._config
276 275 config.set('extensions', 'largefiles', '')
277 276 return is_valid_repo(
278 277 repo_name, base_path,
279 278 explicit_scm=scm_type, expect_scm=scm_type, config=config)
280 279
281 280 def valid_and_active_user(self, user):
282 281 """
283 282 Checks if that user is not empty, and if it's actually object it checks
284 283 if he's active.
285 284
286 285 :param user: user object or None
287 286 :return: boolean
288 287 """
289 288 if user is None:
290 289 return False
291 290
292 291 elif user.active:
293 292 return True
294 293
295 294 return False
296 295
297 296 @property
298 297 def is_shadow_repo_dir(self):
299 298 return os.path.isdir(self.vcs_repo_name)
300 299
301 300 def _check_permission(self, action, user, repo_name, ip_addr=None,
302 301 plugin_id='', plugin_cache_active=False, cache_ttl=0):
303 302 """
304 303 Checks permissions using action (push/pull) user and repository
305 304 name. If plugin_cache and ttl is set it will use the plugin which
306 305 authenticated the user to store the cached permissions result for N
307 306 amount of seconds as in cache_ttl
308 307
309 308 :param action: push or pull action
310 309 :param user: user instance
311 310 :param repo_name: repository name
312 311 """
313 312
314 # get instance of cache manager configured for a namespace
315 cache_manager = get_perms_cache_manager(
316 custom_ttl=cache_ttl, suffix=user.user_id)
317 313 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
318 314 plugin_id, plugin_cache_active, cache_ttl)
319 315
320 # for environ based password can be empty, but then the validation is
321 # on the server that fills in the env data needed for authentication
322 _perm_calc_hash = caches.compute_key_from_params(
323 plugin_id, action, user.user_id, repo_name, ip_addr)
316 user_id = user.user_id
317 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
318 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
324 319
325 # _authenticate is a wrapper for .auth() method of plugin.
326 # it checks if .auth() sends proper data.
327 # For RhodeCodeExternalAuthPlugin it also maps users to
328 # Database and maps the attributes returned from .auth()
329 # to RhodeCode database. If this function returns data
330 # then auth is correct.
331 start = time.time()
332 log.debug('Running plugin `%s` permissions check', plugin_id)
320 @region.cache_on_arguments(namespace=cache_namespace_uid,
321 expiration_time=cache_ttl,
322 should_cache_fn=lambda v: plugin_cache_active)
323 def compute_perm_vcs(
324 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
333 325
334 def perm_func():
335 """
336 This function is used internally in Cache of Beaker to calculate
337 Results
338 """
339 326 log.debug('auth: calculating permission access now...')
340 327 # check IP
341 328 inherit = user.inherit_default_permissions
342 329 ip_allowed = AuthUser.check_ip_allowed(
343 user.user_id, ip_addr, inherit_from_default=inherit)
330 user_id, ip_addr, inherit_from_default=inherit)
344 331 if ip_allowed:
345 332 log.info('Access for IP:%s allowed', ip_addr)
346 333 else:
347 334 return False
348 335
349 336 if action == 'push':
350 337 perms = ('repository.write', 'repository.admin')
351 338 if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
352 339 return False
353 340
354 341 else:
355 342 # any other action need at least read permission
356 343 perms = (
357 344 'repository.read', 'repository.write', 'repository.admin')
358 345 if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
359 346 return False
360 347
361 348 return True
362 349
363 if plugin_cache_active:
364 log.debug('Trying to fetch cached perms by %s', _perm_calc_hash[:6])
365 perm_result = cache_manager.get(
366 _perm_calc_hash, createfunc=perm_func)
367 else:
368 perm_result = perm_func()
350 start = time.time()
351 log.debug('Running plugin `%s` permissions check', plugin_id)
352
353 # for environ based auth, password can be empty, but then the validation is
354 # on the server that fills in the env data needed for authentication
355 perm_result = compute_perm_vcs(
356 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
369 357
370 358 auth_time = time.time() - start
371 359 log.debug('Permissions for plugin `%s` completed in %.3fs, '
372 360 'expiration time of fetched cache %.1fs.',
373 361 plugin_id, auth_time, cache_ttl)
374 362
375 363 return perm_result
376 364
377 365 def _check_ssl(self, environ, start_response):
378 366 """
379 367 Checks the SSL check flag and returns False if SSL is not present
380 368 and required True otherwise
381 369 """
382 370 org_proto = environ['wsgi._org_proto']
383 371 # check if we have SSL required ! if not it's a bad request !
384 372 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
385 373 if require_ssl and org_proto == 'http':
386 374 log.debug(
387 375 'Bad request: detected protocol is `%s` and '
388 376 'SSL/HTTPS is required.', org_proto)
389 377 return False
390 378 return True
391 379
392 380 def _get_default_cache_ttl(self):
393 381 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
394 382 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
395 383 plugin_settings = plugin.get_settings()
396 384 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
397 385 plugin_settings) or (False, 0)
398 386 return plugin_cache_active, cache_ttl
399 387
400 388 def __call__(self, environ, start_response):
401 389 try:
402 390 return self._handle_request(environ, start_response)
403 391 except Exception:
404 392 log.exception("Exception while handling request")
405 393 appenlight.track_exception(environ)
406 394 return HTTPInternalServerError()(environ, start_response)
407 395 finally:
408 396 meta.Session.remove()
409 397
410 398 def _handle_request(self, environ, start_response):
411 399
412 400 if not self._check_ssl(environ, start_response):
413 401 reason = ('SSL required, while RhodeCode was unable '
414 402 'to detect this as SSL request')
415 403 log.debug('User not allowed to proceed, %s', reason)
416 404 return HTTPNotAcceptable(reason)(environ, start_response)
417 405
418 406 if not self.url_repo_name:
419 407 log.warning('Repository name is empty: %s', self.url_repo_name)
420 408 # failed to get repo name, we fail now
421 409 return HTTPNotFound()(environ, start_response)
422 410 log.debug('Extracted repo name is %s', self.url_repo_name)
423 411
424 412 ip_addr = get_ip_addr(environ)
425 413 user_agent = get_user_agent(environ)
426 414 username = None
427 415
428 416 # skip passing error to error controller
429 417 environ['pylons.status_code_redirect'] = True
430 418
431 419 # ======================================================================
432 420 # GET ACTION PULL or PUSH
433 421 # ======================================================================
434 422 action = self._get_action(environ)
435 423
436 424 # ======================================================================
437 425 # Check if this is a request to a shadow repository of a pull request.
438 426 # In this case only pull action is allowed.
439 427 # ======================================================================
440 428 if self.is_shadow_repo and action != 'pull':
441 429 reason = 'Only pull action is allowed for shadow repositories.'
442 430 log.debug('User not allowed to proceed, %s', reason)
443 431 return HTTPNotAcceptable(reason)(environ, start_response)
444 432
445 433 # Check if the shadow repo actually exists, in case someone refers
446 434 # to it, and it has been deleted because of successful merge.
447 435 if self.is_shadow_repo and not self.is_shadow_repo_dir:
448 436 log.debug(
449 437 'Shadow repo detected, and shadow repo dir `%s` is missing',
450 438 self.is_shadow_repo_dir)
451 439 return HTTPNotFound()(environ, start_response)
452 440
453 441 # ======================================================================
454 442 # CHECK ANONYMOUS PERMISSION
455 443 # ======================================================================
456 444 if action in ['pull', 'push']:
457 445 anonymous_user = User.get_default_user()
458 446 username = anonymous_user.username
459 447 if anonymous_user.active:
460 448 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
461 449 # ONLY check permissions if the user is activated
462 450 anonymous_perm = self._check_permission(
463 451 action, anonymous_user, self.acl_repo_name, ip_addr,
464 452 plugin_id='anonymous_access',
465 453 plugin_cache_active=plugin_cache_active,
466 454 cache_ttl=cache_ttl,
467 455 )
468 456 else:
469 457 anonymous_perm = False
470 458
471 459 if not anonymous_user.active or not anonymous_perm:
472 460 if not anonymous_user.active:
473 461 log.debug('Anonymous access is disabled, running '
474 462 'authentication')
475 463
476 464 if not anonymous_perm:
477 465 log.debug('Not enough credentials to access this '
478 466 'repository as anonymous user')
479 467
480 468 username = None
481 469 # ==============================================================
482 470 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
483 471 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
484 472 # ==============================================================
485 473
486 474 # try to auth based on environ, container auth methods
487 475 log.debug('Running PRE-AUTH for container based authentication')
488 476 pre_auth = authenticate(
489 477 '', '', environ, VCS_TYPE, registry=self.registry,
490 478 acl_repo_name=self.acl_repo_name)
491 479 if pre_auth and pre_auth.get('username'):
492 480 username = pre_auth['username']
493 481 log.debug('PRE-AUTH got %s as username', username)
494 482 if pre_auth:
495 483 log.debug('PRE-AUTH successful from %s',
496 484 pre_auth.get('auth_data', {}).get('_plugin'))
497 485
498 486 # If not authenticated by the container, running basic auth
499 487 # before inject the calling repo_name for special scope checks
500 488 self.authenticate.acl_repo_name = self.acl_repo_name
501 489
502 490 plugin_cache_active, cache_ttl = False, 0
503 491 plugin = None
504 492 if not username:
505 493 self.authenticate.realm = self.authenticate.get_rc_realm()
506 494
507 495 try:
508 496 auth_result = self.authenticate(environ)
509 497 except (UserCreationError, NotAllowedToCreateUserError) as e:
510 498 log.error(e)
511 499 reason = safe_str(e)
512 500 return HTTPNotAcceptable(reason)(environ, start_response)
513 501
514 502 if isinstance(auth_result, dict):
515 503 AUTH_TYPE.update(environ, 'basic')
516 504 REMOTE_USER.update(environ, auth_result['username'])
517 505 username = auth_result['username']
518 506 plugin = auth_result.get('auth_data', {}).get('_plugin')
519 507 log.info(
520 508 'MAIN-AUTH successful for user `%s` from %s plugin',
521 509 username, plugin)
522 510
523 511 plugin_cache_active, cache_ttl = auth_result.get(
524 512 'auth_data', {}).get('_ttl_cache') or (False, 0)
525 513 else:
526 514 return auth_result.wsgi_application(
527 515 environ, start_response)
528 516
529 517
530 518 # ==============================================================
531 519 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
532 520 # ==============================================================
533 521 user = User.get_by_username(username)
534 522 if not self.valid_and_active_user(user):
535 523 return HTTPForbidden()(environ, start_response)
536 524 username = user.username
537 525 user.update_lastactivity()
538 526 meta.Session().commit()
539 527
540 528 # check user attributes for password change flag
541 529 user_obj = user
542 530 if user_obj and user_obj.username != User.DEFAULT_USER and \
543 531 user_obj.user_data.get('force_password_change'):
544 532 reason = 'password change required'
545 533 log.debug('User not allowed to authenticate, %s', reason)
546 534 return HTTPNotAcceptable(reason)(environ, start_response)
547 535
548 536 # check permissions for this repository
549 537 perm = self._check_permission(
550 538 action, user, self.acl_repo_name, ip_addr,
551 539 plugin, plugin_cache_active, cache_ttl)
552 540 if not perm:
553 541 return HTTPForbidden()(environ, start_response)
554 542
555 543 # extras are injected into UI object and later available
556 544 # in hooks executed by RhodeCode
557 545 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
558 546 extras = vcs_operation_context(
559 547 environ, repo_name=self.acl_repo_name, username=username,
560 548 action=action, scm=self.SCM, check_locking=check_locking,
561 549 is_shadow_repo=self.is_shadow_repo
562 550 )
563 551
564 552 # ======================================================================
565 553 # REQUEST HANDLING
566 554 # ======================================================================
567 555 repo_path = os.path.join(
568 556 safe_str(self.base_path), safe_str(self.vcs_repo_name))
569 557 log.debug('Repository path is %s', repo_path)
570 558
571 559 fix_PATH()
572 560
573 561 log.info(
574 562 '%s action on %s repo "%s" by "%s" from %s %s',
575 563 action, self.SCM, safe_str(self.url_repo_name),
576 564 safe_str(username), ip_addr, user_agent)
577 565
578 566 return self._generate_vcs_response(
579 567 environ, start_response, repo_path, extras, action)
580 568
581 569 @initialize_generator
582 570 def _generate_vcs_response(
583 571 self, environ, start_response, repo_path, extras, action):
584 572 """
585 573 Returns a generator for the response content.
586 574
587 575 This method is implemented as a generator, so that it can trigger
588 576 the cache validation after all content sent back to the client. It
589 577 also handles the locking exceptions which will be triggered when
590 578 the first chunk is produced by the underlying WSGI application.
591 579 """
592 580 txn_id = ''
593 581 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
594 582 # case for SVN, we want to re-use the callback daemon port
595 583 # so we use the txn_id, for this we peek the body, and still save
596 584 # it as wsgi.input
597 585 data = environ['wsgi.input'].read()
598 586 environ['wsgi.input'] = StringIO(data)
599 587 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
600 588
601 589 callback_daemon, extras = self._prepare_callback_daemon(
602 590 extras, environ, action, txn_id=txn_id)
603 591 log.debug('HOOKS extras is %s', extras)
604 592
605 593 config = self._create_config(extras, self.acl_repo_name)
606 594 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
607 595 with callback_daemon:
608 596 app.rc_extras = extras
609 597
610 598 try:
611 599 response = app(environ, start_response)
612 600 finally:
613 601 # This statement works together with the decorator
614 602 # "initialize_generator" above. The decorator ensures that
615 603 # we hit the first yield statement before the generator is
616 604 # returned back to the WSGI server. This is needed to
617 605 # ensure that the call to "app" above triggers the
618 606 # needed callback to "start_response" before the
619 607 # generator is actually used.
620 608 yield "__init__"
621 609
622 610 # iter content
623 611 for chunk in response:
624 612 yield chunk
625 613
626 614 try:
627 615 # invalidate cache on push
628 616 if action == 'push':
629 617 self._invalidate_cache(self.url_repo_name)
630 618 finally:
631 619 meta.Session.remove()
632 620
633 621 def _get_repository_name(self, environ):
634 622 """Get repository name out of the environmnent
635 623
636 624 :param environ: WSGI environment
637 625 """
638 626 raise NotImplementedError()
639 627
640 628 def _get_action(self, environ):
641 629 """Map request commands into a pull or push command.
642 630
643 631 :param environ: WSGI environment
644 632 """
645 633 raise NotImplementedError()
646 634
647 635 def _create_wsgi_app(self, repo_path, repo_name, config):
648 636 """Return the WSGI app that will finally handle the request."""
649 637 raise NotImplementedError()
650 638
651 639 def _create_config(self, extras, repo_name):
652 640 """Create a safe config representation."""
653 641 raise NotImplementedError()
654 642
655 643 def _should_use_callback_daemon(self, extras, environ, action):
656 644 return True
657 645
658 646 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
659 647 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
660 648 if not self._should_use_callback_daemon(extras, environ, action):
661 649 # disable callback daemon for actions that don't require it
662 650 direct_calls = True
663 651
664 652 return prepare_callback_daemon(
665 653 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
666 654 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
667 655
668 656
669 657 def _should_check_locking(query_string):
670 658 # this is kind of hacky, but due to how mercurial handles client-server
671 659 # server see all operation on commit; bookmarks, phases and
672 660 # obsolescence marker in different transaction, we don't want to check
673 661 # locking on those
674 662 return query_string not in ['cmd=listkeys']
@@ -1,325 +1,327 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('favicon', '/favicon.ico', []);
16 16 pyroutes.register('robots', '/robots.txt', []);
17 17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 38 pyroutes.register('admin_home', '/_admin', []);
39 39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
49 49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
50 50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
51 51 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
52 52 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
53 53 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
54 54 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
55 55 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
56 56 pyroutes.register('admin_settings', '/_admin/settings', []);
57 57 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
58 58 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
59 59 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
60 60 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
61 61 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
62 62 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
63 63 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
64 64 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
65 65 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
66 66 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
67 67 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
68 68 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
69 69 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
70 70 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
71 71 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
72 72 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
73 73 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
74 74 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
75 75 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
76 76 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
77 77 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
78 78 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
79 79 pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
80 80 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
81 81 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
82 82 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
83 83 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
84 84 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
85 85 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
86 86 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
87 87 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
88 88 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
89 89 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
90 90 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
91 91 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
92 92 pyroutes.register('users', '/_admin/users', []);
93 93 pyroutes.register('users_data', '/_admin/users_data', []);
94 94 pyroutes.register('users_create', '/_admin/users/create', []);
95 95 pyroutes.register('users_new', '/_admin/users/new', []);
96 96 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
97 97 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
98 98 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
99 99 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
100 100 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
101 101 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
102 102 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
103 103 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
104 104 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
105 105 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
106 106 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
107 107 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
108 108 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
109 109 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
110 110 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
111 111 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
112 112 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
113 113 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
114 114 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
115 115 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
116 116 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
117 117 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
118 118 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
119 119 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
120 120 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
121 121 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
122 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
123 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
122 124 pyroutes.register('user_groups', '/_admin/user_groups', []);
123 125 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
124 126 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
125 127 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
126 128 pyroutes.register('repos', '/_admin/repos', []);
127 129 pyroutes.register('repo_new', '/_admin/repos/new', []);
128 130 pyroutes.register('repo_create', '/_admin/repos/create', []);
129 131 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
130 132 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
131 133 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
132 134 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
133 135 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
134 136 pyroutes.register('channelstream_proxy', '/_channelstream', []);
135 137 pyroutes.register('login', '/_admin/login', []);
136 138 pyroutes.register('logout', '/_admin/logout', []);
137 139 pyroutes.register('register', '/_admin/register', []);
138 140 pyroutes.register('reset_password', '/_admin/password_reset', []);
139 141 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
140 142 pyroutes.register('home', '/', []);
141 143 pyroutes.register('user_autocomplete_data', '/_users', []);
142 144 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
143 145 pyroutes.register('repo_list_data', '/_repos', []);
144 146 pyroutes.register('goto_switcher_data', '/_goto_data', []);
145 147 pyroutes.register('markup_preview', '/_markup_preview', []);
146 148 pyroutes.register('journal', '/_admin/journal', []);
147 149 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
148 150 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
149 151 pyroutes.register('journal_public', '/_admin/public_journal', []);
150 152 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
151 153 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
152 154 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
153 155 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
154 156 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
155 157 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
156 158 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
157 159 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
158 160 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
159 161 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
160 162 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
161 163 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
162 164 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
163 165 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
164 166 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
165 167 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
166 168 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
167 169 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
168 170 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
169 171 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
170 172 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
171 173 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
172 174 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
173 175 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
174 176 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
175 177 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
176 178 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
177 179 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
178 180 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
179 181 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
180 182 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
181 183 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
182 184 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
183 185 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 186 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 187 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 188 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 189 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 190 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 191 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 192 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 193 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 194 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 195 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
194 196 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
195 197 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
196 198 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
197 199 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 200 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
199 201 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 202 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
201 203 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
202 204 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
203 205 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
204 206 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
205 207 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
206 208 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
207 209 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
208 210 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
209 211 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
210 212 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
211 213 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
212 214 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
213 215 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
214 216 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
215 217 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
216 218 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
217 219 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
218 220 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
219 221 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
220 222 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
221 223 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
222 224 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
223 225 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
224 226 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
225 227 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
226 228 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
227 229 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
228 230 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
229 231 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
230 232 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
231 233 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
232 234 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
233 235 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
234 236 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
235 237 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
236 238 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
237 239 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
238 240 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
239 241 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
240 242 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
241 243 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
242 244 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
243 245 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
244 246 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
245 247 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
246 248 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
247 249 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
248 250 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
249 251 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
250 252 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
251 253 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
252 254 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
253 255 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
254 256 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
255 257 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
256 258 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
257 259 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
258 260 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
259 261 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
260 262 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
261 263 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
262 264 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
263 265 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
264 266 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
265 267 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
266 268 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
267 269 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
268 270 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
269 271 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
270 272 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
271 273 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
272 274 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
273 275 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
274 276 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
275 277 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
276 278 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
277 279 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
278 280 pyroutes.register('search', '/_admin/search', []);
279 281 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
280 282 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
281 283 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
282 284 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
283 285 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
284 286 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
285 287 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
286 288 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
287 289 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
288 290 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
289 291 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
290 292 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
291 293 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
292 294 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
293 295 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
294 296 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
295 297 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
296 298 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
297 299 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
298 300 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
299 301 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
300 302 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
301 303 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
302 304 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
303 305 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
304 306 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
305 307 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
306 308 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
307 309 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
308 310 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
309 311 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
310 312 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
311 313 pyroutes.register('gists_show', '/_admin/gists', []);
312 314 pyroutes.register('gists_new', '/_admin/gists/new', []);
313 315 pyroutes.register('gists_create', '/_admin/gists/create', []);
314 316 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
315 317 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
316 318 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
317 319 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
318 320 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
319 321 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
320 322 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
321 323 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
322 324 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
323 325 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
324 326 pyroutes.register('apiv2', '/_admin/api', []);
325 327 }
@@ -1,57 +1,58 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s user settings') % c.user.username}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Users'),h.route_path('users'))}
15 15 &raquo;
16 16 % if c.user.active:
17 17 ${c.user.username}
18 18 % else:
19 19 <strike title="${_('This user is set as disabled')}">${c.user.username}</strike>
20 20 % endif
21 21
22 22 </%def>
23 23
24 24 <%def name="menu_bar_nav()">
25 25 ${self.menu_items(active='admin')}
26 26 </%def>
27 27
28 28 <%def name="main()">
29 29 <div class="box user_settings">
30 30 <div class="title">
31 31 ${self.breadcrumbs()}
32 32 </div>
33 33
34 34 ##main
35 35 <div class="sidebar-col-wrapper">
36 36 <div class="sidebar">
37 37 <ul class="nav nav-pills nav-stacked">
38 38 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.route_path('user_edit', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
39 39 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
40 40 <li class="${'active' if c.active in ['ssh_keys','ssh_keys_generate'] else ''}"><a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id)}">${_('SSH Keys')}</a></li>
41 41 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.route_path('user_edit_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
42 42 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.route_path('user_edit_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
43 43 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.route_path('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
44 44 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
45 45 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.route_path('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
46 46 <li class="${'active' if c.active=='groups' else ''}"><a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a></li>
47 47 <li class="${'active' if c.active=='audit' else ''}"><a href="${h.route_path('edit_user_audit_logs', user_id=c.user.user_id)}">${_('Audit logs')}</a></li>
48 <li class="${'active' if c.active=='caches' else ''}"><a href="${h.route_path('edit_user_caches', user_id=c.user.user_id)}">${_('Caches')}</a></li>
48 49 </ul>
49 50 </div>
50 51
51 52 <div class="main-content-full-width">
52 53 <%include file="/admin/users/user_edit_${c.active}.mako"/>
53 54 </div>
54 55 </div>
55 56 </div>
56 57
57 58 </%def>
@@ -1,106 +1,109 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 time
22 22
23 23 import pytest
24 24
25 from rhodecode.lib import caches
26 from rhodecode.lib.memory_lru_debug import MemoryLRUNamespaceManagerBase
25 from rhodecode.lib import rc_cache
27 26
28 27
29 class TestCacheManager(object):
28 @pytest.mark.usefixtures( 'app')
29 class TestCaches(object):
30 30
31 @pytest.mark.parametrize('repo_name', [
32 ('',),
33 (u'',),
34 (u'ac',),
35 ('ac', ),
36 (u'Δ™Δ‡c',),
37 ('Δ…ac',),
31 def test_cache_decorator_init_not_configured(self):
32 with pytest.raises(EnvironmentError):
33 rc_cache.get_or_create_region('dontexist')
34
35 @pytest.mark.parametrize('region_name', [
36 'cache_perms', u'cache_perms',
38 37 ])
39 def test_cache_manager_init(self, repo_name):
40 cache_manager_instance = caches.get_cache_manager(
41 repo_name, 'my_cache')
42 assert cache_manager_instance
43
44 def test_cache_manager_init_undefined_namespace(self):
45 cache_manager_instance = caches.get_cache_manager(
46 'repo_cache_long_undefined', 'my_cache')
47 assert cache_manager_instance
48
49 def_config = caches.DEFAULT_CACHE_MANAGER_CONFIG.copy()
50 def_config.pop('type')
51 assert cache_manager_instance.nsargs == def_config
52
53 assert isinstance(
54 cache_manager_instance.namespace, MemoryLRUNamespaceManagerBase)
38 def test_cache_decorator_init(self, region_name):
39 namespace = region_name
40 cache_region = rc_cache.get_or_create_region(
41 region_name, region_namespace=namespace)
42 assert cache_region
55 43
56 44 @pytest.mark.parametrize('example_input', [
57 45 ('',),
58 46 (u'/ac',),
59 47 (u'/ac', 1, 2, object()),
60 48 (u'/Δ™Δ‡c', 1, 2, object()),
61 49 ('/Δ…ac',),
62 50 (u'/ac', ),
63 51 ])
64 52 def test_cache_manager_create_key(self, example_input):
65 key = caches.compute_key_from_params(*example_input)
53 key = rc_cache.utils.compute_key_from_params(*example_input)
66 54 assert key
67 55
68 def test_store_and_invalidate_value_from_manager(self):
69 cache_manger_instance = caches.get_cache_manager(
70 'repo_cache_long', 'my_cache_store')
56 @pytest.mark.parametrize('example_namespace', [
57 'namespace', None
58 ])
59 @pytest.mark.parametrize('example_input', [
60 ('',),
61 (u'/ac',),
62 (u'/ac', 1, 2, object()),
63 (u'/Δ™Δ‡c', 1, 2, object()),
64 ('/Δ…ac',),
65 (u'/ac', ),
66 ])
67 def test_cache_keygen(self, example_input, example_namespace):
68 def func_wrapped():
69 return 1
70 func = rc_cache.utils.key_generator(example_namespace, func_wrapped)
71 key = func(*example_input)
72 assert key
71 73
72 def compute():
74 def test_store_value_in_cache(self):
75 cache_region = rc_cache.get_or_create_region('cache_perms')
76 # make sure we empty the cache now
77 for key in cache_region.backend.list_keys():
78 cache_region.delete(key)
79
80 assert cache_region.backend.list_keys() == []
81
82 @cache_region.cache_on_arguments(expiration_time=5)
83 def compute(key):
73 84 return time.time()
74 85
75 added_keys = []
76 for i in xrange(3):
77 _cache_key = caches.compute_key_from_params('foo_bar', 'p%s' % i)
78 added_keys.append(_cache_key)
79 for x in xrange(10):
80 cache_manger_instance.get(_cache_key, createfunc=compute)
86 for x in range(10):
87 compute(x)
88
89 assert len(set(cache_region.backend.list_keys())) == 10
81 90
82 for key in added_keys:
83 assert cache_manger_instance[key]
84
85 caches.clear_cache_manager(cache_manger_instance)
86
87 for key in added_keys:
88 assert key not in cache_manger_instance
89 assert len(cache_manger_instance.namespace.keys()) == 0
91 def test_store_and_get_value_from_region(self):
92 cache_region = rc_cache.get_or_create_region('cache_perms')
93 # make sure we empty the cache now
94 for key in cache_region.backend.list_keys():
95 cache_region.delete(key)
96 assert cache_region.backend.list_keys() == []
90 97
91 def test_store_and_get_value_from_manager(self):
92 cache_manger_instance = caches.get_cache_manager(
93 'repo_cache_long', 'my_cache_store')
94
95 _cache_key = caches.compute_key_from_params('foo_bar', 'multicall')
98 @cache_region.cache_on_arguments(expiration_time=5)
99 def compute(key):
100 return time.time()
96 101
97 def compute():
98 return time.time()
102 result = set()
103 for x in range(10):
104 ret = compute('x')
105 result.add(ret)
99 106
100 result = set()
101 for x in xrange(10):
102 ret = cache_manger_instance.get(_cache_key, createfunc=compute)
103 result.add(ret)
104
105 # once computed we have only one value after executing it 10x
106 assert len(result) == 1
107 # once computed we have only one value (the same from cache)
108 # after executing it 10x
109 assert len(result) == 1
@@ -1,687 +1,683 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 = 0.0.0.0
47 47 port = 5000
48 48
49 49 ##########################
50 50 ## GUNICORN WSGI SERVER ##
51 51 ##########################
52 52 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
53 53
54 54 use = egg:gunicorn#main
55 55 ## Sets the number of process workers. You must set `instance_id = *`
56 56 ## when this option is set to more than one worker, recommended
57 57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 58 ## The `instance_id = *` must be set in the [app:main] section below
59 59 #workers = 2
60 60 ## number of threads for each of the worker, must be set to 1 for gevent
61 61 ## generally recommened to be at 1
62 62 #threads = 1
63 63 ## process name
64 64 #proc_name = rhodecode
65 65 ## type of worker class, one of sync, gevent
66 66 ## recommended for bigger setup is using of of other than sync one
67 67 #worker_class = sync
68 68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 69 #worker_connections = 10
70 70 ## max number of requests that worker will handle before being gracefully
71 71 ## restarted, could prevent memory leaks
72 72 #max_requests = 1000
73 73 #max_requests_jitter = 30
74 74 ## amount of time a worker can spend with handling a request before it
75 75 ## gets killed and restarted. Set to 6hrs
76 76 #timeout = 21600
77 77
78 78 ## prefix middleware for RhodeCode.
79 79 ## recommended when using proxy setup.
80 80 ## allows to set RhodeCode under a prefix in server.
81 81 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
82 82 ## And set your prefix like: `prefix = /custom_prefix`
83 83 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
84 84 ## to make your cookies only work on prefix url
85 85 [filter:proxy-prefix]
86 86 use = egg:PasteDeploy#prefix
87 87 prefix = /
88 88
89 89 [app:main]
90 90 is_test = True
91 91 use = egg:rhodecode-enterprise-ce
92 92
93 93 ## enable proxy prefix middleware, defined above
94 94 #filter-with = proxy-prefix
95 95
96 96
97 97 ## RHODECODE PLUGINS ##
98 98 rhodecode.includes = rhodecode.api
99 99
100 100 # api prefix url
101 101 rhodecode.api.url = /_admin/api
102 102
103 103
104 104 ## END RHODECODE PLUGINS ##
105 105
106 106 ## encryption key used to encrypt social plugin tokens,
107 107 ## remote_urls with credentials etc, if not set it defaults to
108 108 ## `beaker.session.secret`
109 109 #rhodecode.encrypted_values.secret =
110 110
111 111 ## decryption strict mode (enabled by default). It controls if decryption raises
112 112 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
113 113 #rhodecode.encrypted_values.strict = false
114 114
115 115 ## return gzipped responses from Rhodecode (static files/application)
116 116 gzip_responses = false
117 117
118 118 ## autogenerate javascript routes file on startup
119 119 generate_js_files = false
120 120
121 121 ## Optional Languages
122 122 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
123 123 lang = en
124 124
125 125 ## perform a full repository scan on each server start, this should be
126 126 ## set to false after first startup, to allow faster server restarts.
127 127 startup.import_repos = true
128 128
129 129 ## Uncomment and set this path to use archive download cache.
130 130 ## Once enabled, generated archives will be cached at this location
131 131 ## and served from the cache during subsequent requests for the same archive of
132 132 ## the repository.
133 133 #archive_cache_dir = /tmp/tarballcache
134 134
135 135 ## URL at which the application is running. This is used for bootstraping
136 136 ## requests in context when no web request is available. Used in ishell, or
137 137 ## SSH calls. Set this for events to receive proper url for SSH calls.
138 138 app.base_url = http://rhodecode.local
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 = false
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 ## git rev filter option, --all is the default filter, if you need to
158 158 ## hide all refs in changelog switch this to --branches --tags
159 159 git_rev_filter = --all
160 160
161 161 # Set to true if your repos are exposed using the dumb protocol
162 162 git_update_server_info = false
163 163
164 164 ## RSS/ATOM feed options
165 165 rss_cut_off_limit = 256000
166 166 rss_items_per_page = 10
167 167 rss_include_diff = false
168 168
169 169 ## gist URL alias, used to create nicer urls for gist. This should be an
170 170 ## url that does rewrites to _admin/gists/{gistid}.
171 171 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
172 172 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
173 173 gist_alias_url =
174 174
175 175 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
176 176 ## used for access.
177 177 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
178 178 ## came from the the logged in user who own this authentication token.
179 179 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
180 180 ## authentication token. Such view would be only accessible when used together
181 181 ## with 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 # RepoCommitsView:repo_commit_raw@TOKEN
191 191 # RepoFilesView:repo_files_diff
192 192 # RepoFilesView:repo_archivefile
193 193 # RepoFilesView:repo_file_raw
194 194 # GistView:*
195 195 api_access_controllers_whitelist =
196 196
197 197 ## default encoding used to convert from and to unicode
198 198 ## can be also a comma separated list of encoding in case of mixed encodings
199 199 default_encoding = UTF-8
200 200
201 201 ## instance-id prefix
202 202 ## a prefix key for this instance used for cache invalidation when running
203 203 ## multiple instances of rhodecode, make sure it's globally unique for
204 204 ## all running rhodecode instances. Leave empty if you don't use it
205 205 instance_id =
206 206
207 207 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
208 208 ## of an authentication plugin also if it is disabled by it's settings.
209 209 ## This could be useful if you are unable to log in to the system due to broken
210 210 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
211 211 ## module to log in again and fix the settings.
212 212 ##
213 213 ## Available builtin plugin IDs (hash is part of the ID):
214 214 ## egg:rhodecode-enterprise-ce#rhodecode
215 215 ## egg:rhodecode-enterprise-ce#pam
216 216 ## egg:rhodecode-enterprise-ce#ldap
217 217 ## egg:rhodecode-enterprise-ce#jasig_cas
218 218 ## egg:rhodecode-enterprise-ce#headers
219 219 ## egg:rhodecode-enterprise-ce#crowd
220 220 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
221 221
222 222 ## alternative return HTTP header for failed authentication. Default HTTP
223 223 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
224 224 ## handling that causing a series of failed authentication calls.
225 225 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
226 226 ## This will be served instead of default 401 on bad authnetication
227 227 auth_ret_code =
228 228
229 229 ## use special detection method when serving auth_ret_code, instead of serving
230 230 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
231 231 ## and then serve auth_ret_code to clients
232 232 auth_ret_code_detection = false
233 233
234 234 ## locking return code. When repository is locked return this HTTP code. 2XX
235 235 ## codes don't break the transactions while 4XX codes do
236 236 lock_ret_code = 423
237 237
238 238 ## allows to change the repository location in settings page
239 239 allow_repo_location_change = true
240 240
241 241 ## allows to setup custom hooks in settings page
242 242 allow_custom_hooks_settings = true
243 243
244 244 ## generated license token, goto license page in RhodeCode settings to obtain
245 245 ## new token
246 246 license_token = abra-cada-bra1-rce3
247 247
248 248 ## supervisor connection uri, for managing supervisor and logs.
249 249 supervisor.uri =
250 250 ## supervisord group name/id we only want this RC instance to handle
251 251 supervisor.group_id = dev
252 252
253 253 ## Display extended labs settings
254 254 labs_settings_active = true
255 255
256 256 ####################################
257 257 ### CELERY CONFIG ####
258 258 ####################################
259 259 use_celery = false
260 260 broker.host = localhost
261 261 broker.vhost = rabbitmqhost
262 262 broker.port = 5672
263 263 broker.user = rabbitmq
264 264 broker.password = qweqwe
265 265
266 266 celery.imports = rhodecode.lib.celerylib.tasks
267 267
268 268 celery.result.backend = amqp
269 269 celery.result.dburi = amqp://
270 270 celery.result.serialier = json
271 271
272 272 #celery.send.task.error.emails = true
273 273 #celery.amqp.task.result.expires = 18000
274 274
275 275 celeryd.concurrency = 2
276 276 #celeryd.log.file = celeryd.log
277 277 celeryd.log.level = debug
278 278 celeryd.max.tasks.per.child = 1
279 279
280 280 ## tasks will never be sent to the queue, but executed locally instead.
281 281 celery.always.eager = false
282 282
283 283 ####################################
284 284 ### BEAKER CACHE ####
285 285 ####################################
286 286 # default cache dir for templates. Putting this into a ramdisk
287 287 ## can boost performance, eg. %(here)s/data_ramdisk
288 288 cache_dir = %(here)s/data
289 289
290 290 ## locking and default file storage for Beaker. Putting this into a ramdisk
291 291 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
292 292 beaker.cache.data_dir = %(here)s/rc/data/cache/beaker_data
293 293 beaker.cache.lock_dir = %(here)s/rc/data/cache/beaker_lock
294 294
295 beaker.cache.regions = short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
296
297 beaker.cache.short_term.type = file
298 beaker.cache.short_term.expire = 0
299 beaker.cache.short_term.key_length = 256
295 beaker.cache.regions = long_term, sql_cache_short, repo_cache_long
300 296
301 297 beaker.cache.long_term.type = memory
302 298 beaker.cache.long_term.expire = 36000
303 299 beaker.cache.long_term.key_length = 256
304 300
305 301 beaker.cache.sql_cache_short.type = memory
306 302 beaker.cache.sql_cache_short.expire = 1
307 303 beaker.cache.sql_cache_short.key_length = 256
308 304
309 ## default is memory cache, configure only if required
310 ## using multi-node or multi-worker setup
311 #beaker.cache.auth_plugins.type = memory
312 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
313 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
314 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
315 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
316 #beaker.cache.auth_plugins.sa.pool_size = 10
317 #beaker.cache.auth_plugins.sa.max_overflow = 0
318
319 305 beaker.cache.repo_cache_long.type = memorylru_base
320 306 beaker.cache.repo_cache_long.max_items = 4096
321 307 beaker.cache.repo_cache_long.expire = 2592000
322 308
323 309 ## default is memorylru_base cache, configure only if required
324 310 ## using multi-node or multi-worker setup
325 311 #beaker.cache.repo_cache_long.type = ext:memcached
326 312 #beaker.cache.repo_cache_long.url = localhost:11211
327 313 #beaker.cache.repo_cache_long.expire = 1209600
328 314 #beaker.cache.repo_cache_long.key_length = 256
329 315
316
317 #####################################
318 ### DOGPILE CACHE ####
319 #####################################
320
321 ## permission tree cache settings
322 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
323 rc_cache.cache_perms.expiration_time = 0
324 rc_cache.cache_perms.arguments.filename = /tmp/rc_cache_1
325
330 326 ####################################
331 327 ### BEAKER SESSION ####
332 328 ####################################
333 329
334 330 ## .session.type is type of storage options for the session, current allowed
335 331 ## types are file, ext:memcached, ext:database, and memory (default).
336 332 beaker.session.type = file
337 333 beaker.session.data_dir = %(here)s/rc/data/sessions/data
338 334
339 335 ## db based session, fast, and allows easy management over logged in users
340 336 #beaker.session.type = ext:database
341 337 #beaker.session.table_name = db_session
342 338 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
343 339 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
344 340 #beaker.session.sa.pool_recycle = 3600
345 341 #beaker.session.sa.echo = false
346 342
347 343 beaker.session.key = rhodecode
348 344 beaker.session.secret = test-rc-uytcxaz
349 345 beaker.session.lock_dir = %(here)s/rc/data/sessions/lock
350 346
351 347 ## Secure encrypted cookie. Requires AES and AES python libraries
352 348 ## you must disable beaker.session.secret to use this
353 349 #beaker.session.encrypt_key = key_for_encryption
354 350 #beaker.session.validate_key = validation_key
355 351
356 352 ## sets session as invalid(also logging out user) if it haven not been
357 353 ## accessed for given amount of time in seconds
358 354 beaker.session.timeout = 2592000
359 355 beaker.session.httponly = true
360 356 ## Path to use for the cookie. Set to prefix if you use prefix middleware
361 357 #beaker.session.cookie_path = /custom_prefix
362 358
363 359 ## uncomment for https secure cookie
364 360 beaker.session.secure = false
365 361
366 362 ## auto save the session to not to use .save()
367 363 beaker.session.auto = false
368 364
369 365 ## default cookie expiration time in seconds, set to `true` to set expire
370 366 ## at browser close
371 367 #beaker.session.cookie_expires = 3600
372 368
373 369 ###################################
374 370 ## SEARCH INDEXING CONFIGURATION ##
375 371 ###################################
376 372 ## Full text search indexer is available in rhodecode-tools under
377 373 ## `rhodecode-tools index` command
378 374
379 375 ## WHOOSH Backend, doesn't require additional services to run
380 376 ## it works good with few dozen repos
381 377 search.module = rhodecode.lib.index.whoosh
382 378 search.location = %(here)s/data/index
383 379
384 380 ########################################
385 381 ### CHANNELSTREAM CONFIG ####
386 382 ########################################
387 383 ## channelstream enables persistent connections and live notification
388 384 ## in the system. It's also used by the chat system
389 385
390 386 channelstream.enabled = false
391 387
392 388 ## server address for channelstream server on the backend
393 389 channelstream.server = 127.0.0.1:9800
394 390 ## location of the channelstream server from outside world
395 391 ## use ws:// for http or wss:// for https. This address needs to be handled
396 392 ## by external HTTP server such as Nginx or Apache
397 393 ## see nginx/apache configuration examples in our docs
398 394 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
399 395 channelstream.secret = secret
400 396 channelstream.history.location = %(here)s/channelstream_history
401 397
402 398 ## Internal application path that Javascript uses to connect into.
403 399 ## If you use proxy-prefix the prefix should be added before /_channelstream
404 400 channelstream.proxy_path = /_channelstream
405 401
406 402
407 403 ###################################
408 404 ## APPENLIGHT CONFIG ##
409 405 ###################################
410 406
411 407 ## Appenlight is tailored to work with RhodeCode, see
412 408 ## http://appenlight.com for details how to obtain an account
413 409
414 410 ## appenlight integration enabled
415 411 appenlight = false
416 412
417 413 appenlight.server_url = https://api.appenlight.com
418 414 appenlight.api_key = YOUR_API_KEY
419 415 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
420 416
421 417 # used for JS client
422 418 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
423 419
424 420 ## TWEAK AMOUNT OF INFO SENT HERE
425 421
426 422 ## enables 404 error logging (default False)
427 423 appenlight.report_404 = false
428 424
429 425 ## time in seconds after request is considered being slow (default 1)
430 426 appenlight.slow_request_time = 1
431 427
432 428 ## record slow requests in application
433 429 ## (needs to be enabled for slow datastore recording and time tracking)
434 430 appenlight.slow_requests = true
435 431
436 432 ## enable hooking to application loggers
437 433 appenlight.logging = true
438 434
439 435 ## minimum log level for log capture
440 436 appenlight.logging.level = WARNING
441 437
442 438 ## send logs only from erroneous/slow requests
443 439 ## (saves API quota for intensive logging)
444 440 appenlight.logging_on_error = false
445 441
446 442 ## list of additonal keywords that should be grabbed from environ object
447 443 ## can be string with comma separated list of words in lowercase
448 444 ## (by default client will always send following info:
449 445 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
450 446 ## start with HTTP* this list be extended with additional keywords here
451 447 appenlight.environ_keys_whitelist =
452 448
453 449 ## list of keywords that should be blanked from request object
454 450 ## can be string with comma separated list of words in lowercase
455 451 ## (by default client will always blank keys that contain following words
456 452 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
457 453 ## this list be extended with additional keywords set here
458 454 appenlight.request_keys_blacklist =
459 455
460 456 ## list of namespaces that should be ignores when gathering log entries
461 457 ## can be string with comma separated list of namespaces
462 458 ## (by default the client ignores own entries: appenlight_client.client)
463 459 appenlight.log_namespace_blacklist =
464 460
465 461
466 462 ################################################################################
467 463 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
468 464 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
469 465 ## execute malicious code after an exception is raised. ##
470 466 ################################################################################
471 467 set debug = false
472 468
473 469
474 470 ##############
475 471 ## STYLING ##
476 472 ##############
477 473 debug_style = false
478 474
479 475 ###########################################
480 476 ### MAIN RHODECODE DATABASE CONFIG ###
481 477 ###########################################
482 478 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
483 479 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test
484 480 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test
485 481 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
486 482
487 483 # see sqlalchemy docs for other advanced settings
488 484
489 485 ## print the sql statements to output
490 486 sqlalchemy.db1.echo = false
491 487 ## recycle the connections after this amount of seconds
492 488 sqlalchemy.db1.pool_recycle = 3600
493 489 sqlalchemy.db1.convert_unicode = true
494 490
495 491 ## the number of connections to keep open inside the connection pool.
496 492 ## 0 indicates no limit
497 493 #sqlalchemy.db1.pool_size = 5
498 494
499 495 ## the number of connections to allow in connection pool "overflow", that is
500 496 ## connections that can be opened above and beyond the pool_size setting,
501 497 ## which defaults to five.
502 498 #sqlalchemy.db1.max_overflow = 10
503 499
504 500
505 501 ##################
506 502 ### VCS CONFIG ###
507 503 ##################
508 504 vcs.server.enable = true
509 505 vcs.server = localhost:9901
510 506
511 507 ## Web server connectivity protocol, responsible for web based VCS operatations
512 508 ## Available protocols are:
513 509 ## `http` - use http-rpc backend (default)
514 510 vcs.server.protocol = http
515 511
516 512 ## Push/Pull operations protocol, available options are:
517 513 ## `http` - use http-rpc backend (default)
518 514 ## `vcsserver.scm_app` - internal app (EE only)
519 515 vcs.scm_app_implementation = http
520 516
521 517 ## Push/Pull operations hooks protocol, available options are:
522 518 ## `http` - use http-rpc backend (default)
523 519 vcs.hooks.protocol = http
524 520 vcs.hooks.host = 127.0.0.1
525 521
526 522 vcs.server.log_level = debug
527 523 ## Start VCSServer with this instance as a subprocess, usefull for development
528 524 vcs.start_server = false
529 525
530 526 ## List of enabled VCS backends, available options are:
531 527 ## `hg` - mercurial
532 528 ## `git` - git
533 529 ## `svn` - subversion
534 530 vcs.backends = hg, git, svn
535 531
536 532 vcs.connection_timeout = 3600
537 533 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
538 534 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
539 535 #vcs.svn.compatible_version = pre-1.8-compatible
540 536
541 537
542 538 ############################################################
543 539 ### Subversion proxy support (mod_dav_svn) ###
544 540 ### Maps RhodeCode repo groups into SVN paths for Apache ###
545 541 ############################################################
546 542 ## Enable or disable the config file generation.
547 543 svn.proxy.generate_config = false
548 544 ## Generate config file with `SVNListParentPath` set to `On`.
549 545 svn.proxy.list_parent_path = true
550 546 ## Set location and file name of generated config file.
551 547 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
552 548 ## Used as a prefix to the `Location` block in the generated config file.
553 549 ## In most cases it should be set to `/`.
554 550 svn.proxy.location_root = /
555 551 ## Command to reload the mod dav svn configuration on change.
556 552 ## Example: `/etc/init.d/apache2 reload`
557 553 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
558 554 ## If the timeout expires before the reload command finishes, the command will
559 555 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
560 556 #svn.proxy.reload_timeout = 10
561 557
562 558 ############################################################
563 559 ### SSH Support Settings ###
564 560 ############################################################
565 561
566 562 ## Defines if the authorized_keys file should be written on any change of
567 563 ## user ssh keys, setting this to false also disables posibility of adding
568 564 ## ssh keys for users from web interface.
569 565 ssh.generate_authorized_keyfile = true
570 566
571 567 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
572 568 # ssh.authorized_keys_ssh_opts =
573 569
574 570 ## File to generate the authorized keys together with options
575 571 ## It is possible to have multiple key files specified in `sshd_config` e.g.
576 572 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
577 573 ssh.authorized_keys_file_path = %(here)s/rc/authorized_keys_rhodecode
578 574
579 575 ## Command to execute the SSH wrapper. The binary is available in the
580 576 ## rhodecode installation directory.
581 577 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
582 578 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
583 579
584 580 ## Allow shell when executing the ssh-wrapper command
585 581 ssh.wrapper_cmd_allow_shell = false
586 582
587 583 ## Enables logging, and detailed output send back to the client. Usefull for
588 584 ## debugging, shouldn't be used in production.
589 585 ssh.enable_debug_logging = false
590 586
591 587 ## Paths to binary executrables, by default they are the names, but we can
592 588 ## override them if we want to use a custom one
593 589 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
594 590 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
595 591 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
596 592
597 593
598 594 ## Dummy marker to add new entries after.
599 595 ## Add any custom entries below. Please don't remove.
600 596 custom.conf = 1
601 597
602 598
603 599 ################################
604 600 ### LOGGING CONFIGURATION ####
605 601 ################################
606 602 [loggers]
607 603 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper
608 604
609 605 [handlers]
610 606 keys = console, console_sql
611 607
612 608 [formatters]
613 609 keys = generic, color_formatter, color_formatter_sql
614 610
615 611 #############
616 612 ## LOGGERS ##
617 613 #############
618 614 [logger_root]
619 615 level = NOTSET
620 616 handlers = console
621 617
622 618 [logger_routes]
623 619 level = DEBUG
624 620 handlers =
625 621 qualname = routes.middleware
626 622 ## "level = DEBUG" logs the route matched and routing variables.
627 623 propagate = 1
628 624
629 625 [logger_beaker]
630 626 level = DEBUG
631 627 handlers =
632 628 qualname = beaker.container
633 629 propagate = 1
634 630
635 631 [logger_rhodecode]
636 632 level = DEBUG
637 633 handlers =
638 634 qualname = rhodecode
639 635 propagate = 1
640 636
641 637 [logger_sqlalchemy]
642 638 level = ERROR
643 639 handlers = console_sql
644 640 qualname = sqlalchemy.engine
645 641 propagate = 0
646 642
647 643 [logger_ssh_wrapper]
648 644 level = DEBUG
649 645 handlers =
650 646 qualname = ssh_wrapper
651 647 propagate = 1
652 648
653 649
654 650 ##############
655 651 ## HANDLERS ##
656 652 ##############
657 653
658 654 [handler_console]
659 655 class = StreamHandler
660 656 args = (sys.stderr,)
661 657 level = DEBUG
662 658 formatter = generic
663 659
664 660 [handler_console_sql]
665 661 class = StreamHandler
666 662 args = (sys.stderr,)
667 663 level = WARN
668 664 formatter = generic
669 665
670 666 ################
671 667 ## FORMATTERS ##
672 668 ################
673 669
674 670 [formatter_generic]
675 671 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
676 672 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
677 673 datefmt = %Y-%m-%d %H:%M:%S
678 674
679 675 [formatter_color_formatter]
680 676 class = rhodecode.lib.logging_formatter.ColorFormatter
681 677 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
682 678 datefmt = %Y-%m-%d %H:%M:%S
683 679
684 680 [formatter_color_formatter_sql]
685 681 class = rhodecode.lib.logging_formatter.ColorFormatterSql
686 682 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
687 683 datefmt = %Y-%m-%d %H:%M:%S
General Comments 0
You need to be logged in to leave comments. Login now