##// 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 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10
10
11 ################################################################################
11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 ## EMAIL CONFIGURATION ##
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
19 #email_prefix = [RhodeCode]
20
20
21 ## email FROM address all mails will be sent
21 ## email FROM address all mails will be sent
22 #app_email_from = rhodecode-noreply@localhost
22 #app_email_from = rhodecode-noreply@localhost
23
23
24 ## Uncomment and replace with the address which should receive any error report
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
26 #email_to = admin@localhost
27
27
28 ## in case of Application errors, sent an error email form
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
29 #error_email_from = rhodecode_error@localhost
30
30
31 ## additional error message to be send in case of server crash
31 ## additional error message to be send in case of server crash
32 #error_message =
32 #error_message =
33
33
34
34
35 #smtp_server = mail.server.com
35 #smtp_server = mail.server.com
36 #smtp_username =
36 #smtp_username =
37 #smtp_password =
37 #smtp_password =
38 #smtp_port =
38 #smtp_port =
39 #smtp_use_tls = false
39 #smtp_use_tls = false
40 #smtp_use_ssl = true
40 #smtp_use_ssl = true
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 #smtp_auth =
42 #smtp_auth =
43
43
44 [server:main]
44 [server:main]
45 ## COMMON ##
45 ## COMMON ##
46 host = 127.0.0.1
46 host = 127.0.0.1
47 port = 5000
47 port = 5000
48
48
49 ##################################
49 ##################################
50 ## WAITRESS WSGI SERVER ##
50 ## WAITRESS WSGI SERVER ##
51 ## Recommended for Development ##
51 ## Recommended for Development ##
52 ##################################
52 ##################################
53
53
54 use = egg:waitress#main
54 use = egg:waitress#main
55 ## number of worker threads
55 ## number of worker threads
56 threads = 5
56 threads = 5
57 ## MAX BODY SIZE 100GB
57 ## MAX BODY SIZE 100GB
58 max_request_body_size = 107374182400
58 max_request_body_size = 107374182400
59 ## Use poll instead of select, fixes file descriptors limits problems.
59 ## Use poll instead of select, fixes file descriptors limits problems.
60 ## May not work on old windows systems.
60 ## May not work on old windows systems.
61 asyncore_use_poll = true
61 asyncore_use_poll = true
62
62
63
63
64 ##########################
64 ##########################
65 ## GUNICORN WSGI SERVER ##
65 ## GUNICORN WSGI SERVER ##
66 ##########################
66 ##########################
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
68
68
69 #use = egg:gunicorn#main
69 #use = egg:gunicorn#main
70 ## Sets the number of process workers. You must set `instance_id = *`
70 ## Sets the number of process workers. You must set `instance_id = *`
71 ## when this option is set to more than one worker, recommended
71 ## when this option is set to more than one worker, recommended
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
73 ## The `instance_id = *` must be set in the [app:main] section below
73 ## The `instance_id = *` must be set in the [app:main] section below
74 #workers = 2
74 #workers = 2
75 ## number of threads for each of the worker, must be set to 1 for gevent
75 ## number of threads for each of the worker, must be set to 1 for gevent
76 ## generally recommended to be at 1
76 ## generally recommended to be at 1
77 #threads = 1
77 #threads = 1
78 ## process name
78 ## process name
79 #proc_name = rhodecode
79 #proc_name = rhodecode
80 ## type of worker class, one of sync, gevent
80 ## type of worker class, one of sync, gevent
81 ## recommended for bigger setup is using of of other than sync one
81 ## recommended for bigger setup is using of of other than sync one
82 #worker_class = gevent
82 #worker_class = gevent
83 ## The maximum number of simultaneous clients. Valid only for Gevent
83 ## The maximum number of simultaneous clients. Valid only for Gevent
84 #worker_connections = 10
84 #worker_connections = 10
85 ## max number of requests that worker will handle before being gracefully
85 ## max number of requests that worker will handle before being gracefully
86 ## restarted, could prevent memory leaks
86 ## restarted, could prevent memory leaks
87 #max_requests = 1000
87 #max_requests = 1000
88 #max_requests_jitter = 30
88 #max_requests_jitter = 30
89 ## amount of time a worker can spend with handling a request before it
89 ## amount of time a worker can spend with handling a request before it
90 ## gets killed and restarted. Set to 6hrs
90 ## gets killed and restarted. Set to 6hrs
91 #timeout = 21600
91 #timeout = 21600
92
92
93
93
94 ## prefix middleware for RhodeCode.
94 ## prefix middleware for RhodeCode.
95 ## recommended when using proxy setup.
95 ## recommended when using proxy setup.
96 ## allows to set RhodeCode under a prefix in server.
96 ## allows to set RhodeCode under a prefix in server.
97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
98 ## And set your prefix like: `prefix = /custom_prefix`
98 ## And set your prefix like: `prefix = /custom_prefix`
99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
100 ## to make your cookies only work on prefix url
100 ## to make your cookies only work on prefix url
101 [filter:proxy-prefix]
101 [filter:proxy-prefix]
102 use = egg:PasteDeploy#prefix
102 use = egg:PasteDeploy#prefix
103 prefix = /
103 prefix = /
104
104
105 [app:main]
105 [app:main]
106 use = egg:rhodecode-enterprise-ce
106 use = egg:rhodecode-enterprise-ce
107
107
108 ## enable proxy prefix middleware, defined above
108 ## enable proxy prefix middleware, defined above
109 #filter-with = proxy-prefix
109 #filter-with = proxy-prefix
110
110
111 # During development the we want to have the debug toolbar enabled
111 # During development the we want to have the debug toolbar enabled
112 pyramid.includes =
112 pyramid.includes =
113 pyramid_debugtoolbar
113 pyramid_debugtoolbar
114 rhodecode.lib.middleware.request_wrapper
114 rhodecode.lib.middleware.request_wrapper
115
115
116 pyramid.reload_templates = true
116 pyramid.reload_templates = true
117
117
118 debugtoolbar.hosts = 0.0.0.0/0
118 debugtoolbar.hosts = 0.0.0.0/0
119 debugtoolbar.exclude_prefixes =
119 debugtoolbar.exclude_prefixes =
120 /css
120 /css
121 /fonts
121 /fonts
122 /images
122 /images
123 /js
123 /js
124
124
125 ## RHODECODE PLUGINS ##
125 ## RHODECODE PLUGINS ##
126 rhodecode.includes =
126 rhodecode.includes =
127 rhodecode.api
127 rhodecode.api
128
128
129
129
130 # api prefix url
130 # api prefix url
131 rhodecode.api.url = /_admin/api
131 rhodecode.api.url = /_admin/api
132
132
133
133
134 ## END RHODECODE PLUGINS ##
134 ## END RHODECODE PLUGINS ##
135
135
136 ## encryption key used to encrypt social plugin tokens,
136 ## encryption key used to encrypt social plugin tokens,
137 ## remote_urls with credentials etc, if not set it defaults to
137 ## remote_urls with credentials etc, if not set it defaults to
138 ## `beaker.session.secret`
138 ## `beaker.session.secret`
139 #rhodecode.encrypted_values.secret =
139 #rhodecode.encrypted_values.secret =
140
140
141 ## decryption strict mode (enabled by default). It controls if decryption raises
141 ## decryption strict mode (enabled by default). It controls if decryption raises
142 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
142 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
143 #rhodecode.encrypted_values.strict = false
143 #rhodecode.encrypted_values.strict = false
144
144
145 ## return gzipped responses from Rhodecode (static files/application)
145 ## return gzipped responses from Rhodecode (static files/application)
146 gzip_responses = false
146 gzip_responses = false
147
147
148 ## autogenerate javascript routes file on startup
148 ## autogenerate javascript routes file on startup
149 generate_js_files = false
149 generate_js_files = false
150
150
151 ## Optional Languages
151 ## Optional Languages
152 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
152 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
153 lang = en
153 lang = en
154
154
155 ## perform a full repository scan on each server start, this should be
155 ## perform a full repository scan on each server start, this should be
156 ## set to false after first startup, to allow faster server restarts.
156 ## set to false after first startup, to allow faster server restarts.
157 startup.import_repos = false
157 startup.import_repos = false
158
158
159 ## Uncomment and set this path to use archive download cache.
159 ## Uncomment and set this path to use archive download cache.
160 ## Once enabled, generated archives will be cached at this location
160 ## Once enabled, generated archives will be cached at this location
161 ## and served from the cache during subsequent requests for the same archive of
161 ## and served from the cache during subsequent requests for the same archive of
162 ## the repository.
162 ## the repository.
163 #archive_cache_dir = /tmp/tarballcache
163 #archive_cache_dir = /tmp/tarballcache
164
164
165 ## URL at which the application is running. This is used for bootstraping
165 ## URL at which the application is running. This is used for bootstraping
166 ## requests in context when no web request is available. Used in ishell, or
166 ## requests in context when no web request is available. Used in ishell, or
167 ## SSH calls. Set this for events to receive proper url for SSH calls.
167 ## SSH calls. Set this for events to receive proper url for SSH calls.
168 app.base_url = http://rhodecode.local
168 app.base_url = http://rhodecode.local
169
169
170 ## change this to unique ID for security
170 ## change this to unique ID for security
171 app_instance_uuid = rc-production
171 app_instance_uuid = rc-production
172
172
173 ## cut off limit for large diffs (size in bytes). If overall diff size on
173 ## cut off limit for large diffs (size in bytes). If overall diff size on
174 ## commit, or pull request exceeds this limit this diff will be displayed
174 ## commit, or pull request exceeds this limit this diff will be displayed
175 ## partially. E.g 512000 == 512Kb
175 ## partially. E.g 512000 == 512Kb
176 cut_off_limit_diff = 512000
176 cut_off_limit_diff = 512000
177
177
178 ## cut off limit for large files inside diffs (size in bytes). Each individual
178 ## cut off limit for large files inside diffs (size in bytes). Each individual
179 ## file inside diff which exceeds this limit will be displayed partially.
179 ## file inside diff which exceeds this limit will be displayed partially.
180 ## E.g 128000 == 128Kb
180 ## E.g 128000 == 128Kb
181 cut_off_limit_file = 128000
181 cut_off_limit_file = 128000
182
182
183 ## use cache version of scm repo everywhere
183 ## use cache version of scm repo everywhere
184 vcs_full_cache = true
184 vcs_full_cache = true
185
185
186 ## force https in RhodeCode, fixes https redirects, assumes it's always https
186 ## force https in RhodeCode, fixes https redirects, assumes it's always https
187 ## Normally this is controlled by proper http flags sent from http server
187 ## Normally this is controlled by proper http flags sent from http server
188 force_https = false
188 force_https = false
189
189
190 ## use Strict-Transport-Security headers
190 ## use Strict-Transport-Security headers
191 use_htsts = false
191 use_htsts = false
192
192
193 ## git rev filter option, --all is the default filter, if you need to
193 ## git rev filter option, --all is the default filter, if you need to
194 ## hide all refs in changelog switch this to --branches --tags
194 ## hide all refs in changelog switch this to --branches --tags
195 git_rev_filter = --branches --tags
195 git_rev_filter = --branches --tags
196
196
197 # Set to true if your repos are exposed using the dumb protocol
197 # Set to true if your repos are exposed using the dumb protocol
198 git_update_server_info = false
198 git_update_server_info = false
199
199
200 ## RSS/ATOM feed options
200 ## RSS/ATOM feed options
201 rss_cut_off_limit = 256000
201 rss_cut_off_limit = 256000
202 rss_items_per_page = 10
202 rss_items_per_page = 10
203 rss_include_diff = false
203 rss_include_diff = false
204
204
205 ## gist URL alias, used to create nicer urls for gist. This should be an
205 ## gist URL alias, used to create nicer urls for gist. This should be an
206 ## url that does rewrites to _admin/gists/{gistid}.
206 ## url that does rewrites to _admin/gists/{gistid}.
207 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
207 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
208 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
208 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
209 gist_alias_url =
209 gist_alias_url =
210
210
211 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
211 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
212 ## used for access.
212 ## used for access.
213 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
213 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
214 ## came from the the logged in user who own this authentication token.
214 ## came from the the logged in user who own this authentication token.
215 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
215 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
216 ## authentication token. Such view would be only accessible when used together
216 ## authentication token. Such view would be only accessible when used together
217 ## with this authentication token
217 ## with this authentication token
218 ##
218 ##
219 ## list of all views can be found under `/_admin/permissions/auth_token_access`
219 ## list of all views can be found under `/_admin/permissions/auth_token_access`
220 ## The list should be "," separated and on a single line.
220 ## The list should be "," separated and on a single line.
221 ##
221 ##
222 ## Most common views to enable:
222 ## Most common views to enable:
223 # RepoCommitsView:repo_commit_download
223 # RepoCommitsView:repo_commit_download
224 # RepoCommitsView:repo_commit_patch
224 # RepoCommitsView:repo_commit_patch
225 # RepoCommitsView:repo_commit_raw
225 # RepoCommitsView:repo_commit_raw
226 # RepoCommitsView:repo_commit_raw@TOKEN
226 # RepoCommitsView:repo_commit_raw@TOKEN
227 # RepoFilesView:repo_files_diff
227 # RepoFilesView:repo_files_diff
228 # RepoFilesView:repo_archivefile
228 # RepoFilesView:repo_archivefile
229 # RepoFilesView:repo_file_raw
229 # RepoFilesView:repo_file_raw
230 # GistView:*
230 # GistView:*
231 api_access_controllers_whitelist =
231 api_access_controllers_whitelist =
232
232
233 ## default encoding used to convert from and to unicode
233 ## default encoding used to convert from and to unicode
234 ## can be also a comma separated list of encoding in case of mixed encodings
234 ## can be also a comma separated list of encoding in case of mixed encodings
235 default_encoding = UTF-8
235 default_encoding = UTF-8
236
236
237 ## instance-id prefix
237 ## instance-id prefix
238 ## a prefix key for this instance used for cache invalidation when running
238 ## a prefix key for this instance used for cache invalidation when running
239 ## multiple instances of rhodecode, make sure it's globally unique for
239 ## multiple instances of rhodecode, make sure it's globally unique for
240 ## all running rhodecode instances. Leave empty if you don't use it
240 ## all running rhodecode instances. Leave empty if you don't use it
241 instance_id =
241 instance_id =
242
242
243 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
243 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
244 ## of an authentication plugin also if it is disabled by it's settings.
244 ## of an authentication plugin also if it is disabled by it's settings.
245 ## This could be useful if you are unable to log in to the system due to broken
245 ## This could be useful if you are unable to log in to the system due to broken
246 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
246 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
247 ## module to log in again and fix the settings.
247 ## module to log in again and fix the settings.
248 ##
248 ##
249 ## Available builtin plugin IDs (hash is part of the ID):
249 ## Available builtin plugin IDs (hash is part of the ID):
250 ## egg:rhodecode-enterprise-ce#rhodecode
250 ## egg:rhodecode-enterprise-ce#rhodecode
251 ## egg:rhodecode-enterprise-ce#pam
251 ## egg:rhodecode-enterprise-ce#pam
252 ## egg:rhodecode-enterprise-ce#ldap
252 ## egg:rhodecode-enterprise-ce#ldap
253 ## egg:rhodecode-enterprise-ce#jasig_cas
253 ## egg:rhodecode-enterprise-ce#jasig_cas
254 ## egg:rhodecode-enterprise-ce#headers
254 ## egg:rhodecode-enterprise-ce#headers
255 ## egg:rhodecode-enterprise-ce#crowd
255 ## egg:rhodecode-enterprise-ce#crowd
256 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
256 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
257
257
258 ## alternative return HTTP header for failed authentication. Default HTTP
258 ## alternative return HTTP header for failed authentication. Default HTTP
259 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
259 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
260 ## handling that causing a series of failed authentication calls.
260 ## handling that causing a series of failed authentication calls.
261 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
261 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
262 ## This will be served instead of default 401 on bad authnetication
262 ## This will be served instead of default 401 on bad authnetication
263 auth_ret_code =
263 auth_ret_code =
264
264
265 ## use special detection method when serving auth_ret_code, instead of serving
265 ## use special detection method when serving auth_ret_code, instead of serving
266 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
266 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
267 ## and then serve auth_ret_code to clients
267 ## and then serve auth_ret_code to clients
268 auth_ret_code_detection = false
268 auth_ret_code_detection = false
269
269
270 ## locking return code. When repository is locked return this HTTP code. 2XX
270 ## locking return code. When repository is locked return this HTTP code. 2XX
271 ## codes don't break the transactions while 4XX codes do
271 ## codes don't break the transactions while 4XX codes do
272 lock_ret_code = 423
272 lock_ret_code = 423
273
273
274 ## allows to change the repository location in settings page
274 ## allows to change the repository location in settings page
275 allow_repo_location_change = true
275 allow_repo_location_change = true
276
276
277 ## allows to setup custom hooks in settings page
277 ## allows to setup custom hooks in settings page
278 allow_custom_hooks_settings = true
278 allow_custom_hooks_settings = true
279
279
280 ## generated license token, goto license page in RhodeCode settings to obtain
280 ## generated license token, goto license page in RhodeCode settings to obtain
281 ## new token
281 ## new token
282 license_token =
282 license_token =
283
283
284 ## supervisor connection uri, for managing supervisor and logs.
284 ## supervisor connection uri, for managing supervisor and logs.
285 supervisor.uri =
285 supervisor.uri =
286 ## supervisord group name/id we only want this RC instance to handle
286 ## supervisord group name/id we only want this RC instance to handle
287 supervisor.group_id = dev
287 supervisor.group_id = dev
288
288
289 ## Display extended labs settings
289 ## Display extended labs settings
290 labs_settings_active = true
290 labs_settings_active = true
291
291
292 ####################################
292 ####################################
293 ### CELERY CONFIG ####
293 ### CELERY CONFIG ####
294 ####################################
294 ####################################
295 ## run: /path/to/celery worker \
295 ## run: /path/to/celery worker \
296 ## -E --beat --app rhodecode.lib.celerylib.loader \
296 ## -E --beat --app rhodecode.lib.celerylib.loader \
297 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
297 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
298 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
298 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
299
299
300 use_celery = false
300 use_celery = false
301
301
302 ## connection url to the message broker (default rabbitmq)
302 ## connection url to the message broker (default rabbitmq)
303 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
303 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
304
304
305 ## maximum tasks to execute before worker restart
305 ## maximum tasks to execute before worker restart
306 celery.max_tasks_per_child = 100
306 celery.max_tasks_per_child = 100
307
307
308 ## tasks will never be sent to the queue, but executed locally instead.
308 ## tasks will never be sent to the queue, but executed locally instead.
309 celery.task_always_eager = false
309 celery.task_always_eager = false
310
310
311 ####################################
311 ####################################
312 ### BEAKER CACHE ####
312 ### BEAKER CACHE ####
313 ####################################
313 ####################################
314 # default cache dir for templates. Putting this into a ramdisk
314 # default cache dir for templates. Putting this into a ramdisk
315 ## can boost performance, eg. %(here)s/data_ramdisk
315 ## can boost performance, eg. %(here)s/data_ramdisk
316 cache_dir = %(here)s/data
316 cache_dir = %(here)s/data
317
317
318 ## locking and default file storage for Beaker. Putting this into a ramdisk
318 ## locking and default file storage for Beaker. Putting this into a ramdisk
319 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
319 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
320 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
320 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
321 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
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
323 beaker.cache.regions = long_term, sql_cache_short, 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
329
324
330 beaker.cache.long_term.type = memory
325 beaker.cache.long_term.type = memory
331 beaker.cache.long_term.expire = 36000
326 beaker.cache.long_term.expire = 36000
332 beaker.cache.long_term.key_length = 256
327 beaker.cache.long_term.key_length = 256
333
328
334 beaker.cache.sql_cache_short.type = memory
329 beaker.cache.sql_cache_short.type = memory
335 beaker.cache.sql_cache_short.expire = 10
330 beaker.cache.sql_cache_short.expire = 10
336 beaker.cache.sql_cache_short.key_length = 256
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 beaker.cache.repo_cache_long.type = memorylru_base
333 beaker.cache.repo_cache_long.type = memorylru_base
349 beaker.cache.repo_cache_long.max_items = 4096
334 beaker.cache.repo_cache_long.max_items = 4096
350 beaker.cache.repo_cache_long.expire = 2592000
335 beaker.cache.repo_cache_long.expire = 2592000
351
336
352 ## default is memorylru_base cache, configure only if required
337 ## default is memorylru_base cache, configure only if required
353 ## using multi-node or multi-worker setup
338 ## using multi-node or multi-worker setup
354 #beaker.cache.repo_cache_long.type = ext:memcached
339 #beaker.cache.repo_cache_long.type = ext:memcached
355 #beaker.cache.repo_cache_long.url = localhost:11211
340 #beaker.cache.repo_cache_long.url = localhost:11211
356 #beaker.cache.repo_cache_long.expire = 1209600
341 #beaker.cache.repo_cache_long.expire = 1209600
357 #beaker.cache.repo_cache_long.key_length = 256
342 #beaker.cache.repo_cache_long.key_length = 256
358
343
359 ####################################
344 ####################################
360 ### BEAKER SESSION ####
345 ### BEAKER SESSION ####
361 ####################################
346 ####################################
362
347
363 ## .session.type is type of storage options for the session, current allowed
348 ## .session.type is type of storage options for the session, current allowed
364 ## types are file, ext:memcached, ext:database, and memory (default).
349 ## types are file, ext:memcached, ext:database, and memory (default).
365 beaker.session.type = file
350 beaker.session.type = file
366 beaker.session.data_dir = %(here)s/data/sessions/data
351 beaker.session.data_dir = %(here)s/data/sessions/data
367
352
368 ## db based session, fast, and allows easy management over logged in users
353 ## db based session, fast, and allows easy management over logged in users
369 #beaker.session.type = ext:database
354 #beaker.session.type = ext:database
370 #beaker.session.table_name = db_session
355 #beaker.session.table_name = db_session
371 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
356 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
372 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
357 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
373 #beaker.session.sa.pool_recycle = 3600
358 #beaker.session.sa.pool_recycle = 3600
374 #beaker.session.sa.echo = false
359 #beaker.session.sa.echo = false
375
360
376 beaker.session.key = rhodecode
361 beaker.session.key = rhodecode
377 beaker.session.secret = develop-rc-uytcxaz
362 beaker.session.secret = develop-rc-uytcxaz
378 beaker.session.lock_dir = %(here)s/data/sessions/lock
363 beaker.session.lock_dir = %(here)s/data/sessions/lock
379
364
380 ## Secure encrypted cookie. Requires AES and AES python libraries
365 ## Secure encrypted cookie. Requires AES and AES python libraries
381 ## you must disable beaker.session.secret to use this
366 ## you must disable beaker.session.secret to use this
382 #beaker.session.encrypt_key = key_for_encryption
367 #beaker.session.encrypt_key = key_for_encryption
383 #beaker.session.validate_key = validation_key
368 #beaker.session.validate_key = validation_key
384
369
385 ## sets session as invalid(also logging out user) if it haven not been
370 ## sets session as invalid(also logging out user) if it haven not been
386 ## accessed for given amount of time in seconds
371 ## accessed for given amount of time in seconds
387 beaker.session.timeout = 2592000
372 beaker.session.timeout = 2592000
388 beaker.session.httponly = true
373 beaker.session.httponly = true
389 ## Path to use for the cookie. Set to prefix if you use prefix middleware
374 ## Path to use for the cookie. Set to prefix if you use prefix middleware
390 #beaker.session.cookie_path = /custom_prefix
375 #beaker.session.cookie_path = /custom_prefix
391
376
392 ## uncomment for https secure cookie
377 ## uncomment for https secure cookie
393 beaker.session.secure = false
378 beaker.session.secure = false
394
379
395 ## auto save the session to not to use .save()
380 ## auto save the session to not to use .save()
396 beaker.session.auto = false
381 beaker.session.auto = false
397
382
398 ## default cookie expiration time in seconds, set to `true` to set expire
383 ## default cookie expiration time in seconds, set to `true` to set expire
399 ## at browser close
384 ## at browser close
400 #beaker.session.cookie_expires = 3600
385 #beaker.session.cookie_expires = 3600
401
386
402 ###################################
387 ###################################
403 ## SEARCH INDEXING CONFIGURATION ##
388 ## SEARCH INDEXING CONFIGURATION ##
404 ###################################
389 ###################################
405 ## Full text search indexer is available in rhodecode-tools under
390 ## Full text search indexer is available in rhodecode-tools under
406 ## `rhodecode-tools index` command
391 ## `rhodecode-tools index` command
407
392
408 ## WHOOSH Backend, doesn't require additional services to run
393 ## WHOOSH Backend, doesn't require additional services to run
409 ## it works good with few dozen repos
394 ## it works good with few dozen repos
410 search.module = rhodecode.lib.index.whoosh
395 search.module = rhodecode.lib.index.whoosh
411 search.location = %(here)s/data/index
396 search.location = %(here)s/data/index
412
397
413 ########################################
398 ########################################
414 ### CHANNELSTREAM CONFIG ####
399 ### CHANNELSTREAM CONFIG ####
415 ########################################
400 ########################################
416 ## channelstream enables persistent connections and live notification
401 ## channelstream enables persistent connections and live notification
417 ## in the system. It's also used by the chat system
402 ## in the system. It's also used by the chat system
418 channelstream.enabled = false
403 channelstream.enabled = false
419
404
420 ## server address for channelstream server on the backend
405 ## server address for channelstream server on the backend
421 channelstream.server = 127.0.0.1:9800
406 channelstream.server = 127.0.0.1:9800
422
407
423 ## location of the channelstream server from outside world
408 ## location of the channelstream server from outside world
424 ## use ws:// for http or wss:// for https. This address needs to be handled
409 ## use ws:// for http or wss:// for https. This address needs to be handled
425 ## by external HTTP server such as Nginx or Apache
410 ## by external HTTP server such as Nginx or Apache
426 ## see nginx/apache configuration examples in our docs
411 ## see nginx/apache configuration examples in our docs
427 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
412 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
428 channelstream.secret = secret
413 channelstream.secret = secret
429 channelstream.history.location = %(here)s/channelstream_history
414 channelstream.history.location = %(here)s/channelstream_history
430
415
431 ## Internal application path that Javascript uses to connect into.
416 ## Internal application path that Javascript uses to connect into.
432 ## If you use proxy-prefix the prefix should be added before /_channelstream
417 ## If you use proxy-prefix the prefix should be added before /_channelstream
433 channelstream.proxy_path = /_channelstream
418 channelstream.proxy_path = /_channelstream
434
419
435
420
436 ###################################
421 ###################################
437 ## APPENLIGHT CONFIG ##
422 ## APPENLIGHT CONFIG ##
438 ###################################
423 ###################################
439
424
440 ## Appenlight is tailored to work with RhodeCode, see
425 ## Appenlight is tailored to work with RhodeCode, see
441 ## http://appenlight.com for details how to obtain an account
426 ## http://appenlight.com for details how to obtain an account
442
427
443 ## appenlight integration enabled
428 ## appenlight integration enabled
444 appenlight = false
429 appenlight = false
445
430
446 appenlight.server_url = https://api.appenlight.com
431 appenlight.server_url = https://api.appenlight.com
447 appenlight.api_key = YOUR_API_KEY
432 appenlight.api_key = YOUR_API_KEY
448 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
433 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
449
434
450 # used for JS client
435 # used for JS client
451 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
436 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
452
437
453 ## TWEAK AMOUNT OF INFO SENT HERE
438 ## TWEAK AMOUNT OF INFO SENT HERE
454
439
455 ## enables 404 error logging (default False)
440 ## enables 404 error logging (default False)
456 appenlight.report_404 = false
441 appenlight.report_404 = false
457
442
458 ## time in seconds after request is considered being slow (default 1)
443 ## time in seconds after request is considered being slow (default 1)
459 appenlight.slow_request_time = 1
444 appenlight.slow_request_time = 1
460
445
461 ## record slow requests in application
446 ## record slow requests in application
462 ## (needs to be enabled for slow datastore recording and time tracking)
447 ## (needs to be enabled for slow datastore recording and time tracking)
463 appenlight.slow_requests = true
448 appenlight.slow_requests = true
464
449
465 ## enable hooking to application loggers
450 ## enable hooking to application loggers
466 appenlight.logging = true
451 appenlight.logging = true
467
452
468 ## minimum log level for log capture
453 ## minimum log level for log capture
469 appenlight.logging.level = WARNING
454 appenlight.logging.level = WARNING
470
455
471 ## send logs only from erroneous/slow requests
456 ## send logs only from erroneous/slow requests
472 ## (saves API quota for intensive logging)
457 ## (saves API quota for intensive logging)
473 appenlight.logging_on_error = false
458 appenlight.logging_on_error = false
474
459
475 ## list of additonal keywords that should be grabbed from environ object
460 ## list of additonal keywords that should be grabbed from environ object
476 ## can be string with comma separated list of words in lowercase
461 ## can be string with comma separated list of words in lowercase
477 ## (by default client will always send following info:
462 ## (by default client will always send following info:
478 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
463 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
479 ## start with HTTP* this list be extended with additional keywords here
464 ## start with HTTP* this list be extended with additional keywords here
480 appenlight.environ_keys_whitelist =
465 appenlight.environ_keys_whitelist =
481
466
482 ## list of keywords that should be blanked from request object
467 ## list of keywords that should be blanked from request object
483 ## can be string with comma separated list of words in lowercase
468 ## can be string with comma separated list of words in lowercase
484 ## (by default client will always blank keys that contain following words
469 ## (by default client will always blank keys that contain following words
485 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
470 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
486 ## this list be extended with additional keywords set here
471 ## this list be extended with additional keywords set here
487 appenlight.request_keys_blacklist =
472 appenlight.request_keys_blacklist =
488
473
489 ## list of namespaces that should be ignores when gathering log entries
474 ## list of namespaces that should be ignores when gathering log entries
490 ## can be string with comma separated list of namespaces
475 ## can be string with comma separated list of namespaces
491 ## (by default the client ignores own entries: appenlight_client.client)
476 ## (by default the client ignores own entries: appenlight_client.client)
492 appenlight.log_namespace_blacklist =
477 appenlight.log_namespace_blacklist =
493
478
494
479
495 ################################################################################
480 ################################################################################
496 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
481 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
497 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
482 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
498 ## execute malicious code after an exception is raised. ##
483 ## execute malicious code after an exception is raised. ##
499 ################################################################################
484 ################################################################################
500 #set debug = false
485 #set debug = false
501
486
502
487
503 ##############
488 ##############
504 ## STYLING ##
489 ## STYLING ##
505 ##############
490 ##############
506 debug_style = true
491 debug_style = true
507
492
508 ###########################################
493 ###########################################
509 ### MAIN RHODECODE DATABASE CONFIG ###
494 ### MAIN RHODECODE DATABASE CONFIG ###
510 ###########################################
495 ###########################################
511 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
496 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
512 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
497 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
513 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
498 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
514 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
499 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
515
500
516 # see sqlalchemy docs for other advanced settings
501 # see sqlalchemy docs for other advanced settings
517
502
518 ## print the sql statements to output
503 ## print the sql statements to output
519 sqlalchemy.db1.echo = false
504 sqlalchemy.db1.echo = false
520 ## recycle the connections after this amount of seconds
505 ## recycle the connections after this amount of seconds
521 sqlalchemy.db1.pool_recycle = 3600
506 sqlalchemy.db1.pool_recycle = 3600
522 sqlalchemy.db1.convert_unicode = true
507 sqlalchemy.db1.convert_unicode = true
523
508
524 ## the number of connections to keep open inside the connection pool.
509 ## the number of connections to keep open inside the connection pool.
525 ## 0 indicates no limit
510 ## 0 indicates no limit
526 #sqlalchemy.db1.pool_size = 5
511 #sqlalchemy.db1.pool_size = 5
527
512
528 ## the number of connections to allow in connection pool "overflow", that is
513 ## the number of connections to allow in connection pool "overflow", that is
529 ## connections that can be opened above and beyond the pool_size setting,
514 ## connections that can be opened above and beyond the pool_size setting,
530 ## which defaults to five.
515 ## which defaults to five.
531 #sqlalchemy.db1.max_overflow = 10
516 #sqlalchemy.db1.max_overflow = 10
532
517
533
518
534 ##################
519 ##################
535 ### VCS CONFIG ###
520 ### VCS CONFIG ###
536 ##################
521 ##################
537 vcs.server.enable = true
522 vcs.server.enable = true
538 vcs.server = localhost:9900
523 vcs.server = localhost:9900
539
524
540 ## Web server connectivity protocol, responsible for web based VCS operatations
525 ## Web server connectivity protocol, responsible for web based VCS operatations
541 ## Available protocols are:
526 ## Available protocols are:
542 ## `http` - use http-rpc backend (default)
527 ## `http` - use http-rpc backend (default)
543 vcs.server.protocol = http
528 vcs.server.protocol = http
544
529
545 ## Push/Pull operations protocol, available options are:
530 ## Push/Pull operations protocol, available options are:
546 ## `http` - use http-rpc backend (default)
531 ## `http` - use http-rpc backend (default)
547 ##
532 ##
548 vcs.scm_app_implementation = http
533 vcs.scm_app_implementation = http
549
534
550 ## Push/Pull operations hooks protocol, available options are:
535 ## Push/Pull operations hooks protocol, available options are:
551 ## `http` - use http-rpc backend (default)
536 ## `http` - use http-rpc backend (default)
552 vcs.hooks.protocol = http
537 vcs.hooks.protocol = http
553
538
554 ## Host on which this instance is listening for hooks. If vcsserver is in other location
539 ## Host on which this instance is listening for hooks. If vcsserver is in other location
555 ## this should be adjusted.
540 ## this should be adjusted.
556 vcs.hooks.host = 127.0.0.1
541 vcs.hooks.host = 127.0.0.1
557
542
558 vcs.server.log_level = debug
543 vcs.server.log_level = debug
559 ## Start VCSServer with this instance as a subprocess, usefull for development
544 ## Start VCSServer with this instance as a subprocess, usefull for development
560 vcs.start_server = false
545 vcs.start_server = false
561
546
562 ## List of enabled VCS backends, available options are:
547 ## List of enabled VCS backends, available options are:
563 ## `hg` - mercurial
548 ## `hg` - mercurial
564 ## `git` - git
549 ## `git` - git
565 ## `svn` - subversion
550 ## `svn` - subversion
566 vcs.backends = hg, git, svn
551 vcs.backends = hg, git, svn
567
552
568 vcs.connection_timeout = 3600
553 vcs.connection_timeout = 3600
569 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
554 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
570 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
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 #vcs.svn.compatible_version = pre-1.8-compatible
556 #vcs.svn.compatible_version = pre-1.8-compatible
572
557
573
558
574 ############################################################
559 ############################################################
575 ### Subversion proxy support (mod_dav_svn) ###
560 ### Subversion proxy support (mod_dav_svn) ###
576 ### Maps RhodeCode repo groups into SVN paths for Apache ###
561 ### Maps RhodeCode repo groups into SVN paths for Apache ###
577 ############################################################
562 ############################################################
578 ## Enable or disable the config file generation.
563 ## Enable or disable the config file generation.
579 svn.proxy.generate_config = false
564 svn.proxy.generate_config = false
580 ## Generate config file with `SVNListParentPath` set to `On`.
565 ## Generate config file with `SVNListParentPath` set to `On`.
581 svn.proxy.list_parent_path = true
566 svn.proxy.list_parent_path = true
582 ## Set location and file name of generated config file.
567 ## Set location and file name of generated config file.
583 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
568 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
584 ## alternative mod_dav config template. This needs to be a mako template
569 ## alternative mod_dav config template. This needs to be a mako template
585 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
570 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
586 ## Used as a prefix to the `Location` block in the generated config file.
571 ## Used as a prefix to the `Location` block in the generated config file.
587 ## In most cases it should be set to `/`.
572 ## In most cases it should be set to `/`.
588 svn.proxy.location_root = /
573 svn.proxy.location_root = /
589 ## Command to reload the mod dav svn configuration on change.
574 ## Command to reload the mod dav svn configuration on change.
590 ## Example: `/etc/init.d/apache2 reload`
575 ## Example: `/etc/init.d/apache2 reload`
591 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
576 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
592 ## If the timeout expires before the reload command finishes, the command will
577 ## If the timeout expires before the reload command finishes, the command will
593 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
578 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
594 #svn.proxy.reload_timeout = 10
579 #svn.proxy.reload_timeout = 10
595
580
596 ############################################################
581 ############################################################
597 ### SSH Support Settings ###
582 ### SSH Support Settings ###
598 ############################################################
583 ############################################################
599
584
600 ## Defines if a custom authorized_keys file should be created and written on
585 ## Defines if a custom authorized_keys file should be created and written on
601 ## any change user ssh keys. Setting this to false also disables posibility
586 ## any change user ssh keys. Setting this to false also disables posibility
602 ## of adding SSH keys by users from web interface. Super admins can still
587 ## of adding SSH keys by users from web interface. Super admins can still
603 ## manage SSH Keys.
588 ## manage SSH Keys.
604 ssh.generate_authorized_keyfile = false
589 ssh.generate_authorized_keyfile = false
605
590
606 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
591 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
607 # ssh.authorized_keys_ssh_opts =
592 # ssh.authorized_keys_ssh_opts =
608
593
609 ## Path to the authrozied_keys file where the generate entries are placed.
594 ## Path to the authrozied_keys file where the generate entries are placed.
610 ## It is possible to have multiple key files specified in `sshd_config` e.g.
595 ## It is possible to have multiple key files specified in `sshd_config` e.g.
611 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
596 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
612 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
597 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
613
598
614 ## Command to execute the SSH wrapper. The binary is available in the
599 ## Command to execute the SSH wrapper. The binary is available in the
615 ## rhodecode installation directory.
600 ## rhodecode installation directory.
616 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
601 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
617 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
602 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
618
603
619 ## Allow shell when executing the ssh-wrapper command
604 ## Allow shell when executing the ssh-wrapper command
620 ssh.wrapper_cmd_allow_shell = false
605 ssh.wrapper_cmd_allow_shell = false
621
606
622 ## Enables logging, and detailed output send back to the client during SSH
607 ## Enables logging, and detailed output send back to the client during SSH
623 ## operations. Usefull for debugging, shouldn't be used in production.
608 ## operations. Usefull for debugging, shouldn't be used in production.
624 ssh.enable_debug_logging = true
609 ssh.enable_debug_logging = true
625
610
626 ## Paths to binary executable, by default they are the names, but we can
611 ## Paths to binary executable, by default they are the names, but we can
627 ## override them if we want to use a custom one
612 ## override them if we want to use a custom one
628 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
613 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
629 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
614 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
630 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
615 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
631
616
632
617
633 ## Dummy marker to add new entries after.
618 ## Dummy marker to add new entries after.
634 ## Add any custom entries below. Please don't remove.
619 ## Add any custom entries below. Please don't remove.
635 custom.conf = 1
620 custom.conf = 1
636
621
637
622
638 ################################
623 ################################
639 ### LOGGING CONFIGURATION ####
624 ### LOGGING CONFIGURATION ####
640 ################################
625 ################################
641 [loggers]
626 [loggers]
642 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper, celery
627 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper, celery
643
628
644 [handlers]
629 [handlers]
645 keys = console, console_sql
630 keys = console, console_sql
646
631
647 [formatters]
632 [formatters]
648 keys = generic, color_formatter, color_formatter_sql
633 keys = generic, color_formatter, color_formatter_sql
649
634
650 #############
635 #############
651 ## LOGGERS ##
636 ## LOGGERS ##
652 #############
637 #############
653 [logger_root]
638 [logger_root]
654 level = NOTSET
639 level = NOTSET
655 handlers = console
640 handlers = console
656
641
657 [logger_sqlalchemy]
642 [logger_sqlalchemy]
658 level = INFO
643 level = INFO
659 handlers = console_sql
644 handlers = console_sql
660 qualname = sqlalchemy.engine
645 qualname = sqlalchemy.engine
661 propagate = 0
646 propagate = 0
662
647
663 [logger_beaker]
648 [logger_beaker]
664 level = DEBUG
649 level = DEBUG
665 handlers =
650 handlers =
666 qualname = beaker.container
651 qualname = beaker.container
667 propagate = 1
652 propagate = 1
668
653
669 [logger_rhodecode]
654 [logger_rhodecode]
670 level = DEBUG
655 level = DEBUG
671 handlers =
656 handlers =
672 qualname = rhodecode
657 qualname = rhodecode
673 propagate = 1
658 propagate = 1
674
659
675 [logger_ssh_wrapper]
660 [logger_ssh_wrapper]
676 level = DEBUG
661 level = DEBUG
677 handlers =
662 handlers =
678 qualname = ssh_wrapper
663 qualname = ssh_wrapper
679 propagate = 1
664 propagate = 1
680
665
681 [logger_celery]
666 [logger_celery]
682 level = DEBUG
667 level = DEBUG
683 handlers =
668 handlers =
684 qualname = celery
669 qualname = celery
685
670
686
671
687 ##############
672 ##############
688 ## HANDLERS ##
673 ## HANDLERS ##
689 ##############
674 ##############
690
675
691 [handler_console]
676 [handler_console]
692 class = StreamHandler
677 class = StreamHandler
693 args = (sys.stderr, )
678 args = (sys.stderr, )
694 level = DEBUG
679 level = DEBUG
695 formatter = color_formatter
680 formatter = color_formatter
696
681
697 [handler_console_sql]
682 [handler_console_sql]
698 class = StreamHandler
683 class = StreamHandler
699 args = (sys.stderr, )
684 args = (sys.stderr, )
700 level = DEBUG
685 level = DEBUG
701 formatter = color_formatter_sql
686 formatter = color_formatter_sql
702
687
703 ################
688 ################
704 ## FORMATTERS ##
689 ## FORMATTERS ##
705 ################
690 ################
706
691
707 [formatter_generic]
692 [formatter_generic]
708 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
693 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
709 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
694 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
710 datefmt = %Y-%m-%d %H:%M:%S
695 datefmt = %Y-%m-%d %H:%M:%S
711
696
712 [formatter_color_formatter]
697 [formatter_color_formatter]
713 class = rhodecode.lib.logging_formatter.ColorFormatter
698 class = rhodecode.lib.logging_formatter.ColorFormatter
714 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
699 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
715 datefmt = %Y-%m-%d %H:%M:%S
700 datefmt = %Y-%m-%d %H:%M:%S
716
701
717 [formatter_color_formatter_sql]
702 [formatter_color_formatter_sql]
718 class = rhodecode.lib.logging_formatter.ColorFormatterSql
703 class = rhodecode.lib.logging_formatter.ColorFormatterSql
719 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
704 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
720 datefmt = %Y-%m-%d %H:%M:%S
705 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,689 +1,674 b''
1
1
2
2
3 ################################################################################
3 ################################################################################
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10
10
11 ################################################################################
11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 ## EMAIL CONFIGURATION ##
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
19 #email_prefix = [RhodeCode]
20
20
21 ## email FROM address all mails will be sent
21 ## email FROM address all mails will be sent
22 #app_email_from = rhodecode-noreply@localhost
22 #app_email_from = rhodecode-noreply@localhost
23
23
24 ## Uncomment and replace with the address which should receive any error report
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
26 #email_to = admin@localhost
27
27
28 ## in case of Application errors, sent an error email form
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
29 #error_email_from = rhodecode_error@localhost
30
30
31 ## additional error message to be send in case of server crash
31 ## additional error message to be send in case of server crash
32 #error_message =
32 #error_message =
33
33
34
34
35 #smtp_server = mail.server.com
35 #smtp_server = mail.server.com
36 #smtp_username =
36 #smtp_username =
37 #smtp_password =
37 #smtp_password =
38 #smtp_port =
38 #smtp_port =
39 #smtp_use_tls = false
39 #smtp_use_tls = false
40 #smtp_use_ssl = true
40 #smtp_use_ssl = true
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 #smtp_auth =
42 #smtp_auth =
43
43
44 [server:main]
44 [server:main]
45 ## COMMON ##
45 ## COMMON ##
46 host = 127.0.0.1
46 host = 127.0.0.1
47 port = 5000
47 port = 5000
48
48
49 ##################################
49 ##################################
50 ## WAITRESS WSGI SERVER ##
50 ## WAITRESS WSGI SERVER ##
51 ## Recommended for Development ##
51 ## Recommended for Development ##
52 ##################################
52 ##################################
53
53
54 #use = egg:waitress#main
54 #use = egg:waitress#main
55 ## number of worker threads
55 ## number of worker threads
56 #threads = 5
56 #threads = 5
57 ## MAX BODY SIZE 100GB
57 ## MAX BODY SIZE 100GB
58 #max_request_body_size = 107374182400
58 #max_request_body_size = 107374182400
59 ## Use poll instead of select, fixes file descriptors limits problems.
59 ## Use poll instead of select, fixes file descriptors limits problems.
60 ## May not work on old windows systems.
60 ## May not work on old windows systems.
61 #asyncore_use_poll = true
61 #asyncore_use_poll = true
62
62
63
63
64 ##########################
64 ##########################
65 ## GUNICORN WSGI SERVER ##
65 ## GUNICORN WSGI SERVER ##
66 ##########################
66 ##########################
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
68
68
69 use = egg:gunicorn#main
69 use = egg:gunicorn#main
70 ## Sets the number of process workers. You must set `instance_id = *`
70 ## Sets the number of process workers. You must set `instance_id = *`
71 ## when this option is set to more than one worker, recommended
71 ## when this option is set to more than one worker, recommended
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
73 ## The `instance_id = *` must be set in the [app:main] section below
73 ## The `instance_id = *` must be set in the [app:main] section below
74 workers = 2
74 workers = 2
75 ## number of threads for each of the worker, must be set to 1 for gevent
75 ## number of threads for each of the worker, must be set to 1 for gevent
76 ## generally recommended to be at 1
76 ## generally recommended to be at 1
77 #threads = 1
77 #threads = 1
78 ## process name
78 ## process name
79 proc_name = rhodecode
79 proc_name = rhodecode
80 ## type of worker class, one of sync, gevent
80 ## type of worker class, one of sync, gevent
81 ## recommended for bigger setup is using of of other than sync one
81 ## recommended for bigger setup is using of of other than sync one
82 worker_class = gevent
82 worker_class = gevent
83 ## The maximum number of simultaneous clients. Valid only for Gevent
83 ## The maximum number of simultaneous clients. Valid only for Gevent
84 #worker_connections = 10
84 #worker_connections = 10
85 ## max number of requests that worker will handle before being gracefully
85 ## max number of requests that worker will handle before being gracefully
86 ## restarted, could prevent memory leaks
86 ## restarted, could prevent memory leaks
87 max_requests = 1000
87 max_requests = 1000
88 max_requests_jitter = 30
88 max_requests_jitter = 30
89 ## amount of time a worker can spend with handling a request before it
89 ## amount of time a worker can spend with handling a request before it
90 ## gets killed and restarted. Set to 6hrs
90 ## gets killed and restarted. Set to 6hrs
91 timeout = 21600
91 timeout = 21600
92
92
93
93
94 ## prefix middleware for RhodeCode.
94 ## prefix middleware for RhodeCode.
95 ## recommended when using proxy setup.
95 ## recommended when using proxy setup.
96 ## allows to set RhodeCode under a prefix in server.
96 ## allows to set RhodeCode under a prefix in server.
97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
98 ## And set your prefix like: `prefix = /custom_prefix`
98 ## And set your prefix like: `prefix = /custom_prefix`
99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
100 ## to make your cookies only work on prefix url
100 ## to make your cookies only work on prefix url
101 [filter:proxy-prefix]
101 [filter:proxy-prefix]
102 use = egg:PasteDeploy#prefix
102 use = egg:PasteDeploy#prefix
103 prefix = /
103 prefix = /
104
104
105 [app:main]
105 [app:main]
106 use = egg:rhodecode-enterprise-ce
106 use = egg:rhodecode-enterprise-ce
107
107
108 ## enable proxy prefix middleware, defined above
108 ## enable proxy prefix middleware, defined above
109 #filter-with = proxy-prefix
109 #filter-with = proxy-prefix
110
110
111 ## encryption key used to encrypt social plugin tokens,
111 ## encryption key used to encrypt social plugin tokens,
112 ## remote_urls with credentials etc, if not set it defaults to
112 ## remote_urls with credentials etc, if not set it defaults to
113 ## `beaker.session.secret`
113 ## `beaker.session.secret`
114 #rhodecode.encrypted_values.secret =
114 #rhodecode.encrypted_values.secret =
115
115
116 ## decryption strict mode (enabled by default). It controls if decryption raises
116 ## decryption strict mode (enabled by default). It controls if decryption raises
117 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
117 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
118 #rhodecode.encrypted_values.strict = false
118 #rhodecode.encrypted_values.strict = false
119
119
120 ## return gzipped responses from Rhodecode (static files/application)
120 ## return gzipped responses from Rhodecode (static files/application)
121 gzip_responses = false
121 gzip_responses = false
122
122
123 ## autogenerate javascript routes file on startup
123 ## autogenerate javascript routes file on startup
124 generate_js_files = false
124 generate_js_files = false
125
125
126 ## Optional Languages
126 ## Optional Languages
127 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
127 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
128 lang = en
128 lang = en
129
129
130 ## perform a full repository scan on each server start, this should be
130 ## perform a full repository scan on each server start, this should be
131 ## set to false after first startup, to allow faster server restarts.
131 ## set to false after first startup, to allow faster server restarts.
132 startup.import_repos = false
132 startup.import_repos = false
133
133
134 ## Uncomment and set this path to use archive download cache.
134 ## Uncomment and set this path to use archive download cache.
135 ## Once enabled, generated archives will be cached at this location
135 ## Once enabled, generated archives will be cached at this location
136 ## and served from the cache during subsequent requests for the same archive of
136 ## and served from the cache during subsequent requests for the same archive of
137 ## the repository.
137 ## the repository.
138 #archive_cache_dir = /tmp/tarballcache
138 #archive_cache_dir = /tmp/tarballcache
139
139
140 ## URL at which the application is running. This is used for bootstraping
140 ## URL at which the application is running. This is used for bootstraping
141 ## requests in context when no web request is available. Used in ishell, or
141 ## requests in context when no web request is available. Used in ishell, or
142 ## SSH calls. Set this for events to receive proper url for SSH calls.
142 ## SSH calls. Set this for events to receive proper url for SSH calls.
143 app.base_url = http://rhodecode.local
143 app.base_url = http://rhodecode.local
144
144
145 ## change this to unique ID for security
145 ## change this to unique ID for security
146 app_instance_uuid = rc-production
146 app_instance_uuid = rc-production
147
147
148 ## cut off limit for large diffs (size in bytes). If overall diff size on
148 ## cut off limit for large diffs (size in bytes). If overall diff size on
149 ## commit, or pull request exceeds this limit this diff will be displayed
149 ## commit, or pull request exceeds this limit this diff will be displayed
150 ## partially. E.g 512000 == 512Kb
150 ## partially. E.g 512000 == 512Kb
151 cut_off_limit_diff = 512000
151 cut_off_limit_diff = 512000
152
152
153 ## cut off limit for large files inside diffs (size in bytes). Each individual
153 ## cut off limit for large files inside diffs (size in bytes). Each individual
154 ## file inside diff which exceeds this limit will be displayed partially.
154 ## file inside diff which exceeds this limit will be displayed partially.
155 ## E.g 128000 == 128Kb
155 ## E.g 128000 == 128Kb
156 cut_off_limit_file = 128000
156 cut_off_limit_file = 128000
157
157
158 ## use cache version of scm repo everywhere
158 ## use cache version of scm repo everywhere
159 vcs_full_cache = true
159 vcs_full_cache = true
160
160
161 ## force https in RhodeCode, fixes https redirects, assumes it's always https
161 ## force https in RhodeCode, fixes https redirects, assumes it's always https
162 ## Normally this is controlled by proper http flags sent from http server
162 ## Normally this is controlled by proper http flags sent from http server
163 force_https = false
163 force_https = false
164
164
165 ## use Strict-Transport-Security headers
165 ## use Strict-Transport-Security headers
166 use_htsts = false
166 use_htsts = false
167
167
168 ## git rev filter option, --all is the default filter, if you need to
168 ## git rev filter option, --all is the default filter, if you need to
169 ## hide all refs in changelog switch this to --branches --tags
169 ## hide all refs in changelog switch this to --branches --tags
170 git_rev_filter = --branches --tags
170 git_rev_filter = --branches --tags
171
171
172 # Set to true if your repos are exposed using the dumb protocol
172 # Set to true if your repos are exposed using the dumb protocol
173 git_update_server_info = false
173 git_update_server_info = false
174
174
175 ## RSS/ATOM feed options
175 ## RSS/ATOM feed options
176 rss_cut_off_limit = 256000
176 rss_cut_off_limit = 256000
177 rss_items_per_page = 10
177 rss_items_per_page = 10
178 rss_include_diff = false
178 rss_include_diff = false
179
179
180 ## gist URL alias, used to create nicer urls for gist. This should be an
180 ## gist URL alias, used to create nicer urls for gist. This should be an
181 ## url that does rewrites to _admin/gists/{gistid}.
181 ## url that does rewrites to _admin/gists/{gistid}.
182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
184 gist_alias_url =
184 gist_alias_url =
185
185
186 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
186 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
187 ## used for access.
187 ## used for access.
188 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
188 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
189 ## came from the the logged in user who own this authentication token.
189 ## came from the the logged in user who own this authentication token.
190 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
190 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
191 ## authentication token. Such view would be only accessible when used together
191 ## authentication token. Such view would be only accessible when used together
192 ## with this authentication token
192 ## with this authentication token
193 ##
193 ##
194 ## list of all views can be found under `/_admin/permissions/auth_token_access`
194 ## list of all views can be found under `/_admin/permissions/auth_token_access`
195 ## The list should be "," separated and on a single line.
195 ## The list should be "," separated and on a single line.
196 ##
196 ##
197 ## Most common views to enable:
197 ## Most common views to enable:
198 # RepoCommitsView:repo_commit_download
198 # RepoCommitsView:repo_commit_download
199 # RepoCommitsView:repo_commit_patch
199 # RepoCommitsView:repo_commit_patch
200 # RepoCommitsView:repo_commit_raw
200 # RepoCommitsView:repo_commit_raw
201 # RepoCommitsView:repo_commit_raw@TOKEN
201 # RepoCommitsView:repo_commit_raw@TOKEN
202 # RepoFilesView:repo_files_diff
202 # RepoFilesView:repo_files_diff
203 # RepoFilesView:repo_archivefile
203 # RepoFilesView:repo_archivefile
204 # RepoFilesView:repo_file_raw
204 # RepoFilesView:repo_file_raw
205 # GistView:*
205 # GistView:*
206 api_access_controllers_whitelist =
206 api_access_controllers_whitelist =
207
207
208 ## default encoding used to convert from and to unicode
208 ## default encoding used to convert from and to unicode
209 ## can be also a comma separated list of encoding in case of mixed encodings
209 ## can be also a comma separated list of encoding in case of mixed encodings
210 default_encoding = UTF-8
210 default_encoding = UTF-8
211
211
212 ## instance-id prefix
212 ## instance-id prefix
213 ## a prefix key for this instance used for cache invalidation when running
213 ## a prefix key for this instance used for cache invalidation when running
214 ## multiple instances of rhodecode, make sure it's globally unique for
214 ## multiple instances of rhodecode, make sure it's globally unique for
215 ## all running rhodecode instances. Leave empty if you don't use it
215 ## all running rhodecode instances. Leave empty if you don't use it
216 instance_id =
216 instance_id =
217
217
218 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
218 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
219 ## of an authentication plugin also if it is disabled by it's settings.
219 ## of an authentication plugin also if it is disabled by it's settings.
220 ## This could be useful if you are unable to log in to the system due to broken
220 ## This could be useful if you are unable to log in to the system due to broken
221 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
221 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
222 ## module to log in again and fix the settings.
222 ## module to log in again and fix the settings.
223 ##
223 ##
224 ## Available builtin plugin IDs (hash is part of the ID):
224 ## Available builtin plugin IDs (hash is part of the ID):
225 ## egg:rhodecode-enterprise-ce#rhodecode
225 ## egg:rhodecode-enterprise-ce#rhodecode
226 ## egg:rhodecode-enterprise-ce#pam
226 ## egg:rhodecode-enterprise-ce#pam
227 ## egg:rhodecode-enterprise-ce#ldap
227 ## egg:rhodecode-enterprise-ce#ldap
228 ## egg:rhodecode-enterprise-ce#jasig_cas
228 ## egg:rhodecode-enterprise-ce#jasig_cas
229 ## egg:rhodecode-enterprise-ce#headers
229 ## egg:rhodecode-enterprise-ce#headers
230 ## egg:rhodecode-enterprise-ce#crowd
230 ## egg:rhodecode-enterprise-ce#crowd
231 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
231 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
232
232
233 ## alternative return HTTP header for failed authentication. Default HTTP
233 ## alternative return HTTP header for failed authentication. Default HTTP
234 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
234 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
235 ## handling that causing a series of failed authentication calls.
235 ## handling that causing a series of failed authentication calls.
236 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
236 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
237 ## This will be served instead of default 401 on bad authnetication
237 ## This will be served instead of default 401 on bad authnetication
238 auth_ret_code =
238 auth_ret_code =
239
239
240 ## use special detection method when serving auth_ret_code, instead of serving
240 ## use special detection method when serving auth_ret_code, instead of serving
241 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
241 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
242 ## and then serve auth_ret_code to clients
242 ## and then serve auth_ret_code to clients
243 auth_ret_code_detection = false
243 auth_ret_code_detection = false
244
244
245 ## locking return code. When repository is locked return this HTTP code. 2XX
245 ## locking return code. When repository is locked return this HTTP code. 2XX
246 ## codes don't break the transactions while 4XX codes do
246 ## codes don't break the transactions while 4XX codes do
247 lock_ret_code = 423
247 lock_ret_code = 423
248
248
249 ## allows to change the repository location in settings page
249 ## allows to change the repository location in settings page
250 allow_repo_location_change = true
250 allow_repo_location_change = true
251
251
252 ## allows to setup custom hooks in settings page
252 ## allows to setup custom hooks in settings page
253 allow_custom_hooks_settings = true
253 allow_custom_hooks_settings = true
254
254
255 ## generated license token, goto license page in RhodeCode settings to obtain
255 ## generated license token, goto license page in RhodeCode settings to obtain
256 ## new token
256 ## new token
257 license_token =
257 license_token =
258
258
259 ## supervisor connection uri, for managing supervisor and logs.
259 ## supervisor connection uri, for managing supervisor and logs.
260 supervisor.uri =
260 supervisor.uri =
261 ## supervisord group name/id we only want this RC instance to handle
261 ## supervisord group name/id we only want this RC instance to handle
262 supervisor.group_id = prod
262 supervisor.group_id = prod
263
263
264 ## Display extended labs settings
264 ## Display extended labs settings
265 labs_settings_active = true
265 labs_settings_active = true
266
266
267 ####################################
267 ####################################
268 ### CELERY CONFIG ####
268 ### CELERY CONFIG ####
269 ####################################
269 ####################################
270 ## run: /path/to/celery worker \
270 ## run: /path/to/celery worker \
271 ## -E --beat --app rhodecode.lib.celerylib.loader \
271 ## -E --beat --app rhodecode.lib.celerylib.loader \
272 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
272 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
273 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
273 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
274
274
275 use_celery = false
275 use_celery = false
276
276
277 ## connection url to the message broker (default rabbitmq)
277 ## connection url to the message broker (default rabbitmq)
278 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
278 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
279
279
280 ## maximum tasks to execute before worker restart
280 ## maximum tasks to execute before worker restart
281 celery.max_tasks_per_child = 100
281 celery.max_tasks_per_child = 100
282
282
283 ## tasks will never be sent to the queue, but executed locally instead.
283 ## tasks will never be sent to the queue, but executed locally instead.
284 celery.task_always_eager = false
284 celery.task_always_eager = false
285
285
286 ####################################
286 ####################################
287 ### BEAKER CACHE ####
287 ### BEAKER CACHE ####
288 ####################################
288 ####################################
289 # default cache dir for templates. Putting this into a ramdisk
289 # default cache dir for templates. Putting this into a ramdisk
290 ## can boost performance, eg. %(here)s/data_ramdisk
290 ## can boost performance, eg. %(here)s/data_ramdisk
291 cache_dir = %(here)s/data
291 cache_dir = %(here)s/data
292
292
293 ## locking and default file storage for Beaker. Putting this into a ramdisk
293 ## locking and default file storage for Beaker. Putting this into a ramdisk
294 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
294 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
295 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
295 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
296 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
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
298 beaker.cache.regions = long_term, sql_cache_short, 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
304
299
305 beaker.cache.long_term.type = memory
300 beaker.cache.long_term.type = memory
306 beaker.cache.long_term.expire = 36000
301 beaker.cache.long_term.expire = 36000
307 beaker.cache.long_term.key_length = 256
302 beaker.cache.long_term.key_length = 256
308
303
309 beaker.cache.sql_cache_short.type = memory
304 beaker.cache.sql_cache_short.type = memory
310 beaker.cache.sql_cache_short.expire = 10
305 beaker.cache.sql_cache_short.expire = 10
311 beaker.cache.sql_cache_short.key_length = 256
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 beaker.cache.repo_cache_long.type = memorylru_base
308 beaker.cache.repo_cache_long.type = memorylru_base
324 beaker.cache.repo_cache_long.max_items = 4096
309 beaker.cache.repo_cache_long.max_items = 4096
325 beaker.cache.repo_cache_long.expire = 2592000
310 beaker.cache.repo_cache_long.expire = 2592000
326
311
327 ## default is memorylru_base cache, configure only if required
312 ## default is memorylru_base cache, configure only if required
328 ## using multi-node or multi-worker setup
313 ## using multi-node or multi-worker setup
329 #beaker.cache.repo_cache_long.type = ext:memcached
314 #beaker.cache.repo_cache_long.type = ext:memcached
330 #beaker.cache.repo_cache_long.url = localhost:11211
315 #beaker.cache.repo_cache_long.url = localhost:11211
331 #beaker.cache.repo_cache_long.expire = 1209600
316 #beaker.cache.repo_cache_long.expire = 1209600
332 #beaker.cache.repo_cache_long.key_length = 256
317 #beaker.cache.repo_cache_long.key_length = 256
333
318
334 ####################################
319 ####################################
335 ### BEAKER SESSION ####
320 ### BEAKER SESSION ####
336 ####################################
321 ####################################
337
322
338 ## .session.type is type of storage options for the session, current allowed
323 ## .session.type is type of storage options for the session, current allowed
339 ## types are file, ext:memcached, ext:database, and memory (default).
324 ## types are file, ext:memcached, ext:database, and memory (default).
340 beaker.session.type = file
325 beaker.session.type = file
341 beaker.session.data_dir = %(here)s/data/sessions/data
326 beaker.session.data_dir = %(here)s/data/sessions/data
342
327
343 ## db based session, fast, and allows easy management over logged in users
328 ## db based session, fast, and allows easy management over logged in users
344 #beaker.session.type = ext:database
329 #beaker.session.type = ext:database
345 #beaker.session.table_name = db_session
330 #beaker.session.table_name = db_session
346 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
331 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
347 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
332 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
348 #beaker.session.sa.pool_recycle = 3600
333 #beaker.session.sa.pool_recycle = 3600
349 #beaker.session.sa.echo = false
334 #beaker.session.sa.echo = false
350
335
351 beaker.session.key = rhodecode
336 beaker.session.key = rhodecode
352 beaker.session.secret = production-rc-uytcxaz
337 beaker.session.secret = production-rc-uytcxaz
353 beaker.session.lock_dir = %(here)s/data/sessions/lock
338 beaker.session.lock_dir = %(here)s/data/sessions/lock
354
339
355 ## Secure encrypted cookie. Requires AES and AES python libraries
340 ## Secure encrypted cookie. Requires AES and AES python libraries
356 ## you must disable beaker.session.secret to use this
341 ## you must disable beaker.session.secret to use this
357 #beaker.session.encrypt_key = key_for_encryption
342 #beaker.session.encrypt_key = key_for_encryption
358 #beaker.session.validate_key = validation_key
343 #beaker.session.validate_key = validation_key
359
344
360 ## sets session as invalid(also logging out user) if it haven not been
345 ## sets session as invalid(also logging out user) if it haven not been
361 ## accessed for given amount of time in seconds
346 ## accessed for given amount of time in seconds
362 beaker.session.timeout = 2592000
347 beaker.session.timeout = 2592000
363 beaker.session.httponly = true
348 beaker.session.httponly = true
364 ## Path to use for the cookie. Set to prefix if you use prefix middleware
349 ## Path to use for the cookie. Set to prefix if you use prefix middleware
365 #beaker.session.cookie_path = /custom_prefix
350 #beaker.session.cookie_path = /custom_prefix
366
351
367 ## uncomment for https secure cookie
352 ## uncomment for https secure cookie
368 beaker.session.secure = false
353 beaker.session.secure = false
369
354
370 ## auto save the session to not to use .save()
355 ## auto save the session to not to use .save()
371 beaker.session.auto = false
356 beaker.session.auto = false
372
357
373 ## default cookie expiration time in seconds, set to `true` to set expire
358 ## default cookie expiration time in seconds, set to `true` to set expire
374 ## at browser close
359 ## at browser close
375 #beaker.session.cookie_expires = 3600
360 #beaker.session.cookie_expires = 3600
376
361
377 ###################################
362 ###################################
378 ## SEARCH INDEXING CONFIGURATION ##
363 ## SEARCH INDEXING CONFIGURATION ##
379 ###################################
364 ###################################
380 ## Full text search indexer is available in rhodecode-tools under
365 ## Full text search indexer is available in rhodecode-tools under
381 ## `rhodecode-tools index` command
366 ## `rhodecode-tools index` command
382
367
383 ## WHOOSH Backend, doesn't require additional services to run
368 ## WHOOSH Backend, doesn't require additional services to run
384 ## it works good with few dozen repos
369 ## it works good with few dozen repos
385 search.module = rhodecode.lib.index.whoosh
370 search.module = rhodecode.lib.index.whoosh
386 search.location = %(here)s/data/index
371 search.location = %(here)s/data/index
387
372
388 ########################################
373 ########################################
389 ### CHANNELSTREAM CONFIG ####
374 ### CHANNELSTREAM CONFIG ####
390 ########################################
375 ########################################
391 ## channelstream enables persistent connections and live notification
376 ## channelstream enables persistent connections and live notification
392 ## in the system. It's also used by the chat system
377 ## in the system. It's also used by the chat system
393 channelstream.enabled = false
378 channelstream.enabled = false
394
379
395 ## server address for channelstream server on the backend
380 ## server address for channelstream server on the backend
396 channelstream.server = 127.0.0.1:9800
381 channelstream.server = 127.0.0.1:9800
397
382
398 ## location of the channelstream server from outside world
383 ## location of the channelstream server from outside world
399 ## use ws:// for http or wss:// for https. This address needs to be handled
384 ## use ws:// for http or wss:// for https. This address needs to be handled
400 ## by external HTTP server such as Nginx or Apache
385 ## by external HTTP server such as Nginx or Apache
401 ## see nginx/apache configuration examples in our docs
386 ## see nginx/apache configuration examples in our docs
402 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
387 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
403 channelstream.secret = secret
388 channelstream.secret = secret
404 channelstream.history.location = %(here)s/channelstream_history
389 channelstream.history.location = %(here)s/channelstream_history
405
390
406 ## Internal application path that Javascript uses to connect into.
391 ## Internal application path that Javascript uses to connect into.
407 ## If you use proxy-prefix the prefix should be added before /_channelstream
392 ## If you use proxy-prefix the prefix should be added before /_channelstream
408 channelstream.proxy_path = /_channelstream
393 channelstream.proxy_path = /_channelstream
409
394
410
395
411 ###################################
396 ###################################
412 ## APPENLIGHT CONFIG ##
397 ## APPENLIGHT CONFIG ##
413 ###################################
398 ###################################
414
399
415 ## Appenlight is tailored to work with RhodeCode, see
400 ## Appenlight is tailored to work with RhodeCode, see
416 ## http://appenlight.com for details how to obtain an account
401 ## http://appenlight.com for details how to obtain an account
417
402
418 ## appenlight integration enabled
403 ## appenlight integration enabled
419 appenlight = false
404 appenlight = false
420
405
421 appenlight.server_url = https://api.appenlight.com
406 appenlight.server_url = https://api.appenlight.com
422 appenlight.api_key = YOUR_API_KEY
407 appenlight.api_key = YOUR_API_KEY
423 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
408 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
424
409
425 # used for JS client
410 # used for JS client
426 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
411 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
427
412
428 ## TWEAK AMOUNT OF INFO SENT HERE
413 ## TWEAK AMOUNT OF INFO SENT HERE
429
414
430 ## enables 404 error logging (default False)
415 ## enables 404 error logging (default False)
431 appenlight.report_404 = false
416 appenlight.report_404 = false
432
417
433 ## time in seconds after request is considered being slow (default 1)
418 ## time in seconds after request is considered being slow (default 1)
434 appenlight.slow_request_time = 1
419 appenlight.slow_request_time = 1
435
420
436 ## record slow requests in application
421 ## record slow requests in application
437 ## (needs to be enabled for slow datastore recording and time tracking)
422 ## (needs to be enabled for slow datastore recording and time tracking)
438 appenlight.slow_requests = true
423 appenlight.slow_requests = true
439
424
440 ## enable hooking to application loggers
425 ## enable hooking to application loggers
441 appenlight.logging = true
426 appenlight.logging = true
442
427
443 ## minimum log level for log capture
428 ## minimum log level for log capture
444 appenlight.logging.level = WARNING
429 appenlight.logging.level = WARNING
445
430
446 ## send logs only from erroneous/slow requests
431 ## send logs only from erroneous/slow requests
447 ## (saves API quota for intensive logging)
432 ## (saves API quota for intensive logging)
448 appenlight.logging_on_error = false
433 appenlight.logging_on_error = false
449
434
450 ## list of additonal keywords that should be grabbed from environ object
435 ## list of additonal keywords that should be grabbed from environ object
451 ## can be string with comma separated list of words in lowercase
436 ## can be string with comma separated list of words in lowercase
452 ## (by default client will always send following info:
437 ## (by default client will always send following info:
453 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
438 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
454 ## start with HTTP* this list be extended with additional keywords here
439 ## start with HTTP* this list be extended with additional keywords here
455 appenlight.environ_keys_whitelist =
440 appenlight.environ_keys_whitelist =
456
441
457 ## list of keywords that should be blanked from request object
442 ## list of keywords that should be blanked from request object
458 ## can be string with comma separated list of words in lowercase
443 ## can be string with comma separated list of words in lowercase
459 ## (by default client will always blank keys that contain following words
444 ## (by default client will always blank keys that contain following words
460 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
445 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
461 ## this list be extended with additional keywords set here
446 ## this list be extended with additional keywords set here
462 appenlight.request_keys_blacklist =
447 appenlight.request_keys_blacklist =
463
448
464 ## list of namespaces that should be ignores when gathering log entries
449 ## list of namespaces that should be ignores when gathering log entries
465 ## can be string with comma separated list of namespaces
450 ## can be string with comma separated list of namespaces
466 ## (by default the client ignores own entries: appenlight_client.client)
451 ## (by default the client ignores own entries: appenlight_client.client)
467 appenlight.log_namespace_blacklist =
452 appenlight.log_namespace_blacklist =
468
453
469
454
470 ################################################################################
455 ################################################################################
471 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
456 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
472 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
457 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
473 ## execute malicious code after an exception is raised. ##
458 ## execute malicious code after an exception is raised. ##
474 ################################################################################
459 ################################################################################
475 set debug = false
460 set debug = false
476
461
477
462
478 ###########################################
463 ###########################################
479 ### MAIN RHODECODE DATABASE CONFIG ###
464 ### MAIN RHODECODE DATABASE CONFIG ###
480 ###########################################
465 ###########################################
481 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
466 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
482 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
467 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
483 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
468 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
484 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
469 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
485
470
486 # see sqlalchemy docs for other advanced settings
471 # see sqlalchemy docs for other advanced settings
487
472
488 ## print the sql statements to output
473 ## print the sql statements to output
489 sqlalchemy.db1.echo = false
474 sqlalchemy.db1.echo = false
490 ## recycle the connections after this amount of seconds
475 ## recycle the connections after this amount of seconds
491 sqlalchemy.db1.pool_recycle = 3600
476 sqlalchemy.db1.pool_recycle = 3600
492 sqlalchemy.db1.convert_unicode = true
477 sqlalchemy.db1.convert_unicode = true
493
478
494 ## the number of connections to keep open inside the connection pool.
479 ## the number of connections to keep open inside the connection pool.
495 ## 0 indicates no limit
480 ## 0 indicates no limit
496 #sqlalchemy.db1.pool_size = 5
481 #sqlalchemy.db1.pool_size = 5
497
482
498 ## the number of connections to allow in connection pool "overflow", that is
483 ## the number of connections to allow in connection pool "overflow", that is
499 ## connections that can be opened above and beyond the pool_size setting,
484 ## connections that can be opened above and beyond the pool_size setting,
500 ## which defaults to five.
485 ## which defaults to five.
501 #sqlalchemy.db1.max_overflow = 10
486 #sqlalchemy.db1.max_overflow = 10
502
487
503
488
504 ##################
489 ##################
505 ### VCS CONFIG ###
490 ### VCS CONFIG ###
506 ##################
491 ##################
507 vcs.server.enable = true
492 vcs.server.enable = true
508 vcs.server = localhost:9900
493 vcs.server = localhost:9900
509
494
510 ## Web server connectivity protocol, responsible for web based VCS operatations
495 ## Web server connectivity protocol, responsible for web based VCS operatations
511 ## Available protocols are:
496 ## Available protocols are:
512 ## `http` - use http-rpc backend (default)
497 ## `http` - use http-rpc backend (default)
513 vcs.server.protocol = http
498 vcs.server.protocol = http
514
499
515 ## Push/Pull operations protocol, available options are:
500 ## Push/Pull operations protocol, available options are:
516 ## `http` - use http-rpc backend (default)
501 ## `http` - use http-rpc backend (default)
517 ##
502 ##
518 vcs.scm_app_implementation = http
503 vcs.scm_app_implementation = http
519
504
520 ## Push/Pull operations hooks protocol, available options are:
505 ## Push/Pull operations hooks protocol, available options are:
521 ## `http` - use http-rpc backend (default)
506 ## `http` - use http-rpc backend (default)
522 vcs.hooks.protocol = http
507 vcs.hooks.protocol = http
523 ## Host on which this instance is listening for hooks. If vcsserver is in other location
508 ## Host on which this instance is listening for hooks. If vcsserver is in other location
524 ## this should be adjusted.
509 ## this should be adjusted.
525 vcs.hooks.host = 127.0.0.1
510 vcs.hooks.host = 127.0.0.1
526
511
527 vcs.server.log_level = info
512 vcs.server.log_level = info
528 ## Start VCSServer with this instance as a subprocess, usefull for development
513 ## Start VCSServer with this instance as a subprocess, usefull for development
529 vcs.start_server = false
514 vcs.start_server = false
530
515
531 ## List of enabled VCS backends, available options are:
516 ## List of enabled VCS backends, available options are:
532 ## `hg` - mercurial
517 ## `hg` - mercurial
533 ## `git` - git
518 ## `git` - git
534 ## `svn` - subversion
519 ## `svn` - subversion
535 vcs.backends = hg, git, svn
520 vcs.backends = hg, git, svn
536
521
537 vcs.connection_timeout = 3600
522 vcs.connection_timeout = 3600
538 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
523 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
539 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
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 #vcs.svn.compatible_version = pre-1.8-compatible
525 #vcs.svn.compatible_version = pre-1.8-compatible
541
526
542
527
543 ############################################################
528 ############################################################
544 ### Subversion proxy support (mod_dav_svn) ###
529 ### Subversion proxy support (mod_dav_svn) ###
545 ### Maps RhodeCode repo groups into SVN paths for Apache ###
530 ### Maps RhodeCode repo groups into SVN paths for Apache ###
546 ############################################################
531 ############################################################
547 ## Enable or disable the config file generation.
532 ## Enable or disable the config file generation.
548 svn.proxy.generate_config = false
533 svn.proxy.generate_config = false
549 ## Generate config file with `SVNListParentPath` set to `On`.
534 ## Generate config file with `SVNListParentPath` set to `On`.
550 svn.proxy.list_parent_path = true
535 svn.proxy.list_parent_path = true
551 ## Set location and file name of generated config file.
536 ## Set location and file name of generated config file.
552 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
537 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
553 ## alternative mod_dav config template. This needs to be a mako template
538 ## alternative mod_dav config template. This needs to be a mako template
554 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
539 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
555 ## Used as a prefix to the `Location` block in the generated config file.
540 ## Used as a prefix to the `Location` block in the generated config file.
556 ## In most cases it should be set to `/`.
541 ## In most cases it should be set to `/`.
557 svn.proxy.location_root = /
542 svn.proxy.location_root = /
558 ## Command to reload the mod dav svn configuration on change.
543 ## Command to reload the mod dav svn configuration on change.
559 ## Example: `/etc/init.d/apache2 reload`
544 ## Example: `/etc/init.d/apache2 reload`
560 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
545 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
561 ## If the timeout expires before the reload command finishes, the command will
546 ## If the timeout expires before the reload command finishes, the command will
562 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
547 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
563 #svn.proxy.reload_timeout = 10
548 #svn.proxy.reload_timeout = 10
564
549
565 ############################################################
550 ############################################################
566 ### SSH Support Settings ###
551 ### SSH Support Settings ###
567 ############################################################
552 ############################################################
568
553
569 ## Defines if a custom authorized_keys file should be created and written on
554 ## Defines if a custom authorized_keys file should be created and written on
570 ## any change user ssh keys. Setting this to false also disables posibility
555 ## any change user ssh keys. Setting this to false also disables posibility
571 ## of adding SSH keys by users from web interface. Super admins can still
556 ## of adding SSH keys by users from web interface. Super admins can still
572 ## manage SSH Keys.
557 ## manage SSH Keys.
573 ssh.generate_authorized_keyfile = false
558 ssh.generate_authorized_keyfile = false
574
559
575 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
560 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
576 # ssh.authorized_keys_ssh_opts =
561 # ssh.authorized_keys_ssh_opts =
577
562
578 ## Path to the authrozied_keys file where the generate entries are placed.
563 ## Path to the authrozied_keys file where the generate entries are placed.
579 ## It is possible to have multiple key files specified in `sshd_config` e.g.
564 ## It is possible to have multiple key files specified in `sshd_config` e.g.
580 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
565 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
581 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
566 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
582
567
583 ## Command to execute the SSH wrapper. The binary is available in the
568 ## Command to execute the SSH wrapper. The binary is available in the
584 ## rhodecode installation directory.
569 ## rhodecode installation directory.
585 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
570 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
586 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
571 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
587
572
588 ## Allow shell when executing the ssh-wrapper command
573 ## Allow shell when executing the ssh-wrapper command
589 ssh.wrapper_cmd_allow_shell = false
574 ssh.wrapper_cmd_allow_shell = false
590
575
591 ## Enables logging, and detailed output send back to the client during SSH
576 ## Enables logging, and detailed output send back to the client during SSH
592 ## operations. Usefull for debugging, shouldn't be used in production.
577 ## operations. Usefull for debugging, shouldn't be used in production.
593 ssh.enable_debug_logging = false
578 ssh.enable_debug_logging = false
594
579
595 ## Paths to binary executable, by default they are the names, but we can
580 ## Paths to binary executable, by default they are the names, but we can
596 ## override them if we want to use a custom one
581 ## override them if we want to use a custom one
597 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
582 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
598 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
583 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
599 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
584 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
600
585
601
586
602 ## Dummy marker to add new entries after.
587 ## Dummy marker to add new entries after.
603 ## Add any custom entries below. Please don't remove.
588 ## Add any custom entries below. Please don't remove.
604 custom.conf = 1
589 custom.conf = 1
605
590
606
591
607 ################################
592 ################################
608 ### LOGGING CONFIGURATION ####
593 ### LOGGING CONFIGURATION ####
609 ################################
594 ################################
610 [loggers]
595 [loggers]
611 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper, celery
596 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper, celery
612
597
613 [handlers]
598 [handlers]
614 keys = console, console_sql
599 keys = console, console_sql
615
600
616 [formatters]
601 [formatters]
617 keys = generic, color_formatter, color_formatter_sql
602 keys = generic, color_formatter, color_formatter_sql
618
603
619 #############
604 #############
620 ## LOGGERS ##
605 ## LOGGERS ##
621 #############
606 #############
622 [logger_root]
607 [logger_root]
623 level = NOTSET
608 level = NOTSET
624 handlers = console
609 handlers = console
625
610
626 [logger_sqlalchemy]
611 [logger_sqlalchemy]
627 level = INFO
612 level = INFO
628 handlers = console_sql
613 handlers = console_sql
629 qualname = sqlalchemy.engine
614 qualname = sqlalchemy.engine
630 propagate = 0
615 propagate = 0
631
616
632 [logger_beaker]
617 [logger_beaker]
633 level = DEBUG
618 level = DEBUG
634 handlers =
619 handlers =
635 qualname = beaker.container
620 qualname = beaker.container
636 propagate = 1
621 propagate = 1
637
622
638 [logger_rhodecode]
623 [logger_rhodecode]
639 level = DEBUG
624 level = DEBUG
640 handlers =
625 handlers =
641 qualname = rhodecode
626 qualname = rhodecode
642 propagate = 1
627 propagate = 1
643
628
644 [logger_ssh_wrapper]
629 [logger_ssh_wrapper]
645 level = DEBUG
630 level = DEBUG
646 handlers =
631 handlers =
647 qualname = ssh_wrapper
632 qualname = ssh_wrapper
648 propagate = 1
633 propagate = 1
649
634
650 [logger_celery]
635 [logger_celery]
651 level = DEBUG
636 level = DEBUG
652 handlers =
637 handlers =
653 qualname = celery
638 qualname = celery
654
639
655
640
656 ##############
641 ##############
657 ## HANDLERS ##
642 ## HANDLERS ##
658 ##############
643 ##############
659
644
660 [handler_console]
645 [handler_console]
661 class = StreamHandler
646 class = StreamHandler
662 args = (sys.stderr, )
647 args = (sys.stderr, )
663 level = INFO
648 level = INFO
664 formatter = generic
649 formatter = generic
665
650
666 [handler_console_sql]
651 [handler_console_sql]
667 class = StreamHandler
652 class = StreamHandler
668 args = (sys.stderr, )
653 args = (sys.stderr, )
669 level = WARN
654 level = WARN
670 formatter = generic
655 formatter = generic
671
656
672 ################
657 ################
673 ## FORMATTERS ##
658 ## FORMATTERS ##
674 ################
659 ################
675
660
676 [formatter_generic]
661 [formatter_generic]
677 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
662 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
678 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
663 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
679 datefmt = %Y-%m-%d %H:%M:%S
664 datefmt = %Y-%m-%d %H:%M:%S
680
665
681 [formatter_color_formatter]
666 [formatter_color_formatter]
682 class = rhodecode.lib.logging_formatter.ColorFormatter
667 class = rhodecode.lib.logging_formatter.ColorFormatter
683 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
668 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
684 datefmt = %Y-%m-%d %H:%M:%S
669 datefmt = %Y-%m-%d %H:%M:%S
685
670
686 [formatter_color_formatter_sql]
671 [formatter_color_formatter_sql]
687 class = rhodecode.lib.logging_formatter.ColorFormatterSql
672 class = rhodecode.lib.logging_formatter.ColorFormatterSql
688 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
673 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
689 datefmt = %Y-%m-%d %H:%M:%S
674 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,414 +1,424 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps._base import ADMIN_PREFIX
22 from rhodecode.apps._base import ADMIN_PREFIX
23
23
24
24
25 def admin_routes(config):
25 def admin_routes(config):
26 """
26 """
27 Admin prefixed routes
27 Admin prefixed routes
28 """
28 """
29
29
30 config.add_route(
30 config.add_route(
31 name='admin_audit_logs',
31 name='admin_audit_logs',
32 pattern='/audit_logs')
32 pattern='/audit_logs')
33
33
34 config.add_route(
34 config.add_route(
35 name='admin_audit_log_entry',
35 name='admin_audit_log_entry',
36 pattern='/audit_logs/{audit_log_id}')
36 pattern='/audit_logs/{audit_log_id}')
37
37
38 config.add_route(
38 config.add_route(
39 name='pull_requests_global_0', # backward compat
39 name='pull_requests_global_0', # backward compat
40 pattern='/pull_requests/{pull_request_id:\d+}')
40 pattern='/pull_requests/{pull_request_id:\d+}')
41 config.add_route(
41 config.add_route(
42 name='pull_requests_global_1', # backward compat
42 name='pull_requests_global_1', # backward compat
43 pattern='/pull-requests/{pull_request_id:\d+}')
43 pattern='/pull-requests/{pull_request_id:\d+}')
44 config.add_route(
44 config.add_route(
45 name='pull_requests_global',
45 name='pull_requests_global',
46 pattern='/pull-request/{pull_request_id:\d+}')
46 pattern='/pull-request/{pull_request_id:\d+}')
47
47
48 config.add_route(
48 config.add_route(
49 name='admin_settings_open_source',
49 name='admin_settings_open_source',
50 pattern='/settings/open_source')
50 pattern='/settings/open_source')
51 config.add_route(
51 config.add_route(
52 name='admin_settings_vcs_svn_generate_cfg',
52 name='admin_settings_vcs_svn_generate_cfg',
53 pattern='/settings/vcs/svn_generate_cfg')
53 pattern='/settings/vcs/svn_generate_cfg')
54
54
55 config.add_route(
55 config.add_route(
56 name='admin_settings_system',
56 name='admin_settings_system',
57 pattern='/settings/system')
57 pattern='/settings/system')
58 config.add_route(
58 config.add_route(
59 name='admin_settings_system_update',
59 name='admin_settings_system_update',
60 pattern='/settings/system/updates')
60 pattern='/settings/system/updates')
61
61
62 config.add_route(
62 config.add_route(
63 name='admin_settings_sessions',
63 name='admin_settings_sessions',
64 pattern='/settings/sessions')
64 pattern='/settings/sessions')
65 config.add_route(
65 config.add_route(
66 name='admin_settings_sessions_cleanup',
66 name='admin_settings_sessions_cleanup',
67 pattern='/settings/sessions/cleanup')
67 pattern='/settings/sessions/cleanup')
68
68
69 config.add_route(
69 config.add_route(
70 name='admin_settings_process_management',
70 name='admin_settings_process_management',
71 pattern='/settings/process_management')
71 pattern='/settings/process_management')
72 config.add_route(
72 config.add_route(
73 name='admin_settings_process_management_data',
73 name='admin_settings_process_management_data',
74 pattern='/settings/process_management/data')
74 pattern='/settings/process_management/data')
75 config.add_route(
75 config.add_route(
76 name='admin_settings_process_management_signal',
76 name='admin_settings_process_management_signal',
77 pattern='/settings/process_management/signal')
77 pattern='/settings/process_management/signal')
78 config.add_route(
78 config.add_route(
79 name='admin_settings_process_management_master_signal',
79 name='admin_settings_process_management_master_signal',
80 pattern='/settings/process_management/master_signal')
80 pattern='/settings/process_management/master_signal')
81
81
82 # default settings
82 # default settings
83 config.add_route(
83 config.add_route(
84 name='admin_defaults_repositories',
84 name='admin_defaults_repositories',
85 pattern='/defaults/repositories')
85 pattern='/defaults/repositories')
86 config.add_route(
86 config.add_route(
87 name='admin_defaults_repositories_update',
87 name='admin_defaults_repositories_update',
88 pattern='/defaults/repositories/update')
88 pattern='/defaults/repositories/update')
89
89
90 # admin settings
90 # admin settings
91
91
92 config.add_route(
92 config.add_route(
93 name='admin_settings',
93 name='admin_settings',
94 pattern='/settings')
94 pattern='/settings')
95 config.add_route(
95 config.add_route(
96 name='admin_settings_update',
96 name='admin_settings_update',
97 pattern='/settings/update')
97 pattern='/settings/update')
98
98
99 config.add_route(
99 config.add_route(
100 name='admin_settings_global',
100 name='admin_settings_global',
101 pattern='/settings/global')
101 pattern='/settings/global')
102 config.add_route(
102 config.add_route(
103 name='admin_settings_global_update',
103 name='admin_settings_global_update',
104 pattern='/settings/global/update')
104 pattern='/settings/global/update')
105
105
106 config.add_route(
106 config.add_route(
107 name='admin_settings_vcs',
107 name='admin_settings_vcs',
108 pattern='/settings/vcs')
108 pattern='/settings/vcs')
109 config.add_route(
109 config.add_route(
110 name='admin_settings_vcs_update',
110 name='admin_settings_vcs_update',
111 pattern='/settings/vcs/update')
111 pattern='/settings/vcs/update')
112 config.add_route(
112 config.add_route(
113 name='admin_settings_vcs_svn_pattern_delete',
113 name='admin_settings_vcs_svn_pattern_delete',
114 pattern='/settings/vcs/svn_pattern_delete')
114 pattern='/settings/vcs/svn_pattern_delete')
115
115
116 config.add_route(
116 config.add_route(
117 name='admin_settings_mapping',
117 name='admin_settings_mapping',
118 pattern='/settings/mapping')
118 pattern='/settings/mapping')
119 config.add_route(
119 config.add_route(
120 name='admin_settings_mapping_update',
120 name='admin_settings_mapping_update',
121 pattern='/settings/mapping/update')
121 pattern='/settings/mapping/update')
122
122
123 config.add_route(
123 config.add_route(
124 name='admin_settings_visual',
124 name='admin_settings_visual',
125 pattern='/settings/visual')
125 pattern='/settings/visual')
126 config.add_route(
126 config.add_route(
127 name='admin_settings_visual_update',
127 name='admin_settings_visual_update',
128 pattern='/settings/visual/update')
128 pattern='/settings/visual/update')
129
129
130
130
131 config.add_route(
131 config.add_route(
132 name='admin_settings_issuetracker',
132 name='admin_settings_issuetracker',
133 pattern='/settings/issue-tracker')
133 pattern='/settings/issue-tracker')
134 config.add_route(
134 config.add_route(
135 name='admin_settings_issuetracker_update',
135 name='admin_settings_issuetracker_update',
136 pattern='/settings/issue-tracker/update')
136 pattern='/settings/issue-tracker/update')
137 config.add_route(
137 config.add_route(
138 name='admin_settings_issuetracker_test',
138 name='admin_settings_issuetracker_test',
139 pattern='/settings/issue-tracker/test')
139 pattern='/settings/issue-tracker/test')
140 config.add_route(
140 config.add_route(
141 name='admin_settings_issuetracker_delete',
141 name='admin_settings_issuetracker_delete',
142 pattern='/settings/issue-tracker/delete')
142 pattern='/settings/issue-tracker/delete')
143
143
144 config.add_route(
144 config.add_route(
145 name='admin_settings_email',
145 name='admin_settings_email',
146 pattern='/settings/email')
146 pattern='/settings/email')
147 config.add_route(
147 config.add_route(
148 name='admin_settings_email_update',
148 name='admin_settings_email_update',
149 pattern='/settings/email/update')
149 pattern='/settings/email/update')
150
150
151 config.add_route(
151 config.add_route(
152 name='admin_settings_hooks',
152 name='admin_settings_hooks',
153 pattern='/settings/hooks')
153 pattern='/settings/hooks')
154 config.add_route(
154 config.add_route(
155 name='admin_settings_hooks_update',
155 name='admin_settings_hooks_update',
156 pattern='/settings/hooks/update')
156 pattern='/settings/hooks/update')
157 config.add_route(
157 config.add_route(
158 name='admin_settings_hooks_delete',
158 name='admin_settings_hooks_delete',
159 pattern='/settings/hooks/delete')
159 pattern='/settings/hooks/delete')
160
160
161 config.add_route(
161 config.add_route(
162 name='admin_settings_search',
162 name='admin_settings_search',
163 pattern='/settings/search')
163 pattern='/settings/search')
164
164
165 config.add_route(
165 config.add_route(
166 name='admin_settings_labs',
166 name='admin_settings_labs',
167 pattern='/settings/labs')
167 pattern='/settings/labs')
168 config.add_route(
168 config.add_route(
169 name='admin_settings_labs_update',
169 name='admin_settings_labs_update',
170 pattern='/settings/labs/update')
170 pattern='/settings/labs/update')
171
171
172 # Automation EE feature
172 # Automation EE feature
173 config.add_route(
173 config.add_route(
174 'admin_settings_automation',
174 'admin_settings_automation',
175 pattern=ADMIN_PREFIX + '/settings/automation')
175 pattern=ADMIN_PREFIX + '/settings/automation')
176
176
177 # global permissions
177 # global permissions
178
178
179 config.add_route(
179 config.add_route(
180 name='admin_permissions_application',
180 name='admin_permissions_application',
181 pattern='/permissions/application')
181 pattern='/permissions/application')
182 config.add_route(
182 config.add_route(
183 name='admin_permissions_application_update',
183 name='admin_permissions_application_update',
184 pattern='/permissions/application/update')
184 pattern='/permissions/application/update')
185
185
186 config.add_route(
186 config.add_route(
187 name='admin_permissions_global',
187 name='admin_permissions_global',
188 pattern='/permissions/global')
188 pattern='/permissions/global')
189 config.add_route(
189 config.add_route(
190 name='admin_permissions_global_update',
190 name='admin_permissions_global_update',
191 pattern='/permissions/global/update')
191 pattern='/permissions/global/update')
192
192
193 config.add_route(
193 config.add_route(
194 name='admin_permissions_object',
194 name='admin_permissions_object',
195 pattern='/permissions/object')
195 pattern='/permissions/object')
196 config.add_route(
196 config.add_route(
197 name='admin_permissions_object_update',
197 name='admin_permissions_object_update',
198 pattern='/permissions/object/update')
198 pattern='/permissions/object/update')
199
199
200 config.add_route(
200 config.add_route(
201 name='admin_permissions_ips',
201 name='admin_permissions_ips',
202 pattern='/permissions/ips')
202 pattern='/permissions/ips')
203
203
204 config.add_route(
204 config.add_route(
205 name='admin_permissions_overview',
205 name='admin_permissions_overview',
206 pattern='/permissions/overview')
206 pattern='/permissions/overview')
207
207
208 config.add_route(
208 config.add_route(
209 name='admin_permissions_auth_token_access',
209 name='admin_permissions_auth_token_access',
210 pattern='/permissions/auth_token_access')
210 pattern='/permissions/auth_token_access')
211
211
212 config.add_route(
212 config.add_route(
213 name='admin_permissions_ssh_keys',
213 name='admin_permissions_ssh_keys',
214 pattern='/permissions/ssh_keys')
214 pattern='/permissions/ssh_keys')
215 config.add_route(
215 config.add_route(
216 name='admin_permissions_ssh_keys_data',
216 name='admin_permissions_ssh_keys_data',
217 pattern='/permissions/ssh_keys/data')
217 pattern='/permissions/ssh_keys/data')
218 config.add_route(
218 config.add_route(
219 name='admin_permissions_ssh_keys_update',
219 name='admin_permissions_ssh_keys_update',
220 pattern='/permissions/ssh_keys/update')
220 pattern='/permissions/ssh_keys/update')
221
221
222 # users admin
222 # users admin
223 config.add_route(
223 config.add_route(
224 name='users',
224 name='users',
225 pattern='/users')
225 pattern='/users')
226
226
227 config.add_route(
227 config.add_route(
228 name='users_data',
228 name='users_data',
229 pattern='/users_data')
229 pattern='/users_data')
230
230
231 config.add_route(
231 config.add_route(
232 name='users_create',
232 name='users_create',
233 pattern='/users/create')
233 pattern='/users/create')
234
234
235 config.add_route(
235 config.add_route(
236 name='users_new',
236 name='users_new',
237 pattern='/users/new')
237 pattern='/users/new')
238
238
239 # user management
239 # user management
240 config.add_route(
240 config.add_route(
241 name='user_edit',
241 name='user_edit',
242 pattern='/users/{user_id:\d+}/edit',
242 pattern='/users/{user_id:\d+}/edit',
243 user_route=True)
243 user_route=True)
244 config.add_route(
244 config.add_route(
245 name='user_edit_advanced',
245 name='user_edit_advanced',
246 pattern='/users/{user_id:\d+}/edit/advanced',
246 pattern='/users/{user_id:\d+}/edit/advanced',
247 user_route=True)
247 user_route=True)
248 config.add_route(
248 config.add_route(
249 name='user_edit_global_perms',
249 name='user_edit_global_perms',
250 pattern='/users/{user_id:\d+}/edit/global_permissions',
250 pattern='/users/{user_id:\d+}/edit/global_permissions',
251 user_route=True)
251 user_route=True)
252 config.add_route(
252 config.add_route(
253 name='user_edit_global_perms_update',
253 name='user_edit_global_perms_update',
254 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
254 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
255 user_route=True)
255 user_route=True)
256 config.add_route(
256 config.add_route(
257 name='user_update',
257 name='user_update',
258 pattern='/users/{user_id:\d+}/update',
258 pattern='/users/{user_id:\d+}/update',
259 user_route=True)
259 user_route=True)
260 config.add_route(
260 config.add_route(
261 name='user_delete',
261 name='user_delete',
262 pattern='/users/{user_id:\d+}/delete',
262 pattern='/users/{user_id:\d+}/delete',
263 user_route=True)
263 user_route=True)
264 config.add_route(
264 config.add_route(
265 name='user_force_password_reset',
265 name='user_force_password_reset',
266 pattern='/users/{user_id:\d+}/password_reset',
266 pattern='/users/{user_id:\d+}/password_reset',
267 user_route=True)
267 user_route=True)
268 config.add_route(
268 config.add_route(
269 name='user_create_personal_repo_group',
269 name='user_create_personal_repo_group',
270 pattern='/users/{user_id:\d+}/create_repo_group',
270 pattern='/users/{user_id:\d+}/create_repo_group',
271 user_route=True)
271 user_route=True)
272
272
273 # user auth tokens
273 # user auth tokens
274 config.add_route(
274 config.add_route(
275 name='edit_user_auth_tokens',
275 name='edit_user_auth_tokens',
276 pattern='/users/{user_id:\d+}/edit/auth_tokens',
276 pattern='/users/{user_id:\d+}/edit/auth_tokens',
277 user_route=True)
277 user_route=True)
278 config.add_route(
278 config.add_route(
279 name='edit_user_auth_tokens_add',
279 name='edit_user_auth_tokens_add',
280 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
280 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
281 user_route=True)
281 user_route=True)
282 config.add_route(
282 config.add_route(
283 name='edit_user_auth_tokens_delete',
283 name='edit_user_auth_tokens_delete',
284 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
284 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
285 user_route=True)
285 user_route=True)
286
286
287 # user ssh keys
287 # user ssh keys
288 config.add_route(
288 config.add_route(
289 name='edit_user_ssh_keys',
289 name='edit_user_ssh_keys',
290 pattern='/users/{user_id:\d+}/edit/ssh_keys',
290 pattern='/users/{user_id:\d+}/edit/ssh_keys',
291 user_route=True)
291 user_route=True)
292 config.add_route(
292 config.add_route(
293 name='edit_user_ssh_keys_generate_keypair',
293 name='edit_user_ssh_keys_generate_keypair',
294 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
294 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
295 user_route=True)
295 user_route=True)
296 config.add_route(
296 config.add_route(
297 name='edit_user_ssh_keys_add',
297 name='edit_user_ssh_keys_add',
298 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
298 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
299 user_route=True)
299 user_route=True)
300 config.add_route(
300 config.add_route(
301 name='edit_user_ssh_keys_delete',
301 name='edit_user_ssh_keys_delete',
302 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
302 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
303 user_route=True)
303 user_route=True)
304
304
305 # user emails
305 # user emails
306 config.add_route(
306 config.add_route(
307 name='edit_user_emails',
307 name='edit_user_emails',
308 pattern='/users/{user_id:\d+}/edit/emails',
308 pattern='/users/{user_id:\d+}/edit/emails',
309 user_route=True)
309 user_route=True)
310 config.add_route(
310 config.add_route(
311 name='edit_user_emails_add',
311 name='edit_user_emails_add',
312 pattern='/users/{user_id:\d+}/edit/emails/new',
312 pattern='/users/{user_id:\d+}/edit/emails/new',
313 user_route=True)
313 user_route=True)
314 config.add_route(
314 config.add_route(
315 name='edit_user_emails_delete',
315 name='edit_user_emails_delete',
316 pattern='/users/{user_id:\d+}/edit/emails/delete',
316 pattern='/users/{user_id:\d+}/edit/emails/delete',
317 user_route=True)
317 user_route=True)
318
318
319 # user IPs
319 # user IPs
320 config.add_route(
320 config.add_route(
321 name='edit_user_ips',
321 name='edit_user_ips',
322 pattern='/users/{user_id:\d+}/edit/ips',
322 pattern='/users/{user_id:\d+}/edit/ips',
323 user_route=True)
323 user_route=True)
324 config.add_route(
324 config.add_route(
325 name='edit_user_ips_add',
325 name='edit_user_ips_add',
326 pattern='/users/{user_id:\d+}/edit/ips/new',
326 pattern='/users/{user_id:\d+}/edit/ips/new',
327 user_route_with_default=True) # enabled for default user too
327 user_route_with_default=True) # enabled for default user too
328 config.add_route(
328 config.add_route(
329 name='edit_user_ips_delete',
329 name='edit_user_ips_delete',
330 pattern='/users/{user_id:\d+}/edit/ips/delete',
330 pattern='/users/{user_id:\d+}/edit/ips/delete',
331 user_route_with_default=True) # enabled for default user too
331 user_route_with_default=True) # enabled for default user too
332
332
333 # user perms
333 # user perms
334 config.add_route(
334 config.add_route(
335 name='edit_user_perms_summary',
335 name='edit_user_perms_summary',
336 pattern='/users/{user_id:\d+}/edit/permissions_summary',
336 pattern='/users/{user_id:\d+}/edit/permissions_summary',
337 user_route=True)
337 user_route=True)
338 config.add_route(
338 config.add_route(
339 name='edit_user_perms_summary_json',
339 name='edit_user_perms_summary_json',
340 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
340 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
341 user_route=True)
341 user_route=True)
342
342
343 # user user groups management
343 # user user groups management
344 config.add_route(
344 config.add_route(
345 name='edit_user_groups_management',
345 name='edit_user_groups_management',
346 pattern='/users/{user_id:\d+}/edit/groups_management',
346 pattern='/users/{user_id:\d+}/edit/groups_management',
347 user_route=True)
347 user_route=True)
348
348
349 config.add_route(
349 config.add_route(
350 name='edit_user_groups_management_updates',
350 name='edit_user_groups_management_updates',
351 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
351 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
352 user_route=True)
352 user_route=True)
353
353
354 # user audit logs
354 # user audit logs
355 config.add_route(
355 config.add_route(
356 name='edit_user_audit_logs',
356 name='edit_user_audit_logs',
357 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
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 # user-groups admin
369 # user-groups admin
360 config.add_route(
370 config.add_route(
361 name='user_groups',
371 name='user_groups',
362 pattern='/user_groups')
372 pattern='/user_groups')
363
373
364 config.add_route(
374 config.add_route(
365 name='user_groups_data',
375 name='user_groups_data',
366 pattern='/user_groups_data')
376 pattern='/user_groups_data')
367
377
368 config.add_route(
378 config.add_route(
369 name='user_groups_new',
379 name='user_groups_new',
370 pattern='/user_groups/new')
380 pattern='/user_groups/new')
371
381
372 config.add_route(
382 config.add_route(
373 name='user_groups_create',
383 name='user_groups_create',
374 pattern='/user_groups/create')
384 pattern='/user_groups/create')
375
385
376 # repos admin
386 # repos admin
377 config.add_route(
387 config.add_route(
378 name='repos',
388 name='repos',
379 pattern='/repos')
389 pattern='/repos')
380
390
381 config.add_route(
391 config.add_route(
382 name='repo_new',
392 name='repo_new',
383 pattern='/repos/new')
393 pattern='/repos/new')
384
394
385 config.add_route(
395 config.add_route(
386 name='repo_create',
396 name='repo_create',
387 pattern='/repos/create')
397 pattern='/repos/create')
388
398
389 # repo groups admin
399 # repo groups admin
390 config.add_route(
400 config.add_route(
391 name='repo_groups',
401 name='repo_groups',
392 pattern='/repo_groups')
402 pattern='/repo_groups')
393
403
394 config.add_route(
404 config.add_route(
395 name='repo_group_new',
405 name='repo_group_new',
396 pattern='/repo_group/new')
406 pattern='/repo_group/new')
397
407
398 config.add_route(
408 config.add_route(
399 name='repo_group_create',
409 name='repo_group_create',
400 pattern='/repo_group/create')
410 pattern='/repo_group/create')
401
411
402
412
403 def includeme(config):
413 def includeme(config):
404 from rhodecode.apps.admin.navigation import includeme as nav_includeme
414 from rhodecode.apps.admin.navigation import includeme as nav_includeme
405
415
406 # Create admin navigation registry and add it to the pyramid registry.
416 # Create admin navigation registry and add it to the pyramid registry.
407 nav_includeme(config)
417 nav_includeme(config)
408
418
409 # main admin routes
419 # main admin routes
410 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
420 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
411 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
421 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
412
422
413 # Scan module for configuration decorators.
423 # Scan module for configuration decorators.
414 config.scan('.views', ignore='.tests')
424 config.scan('.views', ignore='.tests')
@@ -1,1200 +1,1254 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.authentication.plugins import auth_rhodecode
34 from rhodecode.events import trigger
34 from rhodecode.events import trigger
35 from rhodecode.model.db import true
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 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, DefaultUserException)
40 UserOwnsUserGroupsException, DefaultUserException)
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.auth import (
42 from rhodecode.lib.auth import (
43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
44 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
46 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.forms import (
47 from rhodecode.model.forms import (
48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
49 UserExtraEmailForm, UserExtraIpForm)
49 UserExtraEmailForm, UserExtraIpForm)
50 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.repo_group import RepoGroupModel
51 from rhodecode.model.repo_group import RepoGroupModel
52 from rhodecode.model.ssh_key import SshKeyModel
52 from rhodecode.model.ssh_key import SshKeyModel
53 from rhodecode.model.user import UserModel
53 from rhodecode.model.user import UserModel
54 from rhodecode.model.user_group import UserGroupModel
54 from rhodecode.model.user_group import UserGroupModel
55 from rhodecode.model.db import (
55 from rhodecode.model.db import (
56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
57 UserApiKeys, UserSshKeys, RepoGroup)
57 UserApiKeys, UserSshKeys, RepoGroup)
58 from rhodecode.model.meta import Session
58 from rhodecode.model.meta import Session
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminUsersView(BaseAppView, DataGridAppView):
63 class AdminUsersView(BaseAppView, DataGridAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 return c
67 return c
68
68
69 @LoginRequired()
69 @LoginRequired()
70 @HasPermissionAllDecorator('hg.admin')
70 @HasPermissionAllDecorator('hg.admin')
71 @view_config(
71 @view_config(
72 route_name='users', request_method='GET',
72 route_name='users', request_method='GET',
73 renderer='rhodecode:templates/admin/users/users.mako')
73 renderer='rhodecode:templates/admin/users/users.mako')
74 def users_list(self):
74 def users_list(self):
75 c = self.load_default_context()
75 c = self.load_default_context()
76 return self._get_template_context(c)
76 return self._get_template_context(c)
77
77
78 @LoginRequired()
78 @LoginRequired()
79 @HasPermissionAllDecorator('hg.admin')
79 @HasPermissionAllDecorator('hg.admin')
80 @view_config(
80 @view_config(
81 # renderer defined below
81 # renderer defined below
82 route_name='users_data', request_method='GET',
82 route_name='users_data', request_method='GET',
83 renderer='json_ext', xhr=True)
83 renderer='json_ext', xhr=True)
84 def users_list_data(self):
84 def users_list_data(self):
85 self.load_default_context()
85 self.load_default_context()
86 column_map = {
86 column_map = {
87 'first_name': 'name',
87 'first_name': 'name',
88 'last_name': 'lastname',
88 'last_name': 'lastname',
89 }
89 }
90 draw, start, limit = self._extract_chunk(self.request)
90 draw, start, limit = self._extract_chunk(self.request)
91 search_q, order_by, order_dir = self._extract_ordering(
91 search_q, order_by, order_dir = self._extract_ordering(
92 self.request, column_map=column_map)
92 self.request, column_map=column_map)
93 _render = self.request.get_partial_renderer(
93 _render = self.request.get_partial_renderer(
94 'rhodecode:templates/data_table/_dt_elements.mako')
94 'rhodecode:templates/data_table/_dt_elements.mako')
95
95
96 def user_actions(user_id, username):
96 def user_actions(user_id, username):
97 return _render("user_actions", user_id, username)
97 return _render("user_actions", user_id, username)
98
98
99 users_data_total_count = User.query()\
99 users_data_total_count = User.query()\
100 .filter(User.username != User.DEFAULT_USER) \
100 .filter(User.username != User.DEFAULT_USER) \
101 .count()
101 .count()
102
102
103 users_data_total_inactive_count = User.query()\
103 users_data_total_inactive_count = User.query()\
104 .filter(User.username != User.DEFAULT_USER) \
104 .filter(User.username != User.DEFAULT_USER) \
105 .filter(User.active != true())\
105 .filter(User.active != true())\
106 .count()
106 .count()
107
107
108 # json generate
108 # json generate
109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 base_inactive_q = base_q.filter(User.active != true())
110 base_inactive_q = base_q.filter(User.active != true())
111
111
112 if search_q:
112 if search_q:
113 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 base_q = base_q.filter(or_(
114 base_q = base_q.filter(or_(
115 User.username.ilike(like_expression),
115 User.username.ilike(like_expression),
116 User._email.ilike(like_expression),
116 User._email.ilike(like_expression),
117 User.name.ilike(like_expression),
117 User.name.ilike(like_expression),
118 User.lastname.ilike(like_expression),
118 User.lastname.ilike(like_expression),
119 ))
119 ))
120 base_inactive_q = base_q.filter(User.active != true())
120 base_inactive_q = base_q.filter(User.active != true())
121
121
122 users_data_total_filtered_count = base_q.count()
122 users_data_total_filtered_count = base_q.count()
123 users_data_total_filtered_inactive_count = base_inactive_q.count()
123 users_data_total_filtered_inactive_count = base_inactive_q.count()
124
124
125 sort_col = getattr(User, order_by, None)
125 sort_col = getattr(User, order_by, None)
126 if sort_col:
126 if sort_col:
127 if order_dir == 'asc':
127 if order_dir == 'asc':
128 # handle null values properly to order by NULL last
128 # handle null values properly to order by NULL last
129 if order_by in ['last_activity']:
129 if order_by in ['last_activity']:
130 sort_col = coalesce(sort_col, datetime.date.max)
130 sort_col = coalesce(sort_col, datetime.date.max)
131 sort_col = sort_col.asc()
131 sort_col = sort_col.asc()
132 else:
132 else:
133 # handle null values properly to order by NULL last
133 # handle null values properly to order by NULL last
134 if order_by in ['last_activity']:
134 if order_by in ['last_activity']:
135 sort_col = coalesce(sort_col, datetime.date.min)
135 sort_col = coalesce(sort_col, datetime.date.min)
136 sort_col = sort_col.desc()
136 sort_col = sort_col.desc()
137
137
138 base_q = base_q.order_by(sort_col)
138 base_q = base_q.order_by(sort_col)
139 base_q = base_q.offset(start).limit(limit)
139 base_q = base_q.offset(start).limit(limit)
140
140
141 users_list = base_q.all()
141 users_list = base_q.all()
142
142
143 users_data = []
143 users_data = []
144 for user in users_list:
144 for user in users_list:
145 users_data.append({
145 users_data.append({
146 "username": h.gravatar_with_user(self.request, user.username),
146 "username": h.gravatar_with_user(self.request, user.username),
147 "email": user.email,
147 "email": user.email,
148 "first_name": user.first_name,
148 "first_name": user.first_name,
149 "last_name": user.last_name,
149 "last_name": user.last_name,
150 "last_login": h.format_date(user.last_login),
150 "last_login": h.format_date(user.last_login),
151 "last_activity": h.format_date(user.last_activity),
151 "last_activity": h.format_date(user.last_activity),
152 "active": h.bool2icon(user.active),
152 "active": h.bool2icon(user.active),
153 "active_raw": user.active,
153 "active_raw": user.active,
154 "admin": h.bool2icon(user.admin),
154 "admin": h.bool2icon(user.admin),
155 "extern_type": user.extern_type,
155 "extern_type": user.extern_type,
156 "extern_name": user.extern_name,
156 "extern_name": user.extern_name,
157 "action": user_actions(user.user_id, user.username),
157 "action": user_actions(user.user_id, user.username),
158 })
158 })
159 data = ({
159 data = ({
160 'draw': draw,
160 'draw': draw,
161 'data': users_data,
161 'data': users_data,
162 'recordsTotal': users_data_total_count,
162 'recordsTotal': users_data_total_count,
163 'recordsFiltered': users_data_total_filtered_count,
163 'recordsFiltered': users_data_total_filtered_count,
164 'recordsTotalInactive': users_data_total_inactive_count,
164 'recordsTotalInactive': users_data_total_inactive_count,
165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
166 })
166 })
167
167
168 return data
168 return data
169
169
170 def _set_personal_repo_group_template_vars(self, c_obj):
170 def _set_personal_repo_group_template_vars(self, c_obj):
171 DummyUser = AttributeDict({
171 DummyUser = AttributeDict({
172 'username': '${username}',
172 'username': '${username}',
173 'user_id': '${user_id}',
173 'user_id': '${user_id}',
174 })
174 })
175 c_obj.default_create_repo_group = RepoGroupModel() \
175 c_obj.default_create_repo_group = RepoGroupModel() \
176 .get_default_create_personal_repo_group()
176 .get_default_create_personal_repo_group()
177 c_obj.personal_repo_group_name = RepoGroupModel() \
177 c_obj.personal_repo_group_name = RepoGroupModel() \
178 .get_personal_group_name(DummyUser)
178 .get_personal_group_name(DummyUser)
179
179
180 @LoginRequired()
180 @LoginRequired()
181 @HasPermissionAllDecorator('hg.admin')
181 @HasPermissionAllDecorator('hg.admin')
182 @view_config(
182 @view_config(
183 route_name='users_new', request_method='GET',
183 route_name='users_new', request_method='GET',
184 renderer='rhodecode:templates/admin/users/user_add.mako')
184 renderer='rhodecode:templates/admin/users/user_add.mako')
185 def users_new(self):
185 def users_new(self):
186 _ = self.request.translate
186 _ = self.request.translate
187 c = self.load_default_context()
187 c = self.load_default_context()
188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
189 self._set_personal_repo_group_template_vars(c)
189 self._set_personal_repo_group_template_vars(c)
190 return self._get_template_context(c)
190 return self._get_template_context(c)
191
191
192 @LoginRequired()
192 @LoginRequired()
193 @HasPermissionAllDecorator('hg.admin')
193 @HasPermissionAllDecorator('hg.admin')
194 @CSRFRequired()
194 @CSRFRequired()
195 @view_config(
195 @view_config(
196 route_name='users_create', request_method='POST',
196 route_name='users_create', request_method='POST',
197 renderer='rhodecode:templates/admin/users/user_add.mako')
197 renderer='rhodecode:templates/admin/users/user_add.mako')
198 def users_create(self):
198 def users_create(self):
199 _ = self.request.translate
199 _ = self.request.translate
200 c = self.load_default_context()
200 c = self.load_default_context()
201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
202 user_model = UserModel()
202 user_model = UserModel()
203 user_form = UserForm(self.request.translate)()
203 user_form = UserForm(self.request.translate)()
204 try:
204 try:
205 form_result = user_form.to_python(dict(self.request.POST))
205 form_result = user_form.to_python(dict(self.request.POST))
206 user = user_model.create(form_result)
206 user = user_model.create(form_result)
207 Session().flush()
207 Session().flush()
208 creation_data = user.get_api_data()
208 creation_data = user.get_api_data()
209 username = form_result['username']
209 username = form_result['username']
210
210
211 audit_logger.store_web(
211 audit_logger.store_web(
212 'user.create', action_data={'data': creation_data},
212 'user.create', action_data={'data': creation_data},
213 user=c.rhodecode_user)
213 user=c.rhodecode_user)
214
214
215 user_link = h.link_to(
215 user_link = h.link_to(
216 h.escape(username),
216 h.escape(username),
217 h.route_path('user_edit', user_id=user.user_id))
217 h.route_path('user_edit', user_id=user.user_id))
218 h.flash(h.literal(_('Created user %(user_link)s')
218 h.flash(h.literal(_('Created user %(user_link)s')
219 % {'user_link': user_link}), category='success')
219 % {'user_link': user_link}), category='success')
220 Session().commit()
220 Session().commit()
221 except formencode.Invalid as errors:
221 except formencode.Invalid as errors:
222 self._set_personal_repo_group_template_vars(c)
222 self._set_personal_repo_group_template_vars(c)
223 data = render(
223 data = render(
224 'rhodecode:templates/admin/users/user_add.mako',
224 'rhodecode:templates/admin/users/user_add.mako',
225 self._get_template_context(c), self.request)
225 self._get_template_context(c), self.request)
226 html = formencode.htmlfill.render(
226 html = formencode.htmlfill.render(
227 data,
227 data,
228 defaults=errors.value,
228 defaults=errors.value,
229 errors=errors.error_dict or {},
229 errors=errors.error_dict or {},
230 prefix_error=False,
230 prefix_error=False,
231 encoding="UTF-8",
231 encoding="UTF-8",
232 force_defaults=False
232 force_defaults=False
233 )
233 )
234 return Response(html)
234 return Response(html)
235 except UserCreationError as e:
235 except UserCreationError as e:
236 h.flash(e, 'error')
236 h.flash(e, 'error')
237 except Exception:
237 except Exception:
238 log.exception("Exception creation of user")
238 log.exception("Exception creation of user")
239 h.flash(_('Error occurred during creation of user %s')
239 h.flash(_('Error occurred during creation of user %s')
240 % self.request.POST.get('username'), category='error')
240 % self.request.POST.get('username'), category='error')
241 raise HTTPFound(h.route_path('users'))
241 raise HTTPFound(h.route_path('users'))
242
242
243
243
244 class UsersView(UserAppView):
244 class UsersView(UserAppView):
245 ALLOW_SCOPED_TOKENS = False
245 ALLOW_SCOPED_TOKENS = False
246 """
246 """
247 This view has alternative version inside EE, if modified please take a look
247 This view has alternative version inside EE, if modified please take a look
248 in there as well.
248 in there as well.
249 """
249 """
250
250
251 def load_default_context(self):
251 def load_default_context(self):
252 c = self._get_local_tmpl_context()
252 c = self._get_local_tmpl_context()
253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
254 c.allowed_languages = [
254 c.allowed_languages = [
255 ('en', 'English (en)'),
255 ('en', 'English (en)'),
256 ('de', 'German (de)'),
256 ('de', 'German (de)'),
257 ('fr', 'French (fr)'),
257 ('fr', 'French (fr)'),
258 ('it', 'Italian (it)'),
258 ('it', 'Italian (it)'),
259 ('ja', 'Japanese (ja)'),
259 ('ja', 'Japanese (ja)'),
260 ('pl', 'Polish (pl)'),
260 ('pl', 'Polish (pl)'),
261 ('pt', 'Portuguese (pt)'),
261 ('pt', 'Portuguese (pt)'),
262 ('ru', 'Russian (ru)'),
262 ('ru', 'Russian (ru)'),
263 ('zh', 'Chinese (zh)'),
263 ('zh', 'Chinese (zh)'),
264 ]
264 ]
265 req = self.request
265 req = self.request
266
266
267 c.available_permissions = req.registry.settings['available_permissions']
267 c.available_permissions = req.registry.settings['available_permissions']
268 PermissionModel().set_global_permission_choices(
268 PermissionModel().set_global_permission_choices(
269 c, gettext_translator=req.translate)
269 c, gettext_translator=req.translate)
270
270
271 return c
271 return c
272
272
273 @LoginRequired()
273 @LoginRequired()
274 @HasPermissionAllDecorator('hg.admin')
274 @HasPermissionAllDecorator('hg.admin')
275 @CSRFRequired()
275 @CSRFRequired()
276 @view_config(
276 @view_config(
277 route_name='user_update', request_method='POST',
277 route_name='user_update', request_method='POST',
278 renderer='rhodecode:templates/admin/users/user_edit.mako')
278 renderer='rhodecode:templates/admin/users/user_edit.mako')
279 def user_update(self):
279 def user_update(self):
280 _ = self.request.translate
280 _ = self.request.translate
281 c = self.load_default_context()
281 c = self.load_default_context()
282
282
283 user_id = self.db_user_id
283 user_id = self.db_user_id
284 c.user = self.db_user
284 c.user = self.db_user
285
285
286 c.active = 'profile'
286 c.active = 'profile'
287 c.extern_type = c.user.extern_type
287 c.extern_type = c.user.extern_type
288 c.extern_name = c.user.extern_name
288 c.extern_name = c.user.extern_name
289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
290 available_languages = [x[0] for x in c.allowed_languages]
290 available_languages = [x[0] for x in c.allowed_languages]
291 _form = UserForm(self.request.translate, edit=True,
291 _form = UserForm(self.request.translate, edit=True,
292 available_languages=available_languages,
292 available_languages=available_languages,
293 old_data={'user_id': user_id,
293 old_data={'user_id': user_id,
294 'email': c.user.email})()
294 'email': c.user.email})()
295 form_result = {}
295 form_result = {}
296 old_values = c.user.get_api_data()
296 old_values = c.user.get_api_data()
297 try:
297 try:
298 form_result = _form.to_python(dict(self.request.POST))
298 form_result = _form.to_python(dict(self.request.POST))
299 skip_attrs = ['extern_type', 'extern_name']
299 skip_attrs = ['extern_type', 'extern_name']
300 # TODO: plugin should define if username can be updated
300 # TODO: plugin should define if username can be updated
301 if c.extern_type != "rhodecode":
301 if c.extern_type != "rhodecode":
302 # forbid updating username for external accounts
302 # forbid updating username for external accounts
303 skip_attrs.append('username')
303 skip_attrs.append('username')
304
304
305 UserModel().update_user(
305 UserModel().update_user(
306 user_id, skip_attrs=skip_attrs, **form_result)
306 user_id, skip_attrs=skip_attrs, **form_result)
307
307
308 audit_logger.store_web(
308 audit_logger.store_web(
309 'user.edit', action_data={'old_data': old_values},
309 'user.edit', action_data={'old_data': old_values},
310 user=c.rhodecode_user)
310 user=c.rhodecode_user)
311
311
312 Session().commit()
312 Session().commit()
313 h.flash(_('User updated successfully'), category='success')
313 h.flash(_('User updated successfully'), category='success')
314 except formencode.Invalid as errors:
314 except formencode.Invalid as errors:
315 data = render(
315 data = render(
316 'rhodecode:templates/admin/users/user_edit.mako',
316 'rhodecode:templates/admin/users/user_edit.mako',
317 self._get_template_context(c), self.request)
317 self._get_template_context(c), self.request)
318 html = formencode.htmlfill.render(
318 html = formencode.htmlfill.render(
319 data,
319 data,
320 defaults=errors.value,
320 defaults=errors.value,
321 errors=errors.error_dict or {},
321 errors=errors.error_dict or {},
322 prefix_error=False,
322 prefix_error=False,
323 encoding="UTF-8",
323 encoding="UTF-8",
324 force_defaults=False
324 force_defaults=False
325 )
325 )
326 return Response(html)
326 return Response(html)
327 except UserCreationError as e:
327 except UserCreationError as e:
328 h.flash(e, 'error')
328 h.flash(e, 'error')
329 except Exception:
329 except Exception:
330 log.exception("Exception updating user")
330 log.exception("Exception updating user")
331 h.flash(_('Error occurred during update of user %s')
331 h.flash(_('Error occurred during update of user %s')
332 % form_result.get('username'), category='error')
332 % form_result.get('username'), category='error')
333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
334
334
335 @LoginRequired()
335 @LoginRequired()
336 @HasPermissionAllDecorator('hg.admin')
336 @HasPermissionAllDecorator('hg.admin')
337 @CSRFRequired()
337 @CSRFRequired()
338 @view_config(
338 @view_config(
339 route_name='user_delete', request_method='POST',
339 route_name='user_delete', request_method='POST',
340 renderer='rhodecode:templates/admin/users/user_edit.mako')
340 renderer='rhodecode:templates/admin/users/user_edit.mako')
341 def user_delete(self):
341 def user_delete(self):
342 _ = self.request.translate
342 _ = self.request.translate
343 c = self.load_default_context()
343 c = self.load_default_context()
344 c.user = self.db_user
344 c.user = self.db_user
345
345
346 _repos = c.user.repositories
346 _repos = c.user.repositories
347 _repo_groups = c.user.repository_groups
347 _repo_groups = c.user.repository_groups
348 _user_groups = c.user.user_groups
348 _user_groups = c.user.user_groups
349
349
350 handle_repos = None
350 handle_repos = None
351 handle_repo_groups = None
351 handle_repo_groups = None
352 handle_user_groups = None
352 handle_user_groups = None
353 # dummy call for flash of handle
353 # dummy call for flash of handle
354 set_handle_flash_repos = lambda: None
354 set_handle_flash_repos = lambda: None
355 set_handle_flash_repo_groups = lambda: None
355 set_handle_flash_repo_groups = lambda: None
356 set_handle_flash_user_groups = lambda: None
356 set_handle_flash_user_groups = lambda: None
357
357
358 if _repos and self.request.POST.get('user_repos'):
358 if _repos and self.request.POST.get('user_repos'):
359 do = self.request.POST['user_repos']
359 do = self.request.POST['user_repos']
360 if do == 'detach':
360 if do == 'detach':
361 handle_repos = 'detach'
361 handle_repos = 'detach'
362 set_handle_flash_repos = lambda: h.flash(
362 set_handle_flash_repos = lambda: h.flash(
363 _('Detached %s repositories') % len(_repos),
363 _('Detached %s repositories') % len(_repos),
364 category='success')
364 category='success')
365 elif do == 'delete':
365 elif do == 'delete':
366 handle_repos = 'delete'
366 handle_repos = 'delete'
367 set_handle_flash_repos = lambda: h.flash(
367 set_handle_flash_repos = lambda: h.flash(
368 _('Deleted %s repositories') % len(_repos),
368 _('Deleted %s repositories') % len(_repos),
369 category='success')
369 category='success')
370
370
371 if _repo_groups and self.request.POST.get('user_repo_groups'):
371 if _repo_groups and self.request.POST.get('user_repo_groups'):
372 do = self.request.POST['user_repo_groups']
372 do = self.request.POST['user_repo_groups']
373 if do == 'detach':
373 if do == 'detach':
374 handle_repo_groups = 'detach'
374 handle_repo_groups = 'detach'
375 set_handle_flash_repo_groups = lambda: h.flash(
375 set_handle_flash_repo_groups = lambda: h.flash(
376 _('Detached %s repository groups') % len(_repo_groups),
376 _('Detached %s repository groups') % len(_repo_groups),
377 category='success')
377 category='success')
378 elif do == 'delete':
378 elif do == 'delete':
379 handle_repo_groups = 'delete'
379 handle_repo_groups = 'delete'
380 set_handle_flash_repo_groups = lambda: h.flash(
380 set_handle_flash_repo_groups = lambda: h.flash(
381 _('Deleted %s repository groups') % len(_repo_groups),
381 _('Deleted %s repository groups') % len(_repo_groups),
382 category='success')
382 category='success')
383
383
384 if _user_groups and self.request.POST.get('user_user_groups'):
384 if _user_groups and self.request.POST.get('user_user_groups'):
385 do = self.request.POST['user_user_groups']
385 do = self.request.POST['user_user_groups']
386 if do == 'detach':
386 if do == 'detach':
387 handle_user_groups = 'detach'
387 handle_user_groups = 'detach'
388 set_handle_flash_user_groups = lambda: h.flash(
388 set_handle_flash_user_groups = lambda: h.flash(
389 _('Detached %s user groups') % len(_user_groups),
389 _('Detached %s user groups') % len(_user_groups),
390 category='success')
390 category='success')
391 elif do == 'delete':
391 elif do == 'delete':
392 handle_user_groups = 'delete'
392 handle_user_groups = 'delete'
393 set_handle_flash_user_groups = lambda: h.flash(
393 set_handle_flash_user_groups = lambda: h.flash(
394 _('Deleted %s user groups') % len(_user_groups),
394 _('Deleted %s user groups') % len(_user_groups),
395 category='success')
395 category='success')
396
396
397 old_values = c.user.get_api_data()
397 old_values = c.user.get_api_data()
398 try:
398 try:
399 UserModel().delete(c.user, handle_repos=handle_repos,
399 UserModel().delete(c.user, handle_repos=handle_repos,
400 handle_repo_groups=handle_repo_groups,
400 handle_repo_groups=handle_repo_groups,
401 handle_user_groups=handle_user_groups)
401 handle_user_groups=handle_user_groups)
402
402
403 audit_logger.store_web(
403 audit_logger.store_web(
404 'user.delete', action_data={'old_data': old_values},
404 'user.delete', action_data={'old_data': old_values},
405 user=c.rhodecode_user)
405 user=c.rhodecode_user)
406
406
407 Session().commit()
407 Session().commit()
408 set_handle_flash_repos()
408 set_handle_flash_repos()
409 set_handle_flash_repo_groups()
409 set_handle_flash_repo_groups()
410 set_handle_flash_user_groups()
410 set_handle_flash_user_groups()
411 h.flash(_('Successfully deleted user'), category='success')
411 h.flash(_('Successfully deleted user'), category='success')
412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
413 UserOwnsUserGroupsException, DefaultUserException) as e:
413 UserOwnsUserGroupsException, DefaultUserException) as e:
414 h.flash(e, category='warning')
414 h.flash(e, category='warning')
415 except Exception:
415 except Exception:
416 log.exception("Exception during deletion of user")
416 log.exception("Exception during deletion of user")
417 h.flash(_('An error occurred during deletion of user'),
417 h.flash(_('An error occurred during deletion of user'),
418 category='error')
418 category='error')
419 raise HTTPFound(h.route_path('users'))
419 raise HTTPFound(h.route_path('users'))
420
420
421 @LoginRequired()
421 @LoginRequired()
422 @HasPermissionAllDecorator('hg.admin')
422 @HasPermissionAllDecorator('hg.admin')
423 @view_config(
423 @view_config(
424 route_name='user_edit', request_method='GET',
424 route_name='user_edit', request_method='GET',
425 renderer='rhodecode:templates/admin/users/user_edit.mako')
425 renderer='rhodecode:templates/admin/users/user_edit.mako')
426 def user_edit(self):
426 def user_edit(self):
427 _ = self.request.translate
427 _ = self.request.translate
428 c = self.load_default_context()
428 c = self.load_default_context()
429 c.user = self.db_user
429 c.user = self.db_user
430
430
431 c.active = 'profile'
431 c.active = 'profile'
432 c.extern_type = c.user.extern_type
432 c.extern_type = c.user.extern_type
433 c.extern_name = c.user.extern_name
433 c.extern_name = c.user.extern_name
434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
435
435
436 defaults = c.user.get_dict()
436 defaults = c.user.get_dict()
437 defaults.update({'language': c.user.user_data.get('language')})
437 defaults.update({'language': c.user.user_data.get('language')})
438
438
439 data = render(
439 data = render(
440 'rhodecode:templates/admin/users/user_edit.mako',
440 'rhodecode:templates/admin/users/user_edit.mako',
441 self._get_template_context(c), self.request)
441 self._get_template_context(c), self.request)
442 html = formencode.htmlfill.render(
442 html = formencode.htmlfill.render(
443 data,
443 data,
444 defaults=defaults,
444 defaults=defaults,
445 encoding="UTF-8",
445 encoding="UTF-8",
446 force_defaults=False
446 force_defaults=False
447 )
447 )
448 return Response(html)
448 return Response(html)
449
449
450 @LoginRequired()
450 @LoginRequired()
451 @HasPermissionAllDecorator('hg.admin')
451 @HasPermissionAllDecorator('hg.admin')
452 @view_config(
452 @view_config(
453 route_name='user_edit_advanced', request_method='GET',
453 route_name='user_edit_advanced', request_method='GET',
454 renderer='rhodecode:templates/admin/users/user_edit.mako')
454 renderer='rhodecode:templates/admin/users/user_edit.mako')
455 def user_edit_advanced(self):
455 def user_edit_advanced(self):
456 _ = self.request.translate
456 _ = self.request.translate
457 c = self.load_default_context()
457 c = self.load_default_context()
458
458
459 user_id = self.db_user_id
459 user_id = self.db_user_id
460 c.user = self.db_user
460 c.user = self.db_user
461
461
462 c.active = 'advanced'
462 c.active = 'advanced'
463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
464 c.personal_repo_group_name = RepoGroupModel()\
464 c.personal_repo_group_name = RepoGroupModel()\
465 .get_personal_group_name(c.user)
465 .get_personal_group_name(c.user)
466
466
467 c.user_to_review_rules = sorted(
467 c.user_to_review_rules = sorted(
468 (x.user for x in c.user.user_review_rules),
468 (x.user for x in c.user.user_review_rules),
469 key=lambda u: u.username.lower())
469 key=lambda u: u.username.lower())
470
470
471 c.first_admin = User.get_first_super_admin()
471 c.first_admin = User.get_first_super_admin()
472 defaults = c.user.get_dict()
472 defaults = c.user.get_dict()
473
473
474 # Interim workaround if the user participated on any pull requests as a
474 # Interim workaround if the user participated on any pull requests as a
475 # reviewer.
475 # reviewer.
476 has_review = len(c.user.reviewer_pull_requests)
476 has_review = len(c.user.reviewer_pull_requests)
477 c.can_delete_user = not has_review
477 c.can_delete_user = not has_review
478 c.can_delete_user_message = ''
478 c.can_delete_user_message = ''
479 inactive_link = h.link_to(
479 inactive_link = h.link_to(
480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
481 if has_review == 1:
481 if has_review == 1:
482 c.can_delete_user_message = h.literal(_(
482 c.can_delete_user_message = h.literal(_(
483 'The user participates as reviewer in {} pull request and '
483 'The user participates as reviewer in {} pull request and '
484 'cannot be deleted. \nYou can set the user to '
484 'cannot be deleted. \nYou can set the user to '
485 '"{}" instead of deleting it.').format(
485 '"{}" instead of deleting it.').format(
486 has_review, inactive_link))
486 has_review, inactive_link))
487 elif has_review:
487 elif has_review:
488 c.can_delete_user_message = h.literal(_(
488 c.can_delete_user_message = h.literal(_(
489 'The user participates as reviewer in {} pull requests and '
489 'The user participates as reviewer in {} pull requests and '
490 'cannot be deleted. \nYou can set the user to '
490 'cannot be deleted. \nYou can set the user to '
491 '"{}" instead of deleting it.').format(
491 '"{}" instead of deleting it.').format(
492 has_review, inactive_link))
492 has_review, inactive_link))
493
493
494 data = render(
494 data = render(
495 'rhodecode:templates/admin/users/user_edit.mako',
495 'rhodecode:templates/admin/users/user_edit.mako',
496 self._get_template_context(c), self.request)
496 self._get_template_context(c), self.request)
497 html = formencode.htmlfill.render(
497 html = formencode.htmlfill.render(
498 data,
498 data,
499 defaults=defaults,
499 defaults=defaults,
500 encoding="UTF-8",
500 encoding="UTF-8",
501 force_defaults=False
501 force_defaults=False
502 )
502 )
503 return Response(html)
503 return Response(html)
504
504
505 @LoginRequired()
505 @LoginRequired()
506 @HasPermissionAllDecorator('hg.admin')
506 @HasPermissionAllDecorator('hg.admin')
507 @view_config(
507 @view_config(
508 route_name='user_edit_global_perms', request_method='GET',
508 route_name='user_edit_global_perms', request_method='GET',
509 renderer='rhodecode:templates/admin/users/user_edit.mako')
509 renderer='rhodecode:templates/admin/users/user_edit.mako')
510 def user_edit_global_perms(self):
510 def user_edit_global_perms(self):
511 _ = self.request.translate
511 _ = self.request.translate
512 c = self.load_default_context()
512 c = self.load_default_context()
513 c.user = self.db_user
513 c.user = self.db_user
514
514
515 c.active = 'global_perms'
515 c.active = 'global_perms'
516
516
517 c.default_user = User.get_default_user()
517 c.default_user = User.get_default_user()
518 defaults = c.user.get_dict()
518 defaults = c.user.get_dict()
519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
520 defaults.update(c.default_user.get_default_perms())
520 defaults.update(c.default_user.get_default_perms())
521 defaults.update(c.user.get_default_perms())
521 defaults.update(c.user.get_default_perms())
522
522
523 data = render(
523 data = render(
524 'rhodecode:templates/admin/users/user_edit.mako',
524 'rhodecode:templates/admin/users/user_edit.mako',
525 self._get_template_context(c), self.request)
525 self._get_template_context(c), self.request)
526 html = formencode.htmlfill.render(
526 html = formencode.htmlfill.render(
527 data,
527 data,
528 defaults=defaults,
528 defaults=defaults,
529 encoding="UTF-8",
529 encoding="UTF-8",
530 force_defaults=False
530 force_defaults=False
531 )
531 )
532 return Response(html)
532 return Response(html)
533
533
534 @LoginRequired()
534 @LoginRequired()
535 @HasPermissionAllDecorator('hg.admin')
535 @HasPermissionAllDecorator('hg.admin')
536 @CSRFRequired()
536 @CSRFRequired()
537 @view_config(
537 @view_config(
538 route_name='user_edit_global_perms_update', request_method='POST',
538 route_name='user_edit_global_perms_update', request_method='POST',
539 renderer='rhodecode:templates/admin/users/user_edit.mako')
539 renderer='rhodecode:templates/admin/users/user_edit.mako')
540 def user_edit_global_perms_update(self):
540 def user_edit_global_perms_update(self):
541 _ = self.request.translate
541 _ = self.request.translate
542 c = self.load_default_context()
542 c = self.load_default_context()
543
543
544 user_id = self.db_user_id
544 user_id = self.db_user_id
545 c.user = self.db_user
545 c.user = self.db_user
546
546
547 c.active = 'global_perms'
547 c.active = 'global_perms'
548 try:
548 try:
549 # first stage that verifies the checkbox
549 # first stage that verifies the checkbox
550 _form = UserIndividualPermissionsForm(self.request.translate)
550 _form = UserIndividualPermissionsForm(self.request.translate)
551 form_result = _form.to_python(dict(self.request.POST))
551 form_result = _form.to_python(dict(self.request.POST))
552 inherit_perms = form_result['inherit_default_permissions']
552 inherit_perms = form_result['inherit_default_permissions']
553 c.user.inherit_default_permissions = inherit_perms
553 c.user.inherit_default_permissions = inherit_perms
554 Session().add(c.user)
554 Session().add(c.user)
555
555
556 if not inherit_perms:
556 if not inherit_perms:
557 # only update the individual ones if we un check the flag
557 # only update the individual ones if we un check the flag
558 _form = UserPermissionsForm(
558 _form = UserPermissionsForm(
559 self.request.translate,
559 self.request.translate,
560 [x[0] for x in c.repo_create_choices],
560 [x[0] for x in c.repo_create_choices],
561 [x[0] for x in c.repo_create_on_write_choices],
561 [x[0] for x in c.repo_create_on_write_choices],
562 [x[0] for x in c.repo_group_create_choices],
562 [x[0] for x in c.repo_group_create_choices],
563 [x[0] for x in c.user_group_create_choices],
563 [x[0] for x in c.user_group_create_choices],
564 [x[0] for x in c.fork_choices],
564 [x[0] for x in c.fork_choices],
565 [x[0] for x in c.inherit_default_permission_choices])()
565 [x[0] for x in c.inherit_default_permission_choices])()
566
566
567 form_result = _form.to_python(dict(self.request.POST))
567 form_result = _form.to_python(dict(self.request.POST))
568 form_result.update({'perm_user_id': c.user.user_id})
568 form_result.update({'perm_user_id': c.user.user_id})
569
569
570 PermissionModel().update_user_permissions(form_result)
570 PermissionModel().update_user_permissions(form_result)
571
571
572 # TODO(marcink): implement global permissions
572 # TODO(marcink): implement global permissions
573 # audit_log.store_web('user.edit.permissions')
573 # audit_log.store_web('user.edit.permissions')
574
574
575 Session().commit()
575 Session().commit()
576 h.flash(_('User global permissions updated successfully'),
576 h.flash(_('User global permissions updated successfully'),
577 category='success')
577 category='success')
578
578
579 except formencode.Invalid as errors:
579 except formencode.Invalid as errors:
580 data = render(
580 data = render(
581 'rhodecode:templates/admin/users/user_edit.mako',
581 'rhodecode:templates/admin/users/user_edit.mako',
582 self._get_template_context(c), self.request)
582 self._get_template_context(c), self.request)
583 html = formencode.htmlfill.render(
583 html = formencode.htmlfill.render(
584 data,
584 data,
585 defaults=errors.value,
585 defaults=errors.value,
586 errors=errors.error_dict or {},
586 errors=errors.error_dict or {},
587 prefix_error=False,
587 prefix_error=False,
588 encoding="UTF-8",
588 encoding="UTF-8",
589 force_defaults=False
589 force_defaults=False
590 )
590 )
591 return Response(html)
591 return Response(html)
592 except Exception:
592 except Exception:
593 log.exception("Exception during permissions saving")
593 log.exception("Exception during permissions saving")
594 h.flash(_('An error occurred during permissions saving'),
594 h.flash(_('An error occurred during permissions saving'),
595 category='error')
595 category='error')
596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
597
597
598 @LoginRequired()
598 @LoginRequired()
599 @HasPermissionAllDecorator('hg.admin')
599 @HasPermissionAllDecorator('hg.admin')
600 @CSRFRequired()
600 @CSRFRequired()
601 @view_config(
601 @view_config(
602 route_name='user_force_password_reset', request_method='POST',
602 route_name='user_force_password_reset', request_method='POST',
603 renderer='rhodecode:templates/admin/users/user_edit.mako')
603 renderer='rhodecode:templates/admin/users/user_edit.mako')
604 def user_force_password_reset(self):
604 def user_force_password_reset(self):
605 """
605 """
606 toggle reset password flag for this user
606 toggle reset password flag for this user
607 """
607 """
608 _ = self.request.translate
608 _ = self.request.translate
609 c = self.load_default_context()
609 c = self.load_default_context()
610
610
611 user_id = self.db_user_id
611 user_id = self.db_user_id
612 c.user = self.db_user
612 c.user = self.db_user
613
613
614 try:
614 try:
615 old_value = c.user.user_data.get('force_password_change')
615 old_value = c.user.user_data.get('force_password_change')
616 c.user.update_userdata(force_password_change=not old_value)
616 c.user.update_userdata(force_password_change=not old_value)
617
617
618 if old_value:
618 if old_value:
619 msg = _('Force password change disabled for user')
619 msg = _('Force password change disabled for user')
620 audit_logger.store_web(
620 audit_logger.store_web(
621 'user.edit.password_reset.disabled',
621 'user.edit.password_reset.disabled',
622 user=c.rhodecode_user)
622 user=c.rhodecode_user)
623 else:
623 else:
624 msg = _('Force password change enabled for user')
624 msg = _('Force password change enabled for user')
625 audit_logger.store_web(
625 audit_logger.store_web(
626 'user.edit.password_reset.enabled',
626 'user.edit.password_reset.enabled',
627 user=c.rhodecode_user)
627 user=c.rhodecode_user)
628
628
629 Session().commit()
629 Session().commit()
630 h.flash(msg, category='success')
630 h.flash(msg, category='success')
631 except Exception:
631 except Exception:
632 log.exception("Exception during password reset for user")
632 log.exception("Exception during password reset for user")
633 h.flash(_('An error occurred during password reset for user'),
633 h.flash(_('An error occurred during password reset for user'),
634 category='error')
634 category='error')
635
635
636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
637
637
638 @LoginRequired()
638 @LoginRequired()
639 @HasPermissionAllDecorator('hg.admin')
639 @HasPermissionAllDecorator('hg.admin')
640 @CSRFRequired()
640 @CSRFRequired()
641 @view_config(
641 @view_config(
642 route_name='user_create_personal_repo_group', request_method='POST',
642 route_name='user_create_personal_repo_group', request_method='POST',
643 renderer='rhodecode:templates/admin/users/user_edit.mako')
643 renderer='rhodecode:templates/admin/users/user_edit.mako')
644 def user_create_personal_repo_group(self):
644 def user_create_personal_repo_group(self):
645 """
645 """
646 Create personal repository group for this user
646 Create personal repository group for this user
647 """
647 """
648 from rhodecode.model.repo_group import RepoGroupModel
648 from rhodecode.model.repo_group import RepoGroupModel
649
649
650 _ = self.request.translate
650 _ = self.request.translate
651 c = self.load_default_context()
651 c = self.load_default_context()
652
652
653 user_id = self.db_user_id
653 user_id = self.db_user_id
654 c.user = self.db_user
654 c.user = self.db_user
655
655
656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
657 c.user.user_id)
657 c.user.user_id)
658 if personal_repo_group:
658 if personal_repo_group:
659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
660
660
661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
662 c.user)
662 c.user)
663 named_personal_group = RepoGroup.get_by_group_name(
663 named_personal_group = RepoGroup.get_by_group_name(
664 personal_repo_group_name)
664 personal_repo_group_name)
665 try:
665 try:
666
666
667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
668 # migrate the same named group, and mark it as personal
668 # migrate the same named group, and mark it as personal
669 named_personal_group.personal = True
669 named_personal_group.personal = True
670 Session().add(named_personal_group)
670 Session().add(named_personal_group)
671 Session().commit()
671 Session().commit()
672 msg = _('Linked repository group `%s` as personal' % (
672 msg = _('Linked repository group `%s` as personal' % (
673 personal_repo_group_name,))
673 personal_repo_group_name,))
674 h.flash(msg, category='success')
674 h.flash(msg, category='success')
675 elif not named_personal_group:
675 elif not named_personal_group:
676 RepoGroupModel().create_personal_repo_group(c.user)
676 RepoGroupModel().create_personal_repo_group(c.user)
677
677
678 msg = _('Created repository group `%s`' % (
678 msg = _('Created repository group `%s`' % (
679 personal_repo_group_name,))
679 personal_repo_group_name,))
680 h.flash(msg, category='success')
680 h.flash(msg, category='success')
681 else:
681 else:
682 msg = _('Repository group `%s` is already taken' % (
682 msg = _('Repository group `%s` is already taken' % (
683 personal_repo_group_name,))
683 personal_repo_group_name,))
684 h.flash(msg, category='warning')
684 h.flash(msg, category='warning')
685 except Exception:
685 except Exception:
686 log.exception("Exception during repository group creation")
686 log.exception("Exception during repository group creation")
687 msg = _(
687 msg = _(
688 'An error occurred during repository group creation for user')
688 'An error occurred during repository group creation for user')
689 h.flash(msg, category='error')
689 h.flash(msg, category='error')
690 Session().rollback()
690 Session().rollback()
691
691
692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
693
693
694 @LoginRequired()
694 @LoginRequired()
695 @HasPermissionAllDecorator('hg.admin')
695 @HasPermissionAllDecorator('hg.admin')
696 @view_config(
696 @view_config(
697 route_name='edit_user_auth_tokens', request_method='GET',
697 route_name='edit_user_auth_tokens', request_method='GET',
698 renderer='rhodecode:templates/admin/users/user_edit.mako')
698 renderer='rhodecode:templates/admin/users/user_edit.mako')
699 def auth_tokens(self):
699 def auth_tokens(self):
700 _ = self.request.translate
700 _ = self.request.translate
701 c = self.load_default_context()
701 c = self.load_default_context()
702 c.user = self.db_user
702 c.user = self.db_user
703
703
704 c.active = 'auth_tokens'
704 c.active = 'auth_tokens'
705
705
706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
707 c.role_values = [
707 c.role_values = [
708 (x, AuthTokenModel.cls._get_role_name(x))
708 (x, AuthTokenModel.cls._get_role_name(x))
709 for x in AuthTokenModel.cls.ROLES]
709 for x in AuthTokenModel.cls.ROLES]
710 c.role_options = [(c.role_values, _("Role"))]
710 c.role_options = [(c.role_values, _("Role"))]
711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
712 c.user.user_id, show_expired=True)
712 c.user.user_id, show_expired=True)
713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
714 return self._get_template_context(c)
714 return self._get_template_context(c)
715
715
716 def maybe_attach_token_scope(self, token):
716 def maybe_attach_token_scope(self, token):
717 # implemented in EE edition
717 # implemented in EE edition
718 pass
718 pass
719
719
720 @LoginRequired()
720 @LoginRequired()
721 @HasPermissionAllDecorator('hg.admin')
721 @HasPermissionAllDecorator('hg.admin')
722 @CSRFRequired()
722 @CSRFRequired()
723 @view_config(
723 @view_config(
724 route_name='edit_user_auth_tokens_add', request_method='POST')
724 route_name='edit_user_auth_tokens_add', request_method='POST')
725 def auth_tokens_add(self):
725 def auth_tokens_add(self):
726 _ = self.request.translate
726 _ = self.request.translate
727 c = self.load_default_context()
727 c = self.load_default_context()
728
728
729 user_id = self.db_user_id
729 user_id = self.db_user_id
730 c.user = self.db_user
730 c.user = self.db_user
731
731
732 user_data = c.user.get_api_data()
732 user_data = c.user.get_api_data()
733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
734 description = self.request.POST.get('description')
734 description = self.request.POST.get('description')
735 role = self.request.POST.get('role')
735 role = self.request.POST.get('role')
736
736
737 token = AuthTokenModel().create(
737 token = AuthTokenModel().create(
738 c.user.user_id, description, lifetime, role)
738 c.user.user_id, description, lifetime, role)
739 token_data = token.get_api_data()
739 token_data = token.get_api_data()
740
740
741 self.maybe_attach_token_scope(token)
741 self.maybe_attach_token_scope(token)
742 audit_logger.store_web(
742 audit_logger.store_web(
743 'user.edit.token.add', action_data={
743 'user.edit.token.add', action_data={
744 'data': {'token': token_data, 'user': user_data}},
744 'data': {'token': token_data, 'user': user_data}},
745 user=self._rhodecode_user, )
745 user=self._rhodecode_user, )
746 Session().commit()
746 Session().commit()
747
747
748 h.flash(_("Auth token successfully created"), category='success')
748 h.flash(_("Auth token successfully created"), category='success')
749 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
749 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
750
750
751 @LoginRequired()
751 @LoginRequired()
752 @HasPermissionAllDecorator('hg.admin')
752 @HasPermissionAllDecorator('hg.admin')
753 @CSRFRequired()
753 @CSRFRequired()
754 @view_config(
754 @view_config(
755 route_name='edit_user_auth_tokens_delete', request_method='POST')
755 route_name='edit_user_auth_tokens_delete', request_method='POST')
756 def auth_tokens_delete(self):
756 def auth_tokens_delete(self):
757 _ = self.request.translate
757 _ = self.request.translate
758 c = self.load_default_context()
758 c = self.load_default_context()
759
759
760 user_id = self.db_user_id
760 user_id = self.db_user_id
761 c.user = self.db_user
761 c.user = self.db_user
762
762
763 user_data = c.user.get_api_data()
763 user_data = c.user.get_api_data()
764
764
765 del_auth_token = self.request.POST.get('del_auth_token')
765 del_auth_token = self.request.POST.get('del_auth_token')
766
766
767 if del_auth_token:
767 if del_auth_token:
768 token = UserApiKeys.get_or_404(del_auth_token)
768 token = UserApiKeys.get_or_404(del_auth_token)
769 token_data = token.get_api_data()
769 token_data = token.get_api_data()
770
770
771 AuthTokenModel().delete(del_auth_token, c.user.user_id)
771 AuthTokenModel().delete(del_auth_token, c.user.user_id)
772 audit_logger.store_web(
772 audit_logger.store_web(
773 'user.edit.token.delete', action_data={
773 'user.edit.token.delete', action_data={
774 'data': {'token': token_data, 'user': user_data}},
774 'data': {'token': token_data, 'user': user_data}},
775 user=self._rhodecode_user,)
775 user=self._rhodecode_user,)
776 Session().commit()
776 Session().commit()
777 h.flash(_("Auth token successfully deleted"), category='success')
777 h.flash(_("Auth token successfully deleted"), category='success')
778
778
779 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
779 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
780
780
781 @LoginRequired()
781 @LoginRequired()
782 @HasPermissionAllDecorator('hg.admin')
782 @HasPermissionAllDecorator('hg.admin')
783 @view_config(
783 @view_config(
784 route_name='edit_user_ssh_keys', request_method='GET',
784 route_name='edit_user_ssh_keys', request_method='GET',
785 renderer='rhodecode:templates/admin/users/user_edit.mako')
785 renderer='rhodecode:templates/admin/users/user_edit.mako')
786 def ssh_keys(self):
786 def ssh_keys(self):
787 _ = self.request.translate
787 _ = self.request.translate
788 c = self.load_default_context()
788 c = self.load_default_context()
789 c.user = self.db_user
789 c.user = self.db_user
790
790
791 c.active = 'ssh_keys'
791 c.active = 'ssh_keys'
792 c.default_key = self.request.GET.get('default_key')
792 c.default_key = self.request.GET.get('default_key')
793 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
793 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
794 return self._get_template_context(c)
794 return self._get_template_context(c)
795
795
796 @LoginRequired()
796 @LoginRequired()
797 @HasPermissionAllDecorator('hg.admin')
797 @HasPermissionAllDecorator('hg.admin')
798 @view_config(
798 @view_config(
799 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
799 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
800 renderer='rhodecode:templates/admin/users/user_edit.mako')
800 renderer='rhodecode:templates/admin/users/user_edit.mako')
801 def ssh_keys_generate_keypair(self):
801 def ssh_keys_generate_keypair(self):
802 _ = self.request.translate
802 _ = self.request.translate
803 c = self.load_default_context()
803 c = self.load_default_context()
804
804
805 c.user = self.db_user
805 c.user = self.db_user
806
806
807 c.active = 'ssh_keys_generate'
807 c.active = 'ssh_keys_generate'
808 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
808 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
809 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
809 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
810
810
811 return self._get_template_context(c)
811 return self._get_template_context(c)
812
812
813 @LoginRequired()
813 @LoginRequired()
814 @HasPermissionAllDecorator('hg.admin')
814 @HasPermissionAllDecorator('hg.admin')
815 @CSRFRequired()
815 @CSRFRequired()
816 @view_config(
816 @view_config(
817 route_name='edit_user_ssh_keys_add', request_method='POST')
817 route_name='edit_user_ssh_keys_add', request_method='POST')
818 def ssh_keys_add(self):
818 def ssh_keys_add(self):
819 _ = self.request.translate
819 _ = self.request.translate
820 c = self.load_default_context()
820 c = self.load_default_context()
821
821
822 user_id = self.db_user_id
822 user_id = self.db_user_id
823 c.user = self.db_user
823 c.user = self.db_user
824
824
825 user_data = c.user.get_api_data()
825 user_data = c.user.get_api_data()
826 key_data = self.request.POST.get('key_data')
826 key_data = self.request.POST.get('key_data')
827 description = self.request.POST.get('description')
827 description = self.request.POST.get('description')
828
828
829 fingerprint = 'unknown'
829 fingerprint = 'unknown'
830 try:
830 try:
831 if not key_data:
831 if not key_data:
832 raise ValueError('Please add a valid public key')
832 raise ValueError('Please add a valid public key')
833
833
834 key = SshKeyModel().parse_key(key_data.strip())
834 key = SshKeyModel().parse_key(key_data.strip())
835 fingerprint = key.hash_md5()
835 fingerprint = key.hash_md5()
836
836
837 ssh_key = SshKeyModel().create(
837 ssh_key = SshKeyModel().create(
838 c.user.user_id, fingerprint, key.keydata, description)
838 c.user.user_id, fingerprint, key.keydata, description)
839 ssh_key_data = ssh_key.get_api_data()
839 ssh_key_data = ssh_key.get_api_data()
840
840
841 audit_logger.store_web(
841 audit_logger.store_web(
842 'user.edit.ssh_key.add', action_data={
842 'user.edit.ssh_key.add', action_data={
843 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
843 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
844 user=self._rhodecode_user, )
844 user=self._rhodecode_user, )
845 Session().commit()
845 Session().commit()
846
846
847 # Trigger an event on change of keys.
847 # Trigger an event on change of keys.
848 trigger(SshKeyFileChangeEvent(), self.request.registry)
848 trigger(SshKeyFileChangeEvent(), self.request.registry)
849
849
850 h.flash(_("Ssh Key successfully created"), category='success')
850 h.flash(_("Ssh Key successfully created"), category='success')
851
851
852 except IntegrityError:
852 except IntegrityError:
853 log.exception("Exception during ssh key saving")
853 log.exception("Exception during ssh key saving")
854 err = 'Such key with fingerprint `{}` already exists, ' \
854 err = 'Such key with fingerprint `{}` already exists, ' \
855 'please use a different one'.format(fingerprint)
855 'please use a different one'.format(fingerprint)
856 h.flash(_('An error occurred during ssh key saving: {}').format(err),
856 h.flash(_('An error occurred during ssh key saving: {}').format(err),
857 category='error')
857 category='error')
858 except Exception as e:
858 except Exception as e:
859 log.exception("Exception during ssh key saving")
859 log.exception("Exception during ssh key saving")
860 h.flash(_('An error occurred during ssh key saving: {}').format(e),
860 h.flash(_('An error occurred during ssh key saving: {}').format(e),
861 category='error')
861 category='error')
862
862
863 return HTTPFound(
863 return HTTPFound(
864 h.route_path('edit_user_ssh_keys', user_id=user_id))
864 h.route_path('edit_user_ssh_keys', user_id=user_id))
865
865
866 @LoginRequired()
866 @LoginRequired()
867 @HasPermissionAllDecorator('hg.admin')
867 @HasPermissionAllDecorator('hg.admin')
868 @CSRFRequired()
868 @CSRFRequired()
869 @view_config(
869 @view_config(
870 route_name='edit_user_ssh_keys_delete', request_method='POST')
870 route_name='edit_user_ssh_keys_delete', request_method='POST')
871 def ssh_keys_delete(self):
871 def ssh_keys_delete(self):
872 _ = self.request.translate
872 _ = self.request.translate
873 c = self.load_default_context()
873 c = self.load_default_context()
874
874
875 user_id = self.db_user_id
875 user_id = self.db_user_id
876 c.user = self.db_user
876 c.user = self.db_user
877
877
878 user_data = c.user.get_api_data()
878 user_data = c.user.get_api_data()
879
879
880 del_ssh_key = self.request.POST.get('del_ssh_key')
880 del_ssh_key = self.request.POST.get('del_ssh_key')
881
881
882 if del_ssh_key:
882 if del_ssh_key:
883 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
883 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
884 ssh_key_data = ssh_key.get_api_data()
884 ssh_key_data = ssh_key.get_api_data()
885
885
886 SshKeyModel().delete(del_ssh_key, c.user.user_id)
886 SshKeyModel().delete(del_ssh_key, c.user.user_id)
887 audit_logger.store_web(
887 audit_logger.store_web(
888 'user.edit.ssh_key.delete', action_data={
888 'user.edit.ssh_key.delete', action_data={
889 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
889 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
890 user=self._rhodecode_user,)
890 user=self._rhodecode_user,)
891 Session().commit()
891 Session().commit()
892 # Trigger an event on change of keys.
892 # Trigger an event on change of keys.
893 trigger(SshKeyFileChangeEvent(), self.request.registry)
893 trigger(SshKeyFileChangeEvent(), self.request.registry)
894 h.flash(_("Ssh key successfully deleted"), category='success')
894 h.flash(_("Ssh key successfully deleted"), category='success')
895
895
896 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
896 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
897
897
898 @LoginRequired()
898 @LoginRequired()
899 @HasPermissionAllDecorator('hg.admin')
899 @HasPermissionAllDecorator('hg.admin')
900 @view_config(
900 @view_config(
901 route_name='edit_user_emails', request_method='GET',
901 route_name='edit_user_emails', request_method='GET',
902 renderer='rhodecode:templates/admin/users/user_edit.mako')
902 renderer='rhodecode:templates/admin/users/user_edit.mako')
903 def emails(self):
903 def emails(self):
904 _ = self.request.translate
904 _ = self.request.translate
905 c = self.load_default_context()
905 c = self.load_default_context()
906 c.user = self.db_user
906 c.user = self.db_user
907
907
908 c.active = 'emails'
908 c.active = 'emails'
909 c.user_email_map = UserEmailMap.query() \
909 c.user_email_map = UserEmailMap.query() \
910 .filter(UserEmailMap.user == c.user).all()
910 .filter(UserEmailMap.user == c.user).all()
911
911
912 return self._get_template_context(c)
912 return self._get_template_context(c)
913
913
914 @LoginRequired()
914 @LoginRequired()
915 @HasPermissionAllDecorator('hg.admin')
915 @HasPermissionAllDecorator('hg.admin')
916 @CSRFRequired()
916 @CSRFRequired()
917 @view_config(
917 @view_config(
918 route_name='edit_user_emails_add', request_method='POST')
918 route_name='edit_user_emails_add', request_method='POST')
919 def emails_add(self):
919 def emails_add(self):
920 _ = self.request.translate
920 _ = self.request.translate
921 c = self.load_default_context()
921 c = self.load_default_context()
922
922
923 user_id = self.db_user_id
923 user_id = self.db_user_id
924 c.user = self.db_user
924 c.user = self.db_user
925
925
926 email = self.request.POST.get('new_email')
926 email = self.request.POST.get('new_email')
927 user_data = c.user.get_api_data()
927 user_data = c.user.get_api_data()
928 try:
928 try:
929
929
930 form = UserExtraEmailForm(self.request.translate)()
930 form = UserExtraEmailForm(self.request.translate)()
931 data = form.to_python({'email': email})
931 data = form.to_python({'email': email})
932 email = data['email']
932 email = data['email']
933
933
934 UserModel().add_extra_email(c.user.user_id, email)
934 UserModel().add_extra_email(c.user.user_id, email)
935 audit_logger.store_web(
935 audit_logger.store_web(
936 'user.edit.email.add',
936 'user.edit.email.add',
937 action_data={'email': email, 'user': user_data},
937 action_data={'email': email, 'user': user_data},
938 user=self._rhodecode_user)
938 user=self._rhodecode_user)
939 Session().commit()
939 Session().commit()
940 h.flash(_("Added new email address `%s` for user account") % email,
940 h.flash(_("Added new email address `%s` for user account") % email,
941 category='success')
941 category='success')
942 except formencode.Invalid as error:
942 except formencode.Invalid as error:
943 h.flash(h.escape(error.error_dict['email']), category='error')
943 h.flash(h.escape(error.error_dict['email']), category='error')
944 except IntegrityError:
944 except IntegrityError:
945 log.warning("Email %s already exists", email)
945 log.warning("Email %s already exists", email)
946 h.flash(_('Email `{}` is already registered for another user.').format(email),
946 h.flash(_('Email `{}` is already registered for another user.').format(email),
947 category='error')
947 category='error')
948 except Exception:
948 except Exception:
949 log.exception("Exception during email saving")
949 log.exception("Exception during email saving")
950 h.flash(_('An error occurred during email saving'),
950 h.flash(_('An error occurred during email saving'),
951 category='error')
951 category='error')
952 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
952 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
953
953
954 @LoginRequired()
954 @LoginRequired()
955 @HasPermissionAllDecorator('hg.admin')
955 @HasPermissionAllDecorator('hg.admin')
956 @CSRFRequired()
956 @CSRFRequired()
957 @view_config(
957 @view_config(
958 route_name='edit_user_emails_delete', request_method='POST')
958 route_name='edit_user_emails_delete', request_method='POST')
959 def emails_delete(self):
959 def emails_delete(self):
960 _ = self.request.translate
960 _ = self.request.translate
961 c = self.load_default_context()
961 c = self.load_default_context()
962
962
963 user_id = self.db_user_id
963 user_id = self.db_user_id
964 c.user = self.db_user
964 c.user = self.db_user
965
965
966 email_id = self.request.POST.get('del_email_id')
966 email_id = self.request.POST.get('del_email_id')
967 user_model = UserModel()
967 user_model = UserModel()
968
968
969 email = UserEmailMap.query().get(email_id).email
969 email = UserEmailMap.query().get(email_id).email
970 user_data = c.user.get_api_data()
970 user_data = c.user.get_api_data()
971 user_model.delete_extra_email(c.user.user_id, email_id)
971 user_model.delete_extra_email(c.user.user_id, email_id)
972 audit_logger.store_web(
972 audit_logger.store_web(
973 'user.edit.email.delete',
973 'user.edit.email.delete',
974 action_data={'email': email, 'user': user_data},
974 action_data={'email': email, 'user': user_data},
975 user=self._rhodecode_user)
975 user=self._rhodecode_user)
976 Session().commit()
976 Session().commit()
977 h.flash(_("Removed email address from user account"),
977 h.flash(_("Removed email address from user account"),
978 category='success')
978 category='success')
979 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
979 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
980
980
981 @LoginRequired()
981 @LoginRequired()
982 @HasPermissionAllDecorator('hg.admin')
982 @HasPermissionAllDecorator('hg.admin')
983 @view_config(
983 @view_config(
984 route_name='edit_user_ips', request_method='GET',
984 route_name='edit_user_ips', request_method='GET',
985 renderer='rhodecode:templates/admin/users/user_edit.mako')
985 renderer='rhodecode:templates/admin/users/user_edit.mako')
986 def ips(self):
986 def ips(self):
987 _ = self.request.translate
987 _ = self.request.translate
988 c = self.load_default_context()
988 c = self.load_default_context()
989 c.user = self.db_user
989 c.user = self.db_user
990
990
991 c.active = 'ips'
991 c.active = 'ips'
992 c.user_ip_map = UserIpMap.query() \
992 c.user_ip_map = UserIpMap.query() \
993 .filter(UserIpMap.user == c.user).all()
993 .filter(UserIpMap.user == c.user).all()
994
994
995 c.inherit_default_ips = c.user.inherit_default_permissions
995 c.inherit_default_ips = c.user.inherit_default_permissions
996 c.default_user_ip_map = UserIpMap.query() \
996 c.default_user_ip_map = UserIpMap.query() \
997 .filter(UserIpMap.user == User.get_default_user()).all()
997 .filter(UserIpMap.user == User.get_default_user()).all()
998
998
999 return self._get_template_context(c)
999 return self._get_template_context(c)
1000
1000
1001 @LoginRequired()
1001 @LoginRequired()
1002 @HasPermissionAllDecorator('hg.admin')
1002 @HasPermissionAllDecorator('hg.admin')
1003 @CSRFRequired()
1003 @CSRFRequired()
1004 @view_config(
1004 @view_config(
1005 route_name='edit_user_ips_add', request_method='POST')
1005 route_name='edit_user_ips_add', request_method='POST')
1006 # NOTE(marcink): this view is allowed for default users, as we can
1006 # NOTE(marcink): this view is allowed for default users, as we can
1007 # edit their IP white list
1007 # edit their IP white list
1008 def ips_add(self):
1008 def ips_add(self):
1009 _ = self.request.translate
1009 _ = self.request.translate
1010 c = self.load_default_context()
1010 c = self.load_default_context()
1011
1011
1012 user_id = self.db_user_id
1012 user_id = self.db_user_id
1013 c.user = self.db_user
1013 c.user = self.db_user
1014
1014
1015 user_model = UserModel()
1015 user_model = UserModel()
1016 desc = self.request.POST.get('description')
1016 desc = self.request.POST.get('description')
1017 try:
1017 try:
1018 ip_list = user_model.parse_ip_range(
1018 ip_list = user_model.parse_ip_range(
1019 self.request.POST.get('new_ip'))
1019 self.request.POST.get('new_ip'))
1020 except Exception as e:
1020 except Exception as e:
1021 ip_list = []
1021 ip_list = []
1022 log.exception("Exception during ip saving")
1022 log.exception("Exception during ip saving")
1023 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1023 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1024 category='error')
1024 category='error')
1025 added = []
1025 added = []
1026 user_data = c.user.get_api_data()
1026 user_data = c.user.get_api_data()
1027 for ip in ip_list:
1027 for ip in ip_list:
1028 try:
1028 try:
1029 form = UserExtraIpForm(self.request.translate)()
1029 form = UserExtraIpForm(self.request.translate)()
1030 data = form.to_python({'ip': ip})
1030 data = form.to_python({'ip': ip})
1031 ip = data['ip']
1031 ip = data['ip']
1032
1032
1033 user_model.add_extra_ip(c.user.user_id, ip, desc)
1033 user_model.add_extra_ip(c.user.user_id, ip, desc)
1034 audit_logger.store_web(
1034 audit_logger.store_web(
1035 'user.edit.ip.add',
1035 'user.edit.ip.add',
1036 action_data={'ip': ip, 'user': user_data},
1036 action_data={'ip': ip, 'user': user_data},
1037 user=self._rhodecode_user)
1037 user=self._rhodecode_user)
1038 Session().commit()
1038 Session().commit()
1039 added.append(ip)
1039 added.append(ip)
1040 except formencode.Invalid as error:
1040 except formencode.Invalid as error:
1041 msg = error.error_dict['ip']
1041 msg = error.error_dict['ip']
1042 h.flash(msg, category='error')
1042 h.flash(msg, category='error')
1043 except Exception:
1043 except Exception:
1044 log.exception("Exception during ip saving")
1044 log.exception("Exception during ip saving")
1045 h.flash(_('An error occurred during ip saving'),
1045 h.flash(_('An error occurred during ip saving'),
1046 category='error')
1046 category='error')
1047 if added:
1047 if added:
1048 h.flash(
1048 h.flash(
1049 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1049 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1050 category='success')
1050 category='success')
1051 if 'default_user' in self.request.POST:
1051 if 'default_user' in self.request.POST:
1052 # case for editing global IP list we do it for 'DEFAULT' user
1052 # case for editing global IP list we do it for 'DEFAULT' user
1053 raise HTTPFound(h.route_path('admin_permissions_ips'))
1053 raise HTTPFound(h.route_path('admin_permissions_ips'))
1054 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1054 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1055
1055
1056 @LoginRequired()
1056 @LoginRequired()
1057 @HasPermissionAllDecorator('hg.admin')
1057 @HasPermissionAllDecorator('hg.admin')
1058 @CSRFRequired()
1058 @CSRFRequired()
1059 @view_config(
1059 @view_config(
1060 route_name='edit_user_ips_delete', request_method='POST')
1060 route_name='edit_user_ips_delete', request_method='POST')
1061 # NOTE(marcink): this view is allowed for default users, as we can
1061 # NOTE(marcink): this view is allowed for default users, as we can
1062 # edit their IP white list
1062 # edit their IP white list
1063 def ips_delete(self):
1063 def ips_delete(self):
1064 _ = self.request.translate
1064 _ = self.request.translate
1065 c = self.load_default_context()
1065 c = self.load_default_context()
1066
1066
1067 user_id = self.db_user_id
1067 user_id = self.db_user_id
1068 c.user = self.db_user
1068 c.user = self.db_user
1069
1069
1070 ip_id = self.request.POST.get('del_ip_id')
1070 ip_id = self.request.POST.get('del_ip_id')
1071 user_model = UserModel()
1071 user_model = UserModel()
1072 user_data = c.user.get_api_data()
1072 user_data = c.user.get_api_data()
1073 ip = UserIpMap.query().get(ip_id).ip_addr
1073 ip = UserIpMap.query().get(ip_id).ip_addr
1074 user_model.delete_extra_ip(c.user.user_id, ip_id)
1074 user_model.delete_extra_ip(c.user.user_id, ip_id)
1075 audit_logger.store_web(
1075 audit_logger.store_web(
1076 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1076 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1077 user=self._rhodecode_user)
1077 user=self._rhodecode_user)
1078 Session().commit()
1078 Session().commit()
1079 h.flash(_("Removed ip address from user whitelist"), category='success')
1079 h.flash(_("Removed ip address from user whitelist"), category='success')
1080
1080
1081 if 'default_user' in self.request.POST:
1081 if 'default_user' in self.request.POST:
1082 # case for editing global IP list we do it for 'DEFAULT' user
1082 # case for editing global IP list we do it for 'DEFAULT' user
1083 raise HTTPFound(h.route_path('admin_permissions_ips'))
1083 raise HTTPFound(h.route_path('admin_permissions_ips'))
1084 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1084 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1085
1085
1086 @LoginRequired()
1086 @LoginRequired()
1087 @HasPermissionAllDecorator('hg.admin')
1087 @HasPermissionAllDecorator('hg.admin')
1088 @view_config(
1088 @view_config(
1089 route_name='edit_user_groups_management', request_method='GET',
1089 route_name='edit_user_groups_management', request_method='GET',
1090 renderer='rhodecode:templates/admin/users/user_edit.mako')
1090 renderer='rhodecode:templates/admin/users/user_edit.mako')
1091 def groups_management(self):
1091 def groups_management(self):
1092 c = self.load_default_context()
1092 c = self.load_default_context()
1093 c.user = self.db_user
1093 c.user = self.db_user
1094 c.data = c.user.group_member
1094 c.data = c.user.group_member
1095
1095
1096 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1096 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1097 for group in c.user.group_member]
1097 for group in c.user.group_member]
1098 c.groups = json.dumps(groups)
1098 c.groups = json.dumps(groups)
1099 c.active = 'groups'
1099 c.active = 'groups'
1100
1100
1101 return self._get_template_context(c)
1101 return self._get_template_context(c)
1102
1102
1103 @LoginRequired()
1103 @LoginRequired()
1104 @HasPermissionAllDecorator('hg.admin')
1104 @HasPermissionAllDecorator('hg.admin')
1105 @CSRFRequired()
1105 @CSRFRequired()
1106 @view_config(
1106 @view_config(
1107 route_name='edit_user_groups_management_updates', request_method='POST')
1107 route_name='edit_user_groups_management_updates', request_method='POST')
1108 def groups_management_updates(self):
1108 def groups_management_updates(self):
1109 _ = self.request.translate
1109 _ = self.request.translate
1110 c = self.load_default_context()
1110 c = self.load_default_context()
1111
1111
1112 user_id = self.db_user_id
1112 user_id = self.db_user_id
1113 c.user = self.db_user
1113 c.user = self.db_user
1114
1114
1115 user_groups = set(self.request.POST.getall('users_group_id'))
1115 user_groups = set(self.request.POST.getall('users_group_id'))
1116 user_groups_objects = []
1116 user_groups_objects = []
1117
1117
1118 for ugid in user_groups:
1118 for ugid in user_groups:
1119 user_groups_objects.append(
1119 user_groups_objects.append(
1120 UserGroupModel().get_group(safe_int(ugid)))
1120 UserGroupModel().get_group(safe_int(ugid)))
1121 user_group_model = UserGroupModel()
1121 user_group_model = UserGroupModel()
1122 added_to_groups, removed_from_groups = \
1122 added_to_groups, removed_from_groups = \
1123 user_group_model.change_groups(c.user, user_groups_objects)
1123 user_group_model.change_groups(c.user, user_groups_objects)
1124
1124
1125 user_data = c.user.get_api_data()
1125 user_data = c.user.get_api_data()
1126 for user_group_id in added_to_groups:
1126 for user_group_id in added_to_groups:
1127 user_group = UserGroup.get(user_group_id)
1127 user_group = UserGroup.get(user_group_id)
1128 old_values = user_group.get_api_data()
1128 old_values = user_group.get_api_data()
1129 audit_logger.store_web(
1129 audit_logger.store_web(
1130 'user_group.edit.member.add',
1130 'user_group.edit.member.add',
1131 action_data={'user': user_data, 'old_data': old_values},
1131 action_data={'user': user_data, 'old_data': old_values},
1132 user=self._rhodecode_user)
1132 user=self._rhodecode_user)
1133
1133
1134 for user_group_id in removed_from_groups:
1134 for user_group_id in removed_from_groups:
1135 user_group = UserGroup.get(user_group_id)
1135 user_group = UserGroup.get(user_group_id)
1136 old_values = user_group.get_api_data()
1136 old_values = user_group.get_api_data()
1137 audit_logger.store_web(
1137 audit_logger.store_web(
1138 'user_group.edit.member.delete',
1138 'user_group.edit.member.delete',
1139 action_data={'user': user_data, 'old_data': old_values},
1139 action_data={'user': user_data, 'old_data': old_values},
1140 user=self._rhodecode_user)
1140 user=self._rhodecode_user)
1141
1141
1142 Session().commit()
1142 Session().commit()
1143 c.active = 'user_groups_management'
1143 c.active = 'user_groups_management'
1144 h.flash(_("Groups successfully changed"), category='success')
1144 h.flash(_("Groups successfully changed"), category='success')
1145
1145
1146 return HTTPFound(h.route_path(
1146 return HTTPFound(h.route_path(
1147 'edit_user_groups_management', user_id=user_id))
1147 'edit_user_groups_management', user_id=user_id))
1148
1148
1149 @LoginRequired()
1149 @LoginRequired()
1150 @HasPermissionAllDecorator('hg.admin')
1150 @HasPermissionAllDecorator('hg.admin')
1151 @view_config(
1151 @view_config(
1152 route_name='edit_user_audit_logs', request_method='GET',
1152 route_name='edit_user_audit_logs', request_method='GET',
1153 renderer='rhodecode:templates/admin/users/user_edit.mako')
1153 renderer='rhodecode:templates/admin/users/user_edit.mako')
1154 def user_audit_logs(self):
1154 def user_audit_logs(self):
1155 _ = self.request.translate
1155 _ = self.request.translate
1156 c = self.load_default_context()
1156 c = self.load_default_context()
1157 c.user = self.db_user
1157 c.user = self.db_user
1158
1158
1159 c.active = 'audit'
1159 c.active = 'audit'
1160
1160
1161 p = safe_int(self.request.GET.get('page', 1), 1)
1161 p = safe_int(self.request.GET.get('page', 1), 1)
1162
1162
1163 filter_term = self.request.GET.get('filter')
1163 filter_term = self.request.GET.get('filter')
1164 user_log = UserModel().get_user_log(c.user, filter_term)
1164 user_log = UserModel().get_user_log(c.user, filter_term)
1165
1165
1166 def url_generator(**kw):
1166 def url_generator(**kw):
1167 if filter_term:
1167 if filter_term:
1168 kw['filter'] = filter_term
1168 kw['filter'] = filter_term
1169 return self.request.current_route_path(_query=kw)
1169 return self.request.current_route_path(_query=kw)
1170
1170
1171 c.audit_logs = h.Page(
1171 c.audit_logs = h.Page(
1172 user_log, page=p, items_per_page=10, url=url_generator)
1172 user_log, page=p, items_per_page=10, url=url_generator)
1173 c.filter_term = filter_term
1173 c.filter_term = filter_term
1174 return self._get_template_context(c)
1174 return self._get_template_context(c)
1175
1175
1176 @LoginRequired()
1176 @LoginRequired()
1177 @HasPermissionAllDecorator('hg.admin')
1177 @HasPermissionAllDecorator('hg.admin')
1178 @view_config(
1178 @view_config(
1179 route_name='edit_user_perms_summary', request_method='GET',
1179 route_name='edit_user_perms_summary', request_method='GET',
1180 renderer='rhodecode:templates/admin/users/user_edit.mako')
1180 renderer='rhodecode:templates/admin/users/user_edit.mako')
1181 def user_perms_summary(self):
1181 def user_perms_summary(self):
1182 _ = self.request.translate
1182 _ = self.request.translate
1183 c = self.load_default_context()
1183 c = self.load_default_context()
1184 c.user = self.db_user
1184 c.user = self.db_user
1185
1185
1186 c.active = 'perms_summary'
1186 c.active = 'perms_summary'
1187 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1187 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1188
1188
1189 return self._get_template_context(c)
1189 return self._get_template_context(c)
1190
1190
1191 @LoginRequired()
1191 @LoginRequired()
1192 @HasPermissionAllDecorator('hg.admin')
1192 @HasPermissionAllDecorator('hg.admin')
1193 @view_config(
1193 @view_config(
1194 route_name='edit_user_perms_summary_json', request_method='GET',
1194 route_name='edit_user_perms_summary_json', request_method='GET',
1195 renderer='json_ext')
1195 renderer='json_ext')
1196 def user_perms_summary_json(self):
1196 def user_perms_summary_json(self):
1197 self.load_default_context()
1197 self.load_default_context()
1198 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1198 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1199
1199
1200 return perm_user.permissions
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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Authentication modules
22 Authentication modules
23 """
23 """
24 import socket
24 import socket
25 import string
25 import string
26 import colander
26 import colander
27 import copy
27 import copy
28 import logging
28 import logging
29 import time
29 import time
30 import traceback
30 import traceback
31 import warnings
31 import warnings
32 import functools
32 import functools
33
33
34 from pyramid.threadlocal import get_current_registry
34 from pyramid.threadlocal import get_current_registry
35
35
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.lib import caches
38 from rhodecode.lib import caches, rc_cache
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
40 from rhodecode.lib.utils2 import safe_int, safe_str
40 from rhodecode.lib.utils2 import safe_int, safe_str
41 from rhodecode.lib.exceptions import LdapConnectionError
41 from rhodecode.lib.exceptions import LdapConnectionError
42 from rhodecode.model.db import User
42 from rhodecode.model.db import User
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.user import UserModel
45 from rhodecode.model.user import UserModel
46 from rhodecode.model.user_group import UserGroupModel
46 from rhodecode.model.user_group import UserGroupModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51 # auth types that authenticate() function can receive
51 # auth types that authenticate() function can receive
52 VCS_TYPE = 'vcs'
52 VCS_TYPE = 'vcs'
53 HTTP_TYPE = 'http'
53 HTTP_TYPE = 'http'
54
54
55
55
56 class hybrid_property(object):
56 class hybrid_property(object):
57 """
57 """
58 a property decorator that works both for instance and class
58 a property decorator that works both for instance and class
59 """
59 """
60 def __init__(self, fget, fset=None, fdel=None, expr=None):
60 def __init__(self, fget, fset=None, fdel=None, expr=None):
61 self.fget = fget
61 self.fget = fget
62 self.fset = fset
62 self.fset = fset
63 self.fdel = fdel
63 self.fdel = fdel
64 self.expr = expr or fget
64 self.expr = expr or fget
65 functools.update_wrapper(self, fget)
65 functools.update_wrapper(self, fget)
66
66
67 def __get__(self, instance, owner):
67 def __get__(self, instance, owner):
68 if instance is None:
68 if instance is None:
69 return self.expr(owner)
69 return self.expr(owner)
70 else:
70 else:
71 return self.fget(instance)
71 return self.fget(instance)
72
72
73 def __set__(self, instance, value):
73 def __set__(self, instance, value):
74 self.fset(instance, value)
74 self.fset(instance, value)
75
75
76 def __delete__(self, instance):
76 def __delete__(self, instance):
77 self.fdel(instance)
77 self.fdel(instance)
78
78
79
79
80 class LazyFormencode(object):
80 class LazyFormencode(object):
81 def __init__(self, formencode_obj, *args, **kwargs):
81 def __init__(self, formencode_obj, *args, **kwargs):
82 self.formencode_obj = formencode_obj
82 self.formencode_obj = formencode_obj
83 self.args = args
83 self.args = args
84 self.kwargs = kwargs
84 self.kwargs = kwargs
85
85
86 def __call__(self, *args, **kwargs):
86 def __call__(self, *args, **kwargs):
87 from inspect import isfunction
87 from inspect import isfunction
88 formencode_obj = self.formencode_obj
88 formencode_obj = self.formencode_obj
89 if isfunction(formencode_obj):
89 if isfunction(formencode_obj):
90 # case we wrap validators into functions
90 # case we wrap validators into functions
91 formencode_obj = self.formencode_obj(*args, **kwargs)
91 formencode_obj = self.formencode_obj(*args, **kwargs)
92 return formencode_obj(*self.args, **self.kwargs)
92 return formencode_obj(*self.args, **self.kwargs)
93
93
94
94
95 class RhodeCodeAuthPluginBase(object):
95 class RhodeCodeAuthPluginBase(object):
96 # cache the authentication request for N amount of seconds. Some kind
96 # cache the authentication request for N amount of seconds. Some kind
97 # of authentication methods are very heavy and it's very efficient to cache
97 # of authentication methods are very heavy and it's very efficient to cache
98 # the result of a call. If it's set to None (default) cache is off
98 # the result of a call. If it's set to None (default) cache is off
99 AUTH_CACHE_TTL = None
99 AUTH_CACHE_TTL = None
100 AUTH_CACHE = {}
100 AUTH_CACHE = {}
101
101
102 auth_func_attrs = {
102 auth_func_attrs = {
103 "username": "unique username",
103 "username": "unique username",
104 "firstname": "first name",
104 "firstname": "first name",
105 "lastname": "last name",
105 "lastname": "last name",
106 "email": "email address",
106 "email": "email address",
107 "groups": '["list", "of", "groups"]',
107 "groups": '["list", "of", "groups"]',
108 "user_group_sync":
108 "user_group_sync":
109 'True|False defines if returned user groups should be synced',
109 'True|False defines if returned user groups should be synced',
110 "extern_name": "name in external source of record",
110 "extern_name": "name in external source of record",
111 "extern_type": "type of external source of record",
111 "extern_type": "type of external source of record",
112 "admin": 'True|False defines if user should be RhodeCode super admin',
112 "admin": 'True|False defines if user should be RhodeCode super admin',
113 "active":
113 "active":
114 'True|False defines active state of user internally for RhodeCode',
114 'True|False defines active state of user internally for RhodeCode',
115 "active_from_extern":
115 "active_from_extern":
116 "True|False\None, active state from the external auth, "
116 "True|False\None, active state from the external auth, "
117 "None means use definition from RhodeCode extern_type active value"
117 "None means use definition from RhodeCode extern_type active value"
118
118
119 }
119 }
120 # set on authenticate() method and via set_auth_type func.
120 # set on authenticate() method and via set_auth_type func.
121 auth_type = None
121 auth_type = None
122
122
123 # set on authenticate() method and via set_calling_scope_repo, this is a
123 # set on authenticate() method and via set_calling_scope_repo, this is a
124 # calling scope repository when doing authentication most likely on VCS
124 # calling scope repository when doing authentication most likely on VCS
125 # operations
125 # operations
126 acl_repo_name = None
126 acl_repo_name = None
127
127
128 # List of setting names to store encrypted. Plugins may override this list
128 # List of setting names to store encrypted. Plugins may override this list
129 # to store settings encrypted.
129 # to store settings encrypted.
130 _settings_encrypted = []
130 _settings_encrypted = []
131
131
132 # Mapping of python to DB settings model types. Plugins may override or
132 # Mapping of python to DB settings model types. Plugins may override or
133 # extend this mapping.
133 # extend this mapping.
134 _settings_type_map = {
134 _settings_type_map = {
135 colander.String: 'unicode',
135 colander.String: 'unicode',
136 colander.Integer: 'int',
136 colander.Integer: 'int',
137 colander.Boolean: 'bool',
137 colander.Boolean: 'bool',
138 colander.List: 'list',
138 colander.List: 'list',
139 }
139 }
140
140
141 # list of keys in settings that are unsafe to be logged, should be passwords
141 # list of keys in settings that are unsafe to be logged, should be passwords
142 # or other crucial credentials
142 # or other crucial credentials
143 _settings_unsafe_keys = []
143 _settings_unsafe_keys = []
144
144
145 def __init__(self, plugin_id):
145 def __init__(self, plugin_id):
146 self._plugin_id = plugin_id
146 self._plugin_id = plugin_id
147
147
148 def __str__(self):
148 def __str__(self):
149 return self.get_id()
149 return self.get_id()
150
150
151 def _get_setting_full_name(self, name):
151 def _get_setting_full_name(self, name):
152 """
152 """
153 Return the full setting name used for storing values in the database.
153 Return the full setting name used for storing values in the database.
154 """
154 """
155 # TODO: johbo: Using the name here is problematic. It would be good to
155 # TODO: johbo: Using the name here is problematic. It would be good to
156 # introduce either new models in the database to hold Plugin and
156 # introduce either new models in the database to hold Plugin and
157 # PluginSetting or to use the plugin id here.
157 # PluginSetting or to use the plugin id here.
158 return 'auth_{}_{}'.format(self.name, name)
158 return 'auth_{}_{}'.format(self.name, name)
159
159
160 def _get_setting_type(self, name):
160 def _get_setting_type(self, name):
161 """
161 """
162 Return the type of a setting. This type is defined by the SettingsModel
162 Return the type of a setting. This type is defined by the SettingsModel
163 and determines how the setting is stored in DB. Optionally the suffix
163 and determines how the setting is stored in DB. Optionally the suffix
164 `.encrypted` is appended to instruct SettingsModel to store it
164 `.encrypted` is appended to instruct SettingsModel to store it
165 encrypted.
165 encrypted.
166 """
166 """
167 schema_node = self.get_settings_schema().get(name)
167 schema_node = self.get_settings_schema().get(name)
168 db_type = self._settings_type_map.get(
168 db_type = self._settings_type_map.get(
169 type(schema_node.typ), 'unicode')
169 type(schema_node.typ), 'unicode')
170 if name in self._settings_encrypted:
170 if name in self._settings_encrypted:
171 db_type = '{}.encrypted'.format(db_type)
171 db_type = '{}.encrypted'.format(db_type)
172 return db_type
172 return db_type
173
173
174 def is_enabled(self):
174 def is_enabled(self):
175 """
175 """
176 Returns true if this plugin is enabled. An enabled plugin can be
176 Returns true if this plugin is enabled. An enabled plugin can be
177 configured in the admin interface but it is not consulted during
177 configured in the admin interface but it is not consulted during
178 authentication.
178 authentication.
179 """
179 """
180 auth_plugins = SettingsModel().get_auth_plugins()
180 auth_plugins = SettingsModel().get_auth_plugins()
181 return self.get_id() in auth_plugins
181 return self.get_id() in auth_plugins
182
182
183 def is_active(self, plugin_cached_settings=None):
183 def is_active(self, plugin_cached_settings=None):
184 """
184 """
185 Returns true if the plugin is activated. An activated plugin is
185 Returns true if the plugin is activated. An activated plugin is
186 consulted during authentication, assumed it is also enabled.
186 consulted during authentication, assumed it is also enabled.
187 """
187 """
188 return self.get_setting_by_name(
188 return self.get_setting_by_name(
189 'enabled', plugin_cached_settings=plugin_cached_settings)
189 'enabled', plugin_cached_settings=plugin_cached_settings)
190
190
191 def get_id(self):
191 def get_id(self):
192 """
192 """
193 Returns the plugin id.
193 Returns the plugin id.
194 """
194 """
195 return self._plugin_id
195 return self._plugin_id
196
196
197 def get_display_name(self):
197 def get_display_name(self):
198 """
198 """
199 Returns a translation string for displaying purposes.
199 Returns a translation string for displaying purposes.
200 """
200 """
201 raise NotImplementedError('Not implemented in base class')
201 raise NotImplementedError('Not implemented in base class')
202
202
203 def get_settings_schema(self):
203 def get_settings_schema(self):
204 """
204 """
205 Returns a colander schema, representing the plugin settings.
205 Returns a colander schema, representing the plugin settings.
206 """
206 """
207 return AuthnPluginSettingsSchemaBase()
207 return AuthnPluginSettingsSchemaBase()
208
208
209 def get_settings(self):
209 def get_settings(self):
210 """
210 """
211 Returns the plugin settings as dictionary.
211 Returns the plugin settings as dictionary.
212 """
212 """
213 settings = {}
213 settings = {}
214 raw_settings = SettingsModel().get_all_settings()
214 raw_settings = SettingsModel().get_all_settings()
215 for node in self.get_settings_schema():
215 for node in self.get_settings_schema():
216 settings[node.name] = self.get_setting_by_name(
216 settings[node.name] = self.get_setting_by_name(
217 node.name, plugin_cached_settings=raw_settings)
217 node.name, plugin_cached_settings=raw_settings)
218 return settings
218 return settings
219
219
220 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
220 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
221 """
221 """
222 Returns a plugin setting by name.
222 Returns a plugin setting by name.
223 """
223 """
224 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
224 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
225 if plugin_cached_settings:
225 if plugin_cached_settings:
226 plugin_settings = plugin_cached_settings
226 plugin_settings = plugin_cached_settings
227 else:
227 else:
228 plugin_settings = SettingsModel().get_all_settings()
228 plugin_settings = SettingsModel().get_all_settings()
229
229
230 if full_name in plugin_settings:
230 if full_name in plugin_settings:
231 return plugin_settings[full_name]
231 return plugin_settings[full_name]
232 else:
232 else:
233 return default
233 return default
234
234
235 def create_or_update_setting(self, name, value):
235 def create_or_update_setting(self, name, value):
236 """
236 """
237 Create or update a setting for this plugin in the persistent storage.
237 Create or update a setting for this plugin in the persistent storage.
238 """
238 """
239 full_name = self._get_setting_full_name(name)
239 full_name = self._get_setting_full_name(name)
240 type_ = self._get_setting_type(name)
240 type_ = self._get_setting_type(name)
241 db_setting = SettingsModel().create_or_update_setting(
241 db_setting = SettingsModel().create_or_update_setting(
242 full_name, value, type_)
242 full_name, value, type_)
243 return db_setting.app_settings_value
243 return db_setting.app_settings_value
244
244
245 def log_safe_settings(self, settings):
245 def log_safe_settings(self, settings):
246 """
246 """
247 returns a log safe representation of settings, without any secrets
247 returns a log safe representation of settings, without any secrets
248 """
248 """
249 settings_copy = copy.deepcopy(settings)
249 settings_copy = copy.deepcopy(settings)
250 for k in self._settings_unsafe_keys:
250 for k in self._settings_unsafe_keys:
251 if k in settings_copy:
251 if k in settings_copy:
252 del settings_copy[k]
252 del settings_copy[k]
253 return settings_copy
253 return settings_copy
254
254
255 @hybrid_property
255 @hybrid_property
256 def name(self):
256 def name(self):
257 """
257 """
258 Returns the name of this authentication plugin.
258 Returns the name of this authentication plugin.
259
259
260 :returns: string
260 :returns: string
261 """
261 """
262 raise NotImplementedError("Not implemented in base class")
262 raise NotImplementedError("Not implemented in base class")
263
263
264 def get_url_slug(self):
264 def get_url_slug(self):
265 """
265 """
266 Returns a slug which should be used when constructing URLs which refer
266 Returns a slug which should be used when constructing URLs which refer
267 to this plugin. By default it returns the plugin name. If the name is
267 to this plugin. By default it returns the plugin name. If the name is
268 not suitable for using it in an URL the plugin should override this
268 not suitable for using it in an URL the plugin should override this
269 method.
269 method.
270 """
270 """
271 return self.name
271 return self.name
272
272
273 @property
273 @property
274 def is_headers_auth(self):
274 def is_headers_auth(self):
275 """
275 """
276 Returns True if this authentication plugin uses HTTP headers as
276 Returns True if this authentication plugin uses HTTP headers as
277 authentication method.
277 authentication method.
278 """
278 """
279 return False
279 return False
280
280
281 @hybrid_property
281 @hybrid_property
282 def is_container_auth(self):
282 def is_container_auth(self):
283 """
283 """
284 Deprecated method that indicates if this authentication plugin uses
284 Deprecated method that indicates if this authentication plugin uses
285 HTTP headers as authentication method.
285 HTTP headers as authentication method.
286 """
286 """
287 warnings.warn(
287 warnings.warn(
288 'Use is_headers_auth instead.', category=DeprecationWarning)
288 'Use is_headers_auth instead.', category=DeprecationWarning)
289 return self.is_headers_auth
289 return self.is_headers_auth
290
290
291 @hybrid_property
291 @hybrid_property
292 def allows_creating_users(self):
292 def allows_creating_users(self):
293 """
293 """
294 Defines if Plugin allows users to be created on-the-fly when
294 Defines if Plugin allows users to be created on-the-fly when
295 authentication is called. Controls how external plugins should behave
295 authentication is called. Controls how external plugins should behave
296 in terms if they are allowed to create new users, or not. Base plugins
296 in terms if they are allowed to create new users, or not. Base plugins
297 should not be allowed to, but External ones should be !
297 should not be allowed to, but External ones should be !
298
298
299 :return: bool
299 :return: bool
300 """
300 """
301 return False
301 return False
302
302
303 def set_auth_type(self, auth_type):
303 def set_auth_type(self, auth_type):
304 self.auth_type = auth_type
304 self.auth_type = auth_type
305
305
306 def set_calling_scope_repo(self, acl_repo_name):
306 def set_calling_scope_repo(self, acl_repo_name):
307 self.acl_repo_name = acl_repo_name
307 self.acl_repo_name = acl_repo_name
308
308
309 def allows_authentication_from(
309 def allows_authentication_from(
310 self, user, allows_non_existing_user=True,
310 self, user, allows_non_existing_user=True,
311 allowed_auth_plugins=None, allowed_auth_sources=None):
311 allowed_auth_plugins=None, allowed_auth_sources=None):
312 """
312 """
313 Checks if this authentication module should accept a request for
313 Checks if this authentication module should accept a request for
314 the current user.
314 the current user.
315
315
316 :param user: user object fetched using plugin's get_user() method.
316 :param user: user object fetched using plugin's get_user() method.
317 :param allows_non_existing_user: if True, don't allow the
317 :param allows_non_existing_user: if True, don't allow the
318 user to be empty, meaning not existing in our database
318 user to be empty, meaning not existing in our database
319 :param allowed_auth_plugins: if provided, users extern_type will be
319 :param allowed_auth_plugins: if provided, users extern_type will be
320 checked against a list of provided extern types, which are plugin
320 checked against a list of provided extern types, which are plugin
321 auth_names in the end
321 auth_names in the end
322 :param allowed_auth_sources: authentication type allowed,
322 :param allowed_auth_sources: authentication type allowed,
323 `http` or `vcs` default is both.
323 `http` or `vcs` default is both.
324 defines if plugin will accept only http authentication vcs
324 defines if plugin will accept only http authentication vcs
325 authentication(git/hg) or both
325 authentication(git/hg) or both
326 :returns: boolean
326 :returns: boolean
327 """
327 """
328 if not user and not allows_non_existing_user:
328 if not user and not allows_non_existing_user:
329 log.debug('User is empty but plugin does not allow empty users,'
329 log.debug('User is empty but plugin does not allow empty users,'
330 'not allowed to authenticate')
330 'not allowed to authenticate')
331 return False
331 return False
332
332
333 expected_auth_plugins = allowed_auth_plugins or [self.name]
333 expected_auth_plugins = allowed_auth_plugins or [self.name]
334 if user and (user.extern_type and
334 if user and (user.extern_type and
335 user.extern_type not in expected_auth_plugins):
335 user.extern_type not in expected_auth_plugins):
336 log.debug(
336 log.debug(
337 'User `%s` is bound to `%s` auth type. Plugin allows only '
337 'User `%s` is bound to `%s` auth type. Plugin allows only '
338 '%s, skipping', user, user.extern_type, expected_auth_plugins)
338 '%s, skipping', user, user.extern_type, expected_auth_plugins)
339
339
340 return False
340 return False
341
341
342 # by default accept both
342 # by default accept both
343 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
343 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
344 if self.auth_type not in expected_auth_from:
344 if self.auth_type not in expected_auth_from:
345 log.debug('Current auth source is %s but plugin only allows %s',
345 log.debug('Current auth source is %s but plugin only allows %s',
346 self.auth_type, expected_auth_from)
346 self.auth_type, expected_auth_from)
347 return False
347 return False
348
348
349 return True
349 return True
350
350
351 def get_user(self, username=None, **kwargs):
351 def get_user(self, username=None, **kwargs):
352 """
352 """
353 Helper method for user fetching in plugins, by default it's using
353 Helper method for user fetching in plugins, by default it's using
354 simple fetch by username, but this method can be custimized in plugins
354 simple fetch by username, but this method can be custimized in plugins
355 eg. headers auth plugin to fetch user by environ params
355 eg. headers auth plugin to fetch user by environ params
356
356
357 :param username: username if given to fetch from database
357 :param username: username if given to fetch from database
358 :param kwargs: extra arguments needed for user fetching.
358 :param kwargs: extra arguments needed for user fetching.
359 """
359 """
360 user = None
360 user = None
361 log.debug(
361 log.debug(
362 'Trying to fetch user `%s` from RhodeCode database', username)
362 'Trying to fetch user `%s` from RhodeCode database', username)
363 if username:
363 if username:
364 user = User.get_by_username(username)
364 user = User.get_by_username(username)
365 if not user:
365 if not user:
366 log.debug('User not found, fallback to fetch user in '
366 log.debug('User not found, fallback to fetch user in '
367 'case insensitive mode')
367 'case insensitive mode')
368 user = User.get_by_username(username, case_insensitive=True)
368 user = User.get_by_username(username, case_insensitive=True)
369 else:
369 else:
370 log.debug('provided username:`%s` is empty skipping...', username)
370 log.debug('provided username:`%s` is empty skipping...', username)
371 if not user:
371 if not user:
372 log.debug('User `%s` not found in database', username)
372 log.debug('User `%s` not found in database', username)
373 else:
373 else:
374 log.debug('Got DB user:%s', user)
374 log.debug('Got DB user:%s', user)
375 return user
375 return user
376
376
377 def user_activation_state(self):
377 def user_activation_state(self):
378 """
378 """
379 Defines user activation state when creating new users
379 Defines user activation state when creating new users
380
380
381 :returns: boolean
381 :returns: boolean
382 """
382 """
383 raise NotImplementedError("Not implemented in base class")
383 raise NotImplementedError("Not implemented in base class")
384
384
385 def auth(self, userobj, username, passwd, settings, **kwargs):
385 def auth(self, userobj, username, passwd, settings, **kwargs):
386 """
386 """
387 Given a user object (which may be null), username, a plaintext
387 Given a user object (which may be null), username, a plaintext
388 password, and a settings object (containing all the keys needed as
388 password, and a settings object (containing all the keys needed as
389 listed in settings()), authenticate this user's login attempt.
389 listed in settings()), authenticate this user's login attempt.
390
390
391 Return None on failure. On success, return a dictionary of the form:
391 Return None on failure. On success, return a dictionary of the form:
392
392
393 see: RhodeCodeAuthPluginBase.auth_func_attrs
393 see: RhodeCodeAuthPluginBase.auth_func_attrs
394 This is later validated for correctness
394 This is later validated for correctness
395 """
395 """
396 raise NotImplementedError("not implemented in base class")
396 raise NotImplementedError("not implemented in base class")
397
397
398 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
398 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
399 """
399 """
400 Wrapper to call self.auth() that validates call on it
400 Wrapper to call self.auth() that validates call on it
401
401
402 :param userobj: userobj
402 :param userobj: userobj
403 :param username: username
403 :param username: username
404 :param passwd: plaintext password
404 :param passwd: plaintext password
405 :param settings: plugin settings
405 :param settings: plugin settings
406 """
406 """
407 auth = self.auth(userobj, username, passwd, settings, **kwargs)
407 auth = self.auth(userobj, username, passwd, settings, **kwargs)
408 if auth:
408 if auth:
409 auth['_plugin'] = self.name
409 auth['_plugin'] = self.name
410 auth['_ttl_cache'] = self.get_ttl_cache(settings)
410 auth['_ttl_cache'] = self.get_ttl_cache(settings)
411 # check if hash should be migrated ?
411 # check if hash should be migrated ?
412 new_hash = auth.get('_hash_migrate')
412 new_hash = auth.get('_hash_migrate')
413 if new_hash:
413 if new_hash:
414 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
414 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
415 if 'user_group_sync' not in auth:
415 if 'user_group_sync' not in auth:
416 auth['user_group_sync'] = False
416 auth['user_group_sync'] = False
417 return self._validate_auth_return(auth)
417 return self._validate_auth_return(auth)
418 return auth
418 return auth
419
419
420 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
420 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
421 new_hash_cypher = _RhodeCodeCryptoBCrypt()
421 new_hash_cypher = _RhodeCodeCryptoBCrypt()
422 # extra checks, so make sure new hash is correct.
422 # extra checks, so make sure new hash is correct.
423 password_encoded = safe_str(password)
423 password_encoded = safe_str(password)
424 if new_hash and new_hash_cypher.hash_check(
424 if new_hash and new_hash_cypher.hash_check(
425 password_encoded, new_hash):
425 password_encoded, new_hash):
426 cur_user = User.get_by_username(username)
426 cur_user = User.get_by_username(username)
427 cur_user.password = new_hash
427 cur_user.password = new_hash
428 Session().add(cur_user)
428 Session().add(cur_user)
429 Session().flush()
429 Session().flush()
430 log.info('Migrated user %s hash to bcrypt', cur_user)
430 log.info('Migrated user %s hash to bcrypt', cur_user)
431
431
432 def _validate_auth_return(self, ret):
432 def _validate_auth_return(self, ret):
433 if not isinstance(ret, dict):
433 if not isinstance(ret, dict):
434 raise Exception('returned value from auth must be a dict')
434 raise Exception('returned value from auth must be a dict')
435 for k in self.auth_func_attrs:
435 for k in self.auth_func_attrs:
436 if k not in ret:
436 if k not in ret:
437 raise Exception('Missing %s attribute from returned data' % k)
437 raise Exception('Missing %s attribute from returned data' % k)
438 return ret
438 return ret
439
439
440 def get_ttl_cache(self, settings=None):
440 def get_ttl_cache(self, settings=None):
441 plugin_settings = settings or self.get_settings()
441 plugin_settings = settings or self.get_settings()
442 cache_ttl = 0
442 cache_ttl = 0
443
443
444 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
444 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
445 # plugin cache set inside is more important than the settings value
445 # plugin cache set inside is more important than the settings value
446 cache_ttl = self.AUTH_CACHE_TTL
446 cache_ttl = self.AUTH_CACHE_TTL
447 elif plugin_settings.get('cache_ttl'):
447 elif plugin_settings.get('cache_ttl'):
448 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
448 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
449
449
450 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
450 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
451 return plugin_cache_active, cache_ttl
451 return plugin_cache_active, cache_ttl
452
452
453
453
454 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
454 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
455
455
456 @hybrid_property
456 @hybrid_property
457 def allows_creating_users(self):
457 def allows_creating_users(self):
458 return True
458 return True
459
459
460 def use_fake_password(self):
460 def use_fake_password(self):
461 """
461 """
462 Return a boolean that indicates whether or not we should set the user's
462 Return a boolean that indicates whether or not we should set the user's
463 password to a random value when it is authenticated by this plugin.
463 password to a random value when it is authenticated by this plugin.
464 If your plugin provides authentication, then you will generally
464 If your plugin provides authentication, then you will generally
465 want this.
465 want this.
466
466
467 :returns: boolean
467 :returns: boolean
468 """
468 """
469 raise NotImplementedError("Not implemented in base class")
469 raise NotImplementedError("Not implemented in base class")
470
470
471 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
471 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
472 # at this point _authenticate calls plugin's `auth()` function
472 # at this point _authenticate calls plugin's `auth()` function
473 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
473 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
474 userobj, username, passwd, settings, **kwargs)
474 userobj, username, passwd, settings, **kwargs)
475
475
476 if auth:
476 if auth:
477 # maybe plugin will clean the username ?
477 # maybe plugin will clean the username ?
478 # we should use the return value
478 # we should use the return value
479 username = auth['username']
479 username = auth['username']
480
480
481 # if external source tells us that user is not active, we should
481 # if external source tells us that user is not active, we should
482 # skip rest of the process. This can prevent from creating users in
482 # skip rest of the process. This can prevent from creating users in
483 # RhodeCode when using external authentication, but if it's
483 # RhodeCode when using external authentication, but if it's
484 # inactive user we shouldn't create that user anyway
484 # inactive user we shouldn't create that user anyway
485 if auth['active_from_extern'] is False:
485 if auth['active_from_extern'] is False:
486 log.warning(
486 log.warning(
487 "User %s authenticated against %s, but is inactive",
487 "User %s authenticated against %s, but is inactive",
488 username, self.__module__)
488 username, self.__module__)
489 return None
489 return None
490
490
491 cur_user = User.get_by_username(username, case_insensitive=True)
491 cur_user = User.get_by_username(username, case_insensitive=True)
492 is_user_existing = cur_user is not None
492 is_user_existing = cur_user is not None
493
493
494 if is_user_existing:
494 if is_user_existing:
495 log.debug('Syncing user `%s` from '
495 log.debug('Syncing user `%s` from '
496 '`%s` plugin', username, self.name)
496 '`%s` plugin', username, self.name)
497 else:
497 else:
498 log.debug('Creating non existing user `%s` from '
498 log.debug('Creating non existing user `%s` from '
499 '`%s` plugin', username, self.name)
499 '`%s` plugin', username, self.name)
500
500
501 if self.allows_creating_users:
501 if self.allows_creating_users:
502 log.debug('Plugin `%s` allows to '
502 log.debug('Plugin `%s` allows to '
503 'create new users', self.name)
503 'create new users', self.name)
504 else:
504 else:
505 log.debug('Plugin `%s` does not allow to '
505 log.debug('Plugin `%s` does not allow to '
506 'create new users', self.name)
506 'create new users', self.name)
507
507
508 user_parameters = {
508 user_parameters = {
509 'username': username,
509 'username': username,
510 'email': auth["email"],
510 'email': auth["email"],
511 'firstname': auth["firstname"],
511 'firstname': auth["firstname"],
512 'lastname': auth["lastname"],
512 'lastname': auth["lastname"],
513 'active': auth["active"],
513 'active': auth["active"],
514 'admin': auth["admin"],
514 'admin': auth["admin"],
515 'extern_name': auth["extern_name"],
515 'extern_name': auth["extern_name"],
516 'extern_type': self.name,
516 'extern_type': self.name,
517 'plugin': self,
517 'plugin': self,
518 'allow_to_create_user': self.allows_creating_users,
518 'allow_to_create_user': self.allows_creating_users,
519 }
519 }
520
520
521 if not is_user_existing:
521 if not is_user_existing:
522 if self.use_fake_password():
522 if self.use_fake_password():
523 # Randomize the PW because we don't need it, but don't want
523 # Randomize the PW because we don't need it, but don't want
524 # them blank either
524 # them blank either
525 passwd = PasswordGenerator().gen_password(length=16)
525 passwd = PasswordGenerator().gen_password(length=16)
526 user_parameters['password'] = passwd
526 user_parameters['password'] = passwd
527 else:
527 else:
528 # Since the password is required by create_or_update method of
528 # Since the password is required by create_or_update method of
529 # UserModel, we need to set it explicitly.
529 # UserModel, we need to set it explicitly.
530 # The create_or_update method is smart and recognises the
530 # The create_or_update method is smart and recognises the
531 # password hashes as well.
531 # password hashes as well.
532 user_parameters['password'] = cur_user.password
532 user_parameters['password'] = cur_user.password
533
533
534 # we either create or update users, we also pass the flag
534 # we either create or update users, we also pass the flag
535 # that controls if this method can actually do that.
535 # that controls if this method can actually do that.
536 # raises NotAllowedToCreateUserError if it cannot, and we try to.
536 # raises NotAllowedToCreateUserError if it cannot, and we try to.
537 user = UserModel().create_or_update(**user_parameters)
537 user = UserModel().create_or_update(**user_parameters)
538 Session().flush()
538 Session().flush()
539 # enforce user is just in given groups, all of them has to be ones
539 # enforce user is just in given groups, all of them has to be ones
540 # created from plugins. We store this info in _group_data JSON
540 # created from plugins. We store this info in _group_data JSON
541 # field
541 # field
542
542
543 if auth['user_group_sync']:
543 if auth['user_group_sync']:
544 try:
544 try:
545 groups = auth['groups'] or []
545 groups = auth['groups'] or []
546 log.debug(
546 log.debug(
547 'Performing user_group sync based on set `%s` '
547 'Performing user_group sync based on set `%s` '
548 'returned by `%s` plugin', groups, self.name)
548 'returned by `%s` plugin', groups, self.name)
549 UserGroupModel().enforce_groups(user, groups, self.name)
549 UserGroupModel().enforce_groups(user, groups, self.name)
550 except Exception:
550 except Exception:
551 # for any reason group syncing fails, we should
551 # for any reason group syncing fails, we should
552 # proceed with login
552 # proceed with login
553 log.error(traceback.format_exc())
553 log.error(traceback.format_exc())
554
554
555 Session().commit()
555 Session().commit()
556 return auth
556 return auth
557
557
558
558
559 class AuthLdapBase(object):
559 class AuthLdapBase(object):
560
560
561 @classmethod
561 @classmethod
562 def _build_servers(cls, ldap_server_type, ldap_server, port):
562 def _build_servers(cls, ldap_server_type, ldap_server, port):
563 def host_resolver(host, port, full_resolve=True):
563 def host_resolver(host, port, full_resolve=True):
564 """
564 """
565 Main work for this function is to prevent ldap connection issues,
565 Main work for this function is to prevent ldap connection issues,
566 and detect them early using a "greenified" sockets
566 and detect them early using a "greenified" sockets
567 """
567 """
568 host = host.strip()
568 host = host.strip()
569 if not full_resolve:
569 if not full_resolve:
570 return '{}:{}'.format(host, port)
570 return '{}:{}'.format(host, port)
571
571
572 log.debug('LDAP: Resolving IP for LDAP host %s', host)
572 log.debug('LDAP: Resolving IP for LDAP host %s', host)
573 try:
573 try:
574 ip = socket.gethostbyname(host)
574 ip = socket.gethostbyname(host)
575 log.debug('Got LDAP server %s ip %s', host, ip)
575 log.debug('Got LDAP server %s ip %s', host, ip)
576 except Exception:
576 except Exception:
577 raise LdapConnectionError(
577 raise LdapConnectionError(
578 'Failed to resolve host: `{}`'.format(host))
578 'Failed to resolve host: `{}`'.format(host))
579
579
580 log.debug('LDAP: Checking if IP %s is accessible', ip)
580 log.debug('LDAP: Checking if IP %s is accessible', ip)
581 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
581 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
582 try:
582 try:
583 s.connect((ip, int(port)))
583 s.connect((ip, int(port)))
584 s.shutdown(socket.SHUT_RD)
584 s.shutdown(socket.SHUT_RD)
585 except Exception:
585 except Exception:
586 raise LdapConnectionError(
586 raise LdapConnectionError(
587 'Failed to connect to host: `{}:{}`'.format(host, port))
587 'Failed to connect to host: `{}:{}`'.format(host, port))
588
588
589 return '{}:{}'.format(host, port)
589 return '{}:{}'.format(host, port)
590
590
591 if len(ldap_server) == 1:
591 if len(ldap_server) == 1:
592 # in case of single server use resolver to detect potential
592 # in case of single server use resolver to detect potential
593 # connection issues
593 # connection issues
594 full_resolve = True
594 full_resolve = True
595 else:
595 else:
596 full_resolve = False
596 full_resolve = False
597
597
598 return ', '.join(
598 return ', '.join(
599 ["{}://{}".format(
599 ["{}://{}".format(
600 ldap_server_type,
600 ldap_server_type,
601 host_resolver(host, port, full_resolve=full_resolve))
601 host_resolver(host, port, full_resolve=full_resolve))
602 for host in ldap_server])
602 for host in ldap_server])
603
603
604 @classmethod
604 @classmethod
605 def _get_server_list(cls, servers):
605 def _get_server_list(cls, servers):
606 return map(string.strip, servers.split(','))
606 return map(string.strip, servers.split(','))
607
607
608 @classmethod
608 @classmethod
609 def get_uid(cls, username, server_addresses):
609 def get_uid(cls, username, server_addresses):
610 uid = username
610 uid = username
611 for server_addr in server_addresses:
611 for server_addr in server_addresses:
612 uid = chop_at(username, "@%s" % server_addr)
612 uid = chop_at(username, "@%s" % server_addr)
613 return uid
613 return uid
614
614
615
615
616 def loadplugin(plugin_id):
616 def loadplugin(plugin_id):
617 """
617 """
618 Loads and returns an instantiated authentication plugin.
618 Loads and returns an instantiated authentication plugin.
619 Returns the RhodeCodeAuthPluginBase subclass on success,
619 Returns the RhodeCodeAuthPluginBase subclass on success,
620 or None on failure.
620 or None on failure.
621 """
621 """
622 # TODO: Disusing pyramids thread locals to retrieve the registry.
622 # TODO: Disusing pyramids thread locals to retrieve the registry.
623 authn_registry = get_authn_registry()
623 authn_registry = get_authn_registry()
624 plugin = authn_registry.get_plugin(plugin_id)
624 plugin = authn_registry.get_plugin(plugin_id)
625 if plugin is None:
625 if plugin is None:
626 log.error('Authentication plugin not found: "%s"', plugin_id)
626 log.error('Authentication plugin not found: "%s"', plugin_id)
627 return plugin
627 return plugin
628
628
629
629
630 def get_authn_registry(registry=None):
630 def get_authn_registry(registry=None):
631 registry = registry or get_current_registry()
631 registry = registry or get_current_registry()
632 authn_registry = registry.getUtility(IAuthnPluginRegistry)
632 authn_registry = registry.getUtility(IAuthnPluginRegistry)
633 return authn_registry
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 def authenticate(username, password, environ=None, auth_type=None,
636 def authenticate(username, password, environ=None, auth_type=None,
653 skip_missing=False, registry=None, acl_repo_name=None):
637 skip_missing=False, registry=None, acl_repo_name=None):
654 """
638 """
655 Authentication function used for access control,
639 Authentication function used for access control,
656 It tries to authenticate based on enabled authentication modules.
640 It tries to authenticate based on enabled authentication modules.
657
641
658 :param username: username can be empty for headers auth
642 :param username: username can be empty for headers auth
659 :param password: password can be empty for headers auth
643 :param password: password can be empty for headers auth
660 :param environ: environ headers passed for headers auth
644 :param environ: environ headers passed for headers auth
661 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
645 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
662 :param skip_missing: ignores plugins that are in db but not in environment
646 :param skip_missing: ignores plugins that are in db but not in environment
663 :returns: None if auth failed, plugin_user dict if auth is correct
647 :returns: None if auth failed, plugin_user dict if auth is correct
664 """
648 """
665 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
649 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
666 raise ValueError('auth type must be on of http, vcs got "%s" instead'
650 raise ValueError('auth type must be on of http, vcs got "%s" instead'
667 % auth_type)
651 % auth_type)
668 headers_only = environ and not (username and password)
652 headers_only = environ and not (username and password)
669
653
670 authn_registry = get_authn_registry(registry)
654 authn_registry = get_authn_registry(registry)
671 plugins_to_check = authn_registry.get_plugins_for_authentication()
655 plugins_to_check = authn_registry.get_plugins_for_authentication()
672 log.debug('Starting ordered authentication chain using %s plugins',
656 log.debug('Starting ordered authentication chain using %s plugins',
673 [x.name for x in plugins_to_check])
657 [x.name for x in plugins_to_check])
674 for plugin in plugins_to_check:
658 for plugin in plugins_to_check:
675 plugin.set_auth_type(auth_type)
659 plugin.set_auth_type(auth_type)
676 plugin.set_calling_scope_repo(acl_repo_name)
660 plugin.set_calling_scope_repo(acl_repo_name)
677
661
678 if headers_only and not plugin.is_headers_auth:
662 if headers_only and not plugin.is_headers_auth:
679 log.debug('Auth type is for headers only and plugin `%s` is not '
663 log.debug('Auth type is for headers only and plugin `%s` is not '
680 'headers plugin, skipping...', plugin.get_id())
664 'headers plugin, skipping...', plugin.get_id())
681 continue
665 continue
682
666
683 log.debug('Trying authentication using ** %s **', plugin.get_id())
667 log.debug('Trying authentication using ** %s **', plugin.get_id())
684
668
685 # load plugin settings from RhodeCode database
669 # load plugin settings from RhodeCode database
686 plugin_settings = plugin.get_settings()
670 plugin_settings = plugin.get_settings()
687 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
671 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
688 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
672 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
689
673
690 # use plugin's method of user extraction.
674 # use plugin's method of user extraction.
691 user = plugin.get_user(username, environ=environ,
675 user = plugin.get_user(username, environ=environ,
692 settings=plugin_settings)
676 settings=plugin_settings)
693 display_user = user.username if user else username
677 display_user = user.username if user else username
694 log.debug(
678 log.debug(
695 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
679 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
696
680
697 if not plugin.allows_authentication_from(user):
681 if not plugin.allows_authentication_from(user):
698 log.debug('Plugin %s does not accept user `%s` for authentication',
682 log.debug('Plugin %s does not accept user `%s` for authentication',
699 plugin.get_id(), display_user)
683 plugin.get_id(), display_user)
700 continue
684 continue
701 else:
685 else:
702 log.debug('Plugin %s accepted user `%s` for authentication',
686 log.debug('Plugin %s accepted user `%s` for authentication',
703 plugin.get_id(), display_user)
687 plugin.get_id(), display_user)
704
688
705 log.info('Authenticating user `%s` using %s plugin',
689 log.info('Authenticating user `%s` using %s plugin',
706 display_user, plugin.get_id())
690 display_user, plugin.get_id())
707
691
708 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
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 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
694 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
715 plugin.get_id(), plugin_cache_active, cache_ttl)
695 plugin.get_id(), plugin_cache_active, cache_ttl)
716
696
717 # for environ based password can be empty, but then the validation is
697 user_id = user.user_id
718 # on the server that fills in the env data needed for authentication
698 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
719
699 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
720 _password_hash = caches.compute_key_from_params(
721 plugin.name, username, (password or ''))
722
700
723 # _authenticate is a wrapper for .auth() method of plugin.
701 @region.cache_on_arguments(namespace=cache_namespace_uid,
724 # it checks if .auth() sends proper data.
702 expiration_time=cache_ttl,
725 # For RhodeCodeExternalAuthPlugin it also maps users to
703 should_cache_fn=lambda v: plugin_cache_active)
726 # Database and maps the attributes returned from .auth()
704 def compute_auth(
727 # to RhodeCode database. If this function returns data
705 cache_name, plugin_name, username, password):
728 # then auth is correct.
729 start = time.time()
730 log.debug('Running plugin `%s` _authenticate method', plugin.get_id())
731
706
732 def auth_func():
707 # _authenticate is a wrapper for .auth() method of plugin.
733 """
708 # it checks if .auth() sends proper data.
734 This function is used internally in Cache of Beaker to calculate
709 # For RhodeCodeExternalAuthPlugin it also maps users to
735 Results
710 # Database and maps the attributes returned from .auth()
736 """
711 # to RhodeCode database. If this function returns data
737 log.debug('auth: calculating password access now...')
712 # then auth is correct.
713 log.debug('Running plugin `%s` _authenticate method '
714 'using username and password', plugin.get_id())
738 return plugin._authenticate(
715 return plugin._authenticate(
739 user, username, password, plugin_settings,
716 user, username, password, plugin_settings,
740 environ=environ or {})
717 environ=environ or {})
741
718
742 if plugin_cache_active:
719 start = time.time()
743 log.debug('Trying to fetch cached auth by pwd hash `...%s`',
720 # for environ based auth, password can be empty, but then the validation is
744 _password_hash[:6])
721 # on the server that fills in the env data needed for authentication
745 plugin_user = cache_manager.get(
722 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
746 _password_hash, createfunc=auth_func)
747 else:
748 plugin_user = auth_func()
749
723
750 auth_time = time.time() - start
724 auth_time = time.time() - start
751 log.debug('Authentication for plugin `%s` completed in %.3fs, '
725 log.debug('Authentication for plugin `%s` completed in %.3fs, '
752 'expiration time of fetched cache %.1fs.',
726 'expiration time of fetched cache %.1fs.',
753 plugin.get_id(), auth_time, cache_ttl)
727 plugin.get_id(), auth_time, cache_ttl)
754
728
755 log.debug('PLUGIN USER DATA: %s', plugin_user)
729 log.debug('PLUGIN USER DATA: %s', plugin_user)
756
730
757 if plugin_user:
731 if plugin_user:
758 log.debug('Plugin returned proper authentication data')
732 log.debug('Plugin returned proper authentication data')
759 return plugin_user
733 return plugin_user
760 # we failed to Auth because .auth() method didn't return proper user
734 # we failed to Auth because .auth() method didn't return proper user
761 log.debug("User `%s` failed to authenticate against %s",
735 log.debug("User `%s` failed to authenticate against %s",
762 display_user, plugin.get_id())
736 display_user, plugin.get_id())
763
737
764 # case when we failed to authenticate against all defined plugins
738 # case when we failed to authenticate against all defined plugins
765 return None
739 return None
766
740
767
741
768 def chop_at(s, sub, inclusive=False):
742 def chop_at(s, sub, inclusive=False):
769 """Truncate string ``s`` at the first occurrence of ``sub``.
743 """Truncate string ``s`` at the first occurrence of ``sub``.
770
744
771 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
745 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
772
746
773 >>> chop_at("plutocratic brats", "rat")
747 >>> chop_at("plutocratic brats", "rat")
774 'plutoc'
748 'plutoc'
775 >>> chop_at("plutocratic brats", "rat", True)
749 >>> chop_at("plutocratic brats", "rat", True)
776 'plutocrat'
750 'plutocrat'
777 """
751 """
778 pos = s.find(sub)
752 pos = s.find(sub)
779 if pos == -1:
753 if pos == -1:
780 return s
754 return s
781 if inclusive:
755 if inclusive:
782 return s[:pos+len(sub)]
756 return s[:pos+len(sub)]
783 return s[:pos]
757 return s[:pos]
@@ -1,191 +1,180 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22 import formencode.htmlfill
22 import formencode.htmlfill
23 import logging
23 import logging
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.apps._base import BaseAppView
29 from rhodecode.apps._base import BaseAppView
30 from rhodecode.authentication.base import (
30 from rhodecode.authentication.base import get_authn_registry
31 get_auth_cache_manager, get_perms_cache_manager, get_authn_registry)
32 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
33 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
35 from rhodecode.lib.caches import clear_cache_manager
34 from rhodecode.lib.caches import clear_cache_manager
36 from rhodecode.model.forms import AuthSettingsForm
35 from rhodecode.model.forms import AuthSettingsForm
37 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
38 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
39
38
40 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
41
40
42
41
43 class AuthnPluginViewBase(BaseAppView):
42 class AuthnPluginViewBase(BaseAppView):
44
43
45 def load_default_context(self):
44 def load_default_context(self):
46 c = self._get_local_tmpl_context()
45 c = self._get_local_tmpl_context()
47 self.plugin = self.context.plugin
46 self.plugin = self.context.plugin
48 return c
47 return c
49
48
50 @LoginRequired()
49 @LoginRequired()
51 @HasPermissionAllDecorator('hg.admin')
50 @HasPermissionAllDecorator('hg.admin')
52 def settings_get(self, defaults=None, errors=None):
51 def settings_get(self, defaults=None, errors=None):
53 """
52 """
54 View that displays the plugin settings as a form.
53 View that displays the plugin settings as a form.
55 """
54 """
56 c = self.load_default_context()
55 c = self.load_default_context()
57 defaults = defaults or {}
56 defaults = defaults or {}
58 errors = errors or {}
57 errors = errors or {}
59 schema = self.plugin.get_settings_schema()
58 schema = self.plugin.get_settings_schema()
60
59
61 # Compute default values for the form. Priority is:
60 # Compute default values for the form. Priority is:
62 # 1. Passed to this method 2. DB value 3. Schema default
61 # 1. Passed to this method 2. DB value 3. Schema default
63 for node in schema:
62 for node in schema:
64 if node.name not in defaults:
63 if node.name not in defaults:
65 defaults[node.name] = self.plugin.get_setting_by_name(
64 defaults[node.name] = self.plugin.get_setting_by_name(
66 node.name, node.default)
65 node.name, node.default)
67
66
68 template_context = {
67 template_context = {
69 'defaults': defaults,
68 'defaults': defaults,
70 'errors': errors,
69 'errors': errors,
71 'plugin': self.context.plugin,
70 'plugin': self.context.plugin,
72 'resource': self.context,
71 'resource': self.context,
73 }
72 }
74
73
75 return self._get_template_context(c, **template_context)
74 return self._get_template_context(c, **template_context)
76
75
77 @LoginRequired()
76 @LoginRequired()
78 @HasPermissionAllDecorator('hg.admin')
77 @HasPermissionAllDecorator('hg.admin')
79 @CSRFRequired()
78 @CSRFRequired()
80 def settings_post(self):
79 def settings_post(self):
81 """
80 """
82 View that validates and stores the plugin settings.
81 View that validates and stores the plugin settings.
83 """
82 """
84 _ = self.request.translate
83 _ = self.request.translate
85 self.load_default_context()
84 self.load_default_context()
86 schema = self.plugin.get_settings_schema()
85 schema = self.plugin.get_settings_schema()
87 data = self.request.params
86 data = self.request.params
88
87
89 try:
88 try:
90 valid_data = schema.deserialize(data)
89 valid_data = schema.deserialize(data)
91 except colander.Invalid as e:
90 except colander.Invalid as e:
92 # Display error message and display form again.
91 # Display error message and display form again.
93 h.flash(
92 h.flash(
94 _('Errors exist when saving plugin settings. '
93 _('Errors exist when saving plugin settings. '
95 'Please check the form inputs.'),
94 'Please check the form inputs.'),
96 category='error')
95 category='error')
97 defaults = {key: data[key] for key in data if key in schema}
96 defaults = {key: data[key] for key in data if key in schema}
98 return self.settings_get(errors=e.asdict(), defaults=defaults)
97 return self.settings_get(errors=e.asdict(), defaults=defaults)
99
98
100 # Store validated data.
99 # Store validated data.
101 for name, value in valid_data.items():
100 for name, value in valid_data.items():
102 self.plugin.create_or_update_setting(name, value)
101 self.plugin.create_or_update_setting(name, value)
103 Session().commit()
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 # Display success message and redirect.
104 # Display success message and redirect.
116 h.flash(_('Auth settings updated successfully.'), category='success')
105 h.flash(_('Auth settings updated successfully.'), category='success')
117 redirect_to = self.request.resource_path(
106 redirect_to = self.request.resource_path(
118 self.context, route_name='auth_home')
107 self.context, route_name='auth_home')
119 return HTTPFound(redirect_to)
108 return HTTPFound(redirect_to)
120
109
121
110
122 class AuthSettingsView(BaseAppView):
111 class AuthSettingsView(BaseAppView):
123 def load_default_context(self):
112 def load_default_context(self):
124 c = self._get_local_tmpl_context()
113 c = self._get_local_tmpl_context()
125 return c
114 return c
126
115
127 @LoginRequired()
116 @LoginRequired()
128 @HasPermissionAllDecorator('hg.admin')
117 @HasPermissionAllDecorator('hg.admin')
129 def index(self, defaults=None, errors=None, prefix_error=False):
118 def index(self, defaults=None, errors=None, prefix_error=False):
130 c = self.load_default_context()
119 c = self.load_default_context()
131
120
132 defaults = defaults or {}
121 defaults = defaults or {}
133 authn_registry = get_authn_registry(self.request.registry)
122 authn_registry = get_authn_registry(self.request.registry)
134 enabled_plugins = SettingsModel().get_auth_plugins()
123 enabled_plugins = SettingsModel().get_auth_plugins()
135
124
136 # Create template context and render it.
125 # Create template context and render it.
137 template_context = {
126 template_context = {
138 'resource': self.context,
127 'resource': self.context,
139 'available_plugins': authn_registry.get_plugins(),
128 'available_plugins': authn_registry.get_plugins(),
140 'enabled_plugins': enabled_plugins,
129 'enabled_plugins': enabled_plugins,
141 }
130 }
142 html = render('rhodecode:templates/admin/auth/auth_settings.mako',
131 html = render('rhodecode:templates/admin/auth/auth_settings.mako',
143 self._get_template_context(c, **template_context),
132 self._get_template_context(c, **template_context),
144 self.request)
133 self.request)
145
134
146 # Create form default values and fill the form.
135 # Create form default values and fill the form.
147 form_defaults = {
136 form_defaults = {
148 'auth_plugins': ',\n'.join(enabled_plugins)
137 'auth_plugins': ',\n'.join(enabled_plugins)
149 }
138 }
150 form_defaults.update(defaults)
139 form_defaults.update(defaults)
151 html = formencode.htmlfill.render(
140 html = formencode.htmlfill.render(
152 html,
141 html,
153 defaults=form_defaults,
142 defaults=form_defaults,
154 errors=errors,
143 errors=errors,
155 prefix_error=prefix_error,
144 prefix_error=prefix_error,
156 encoding="UTF-8",
145 encoding="UTF-8",
157 force_defaults=False)
146 force_defaults=False)
158
147
159 return Response(html)
148 return Response(html)
160
149
161 @LoginRequired()
150 @LoginRequired()
162 @HasPermissionAllDecorator('hg.admin')
151 @HasPermissionAllDecorator('hg.admin')
163 @CSRFRequired()
152 @CSRFRequired()
164 def auth_settings(self):
153 def auth_settings(self):
165 _ = self.request.translate
154 _ = self.request.translate
166 try:
155 try:
167 form = AuthSettingsForm(self.request.translate)()
156 form = AuthSettingsForm(self.request.translate)()
168 form_result = form.to_python(self.request.POST)
157 form_result = form.to_python(self.request.POST)
169 plugins = ','.join(form_result['auth_plugins'])
158 plugins = ','.join(form_result['auth_plugins'])
170 setting = SettingsModel().create_or_update_setting(
159 setting = SettingsModel().create_or_update_setting(
171 'auth_plugins', plugins)
160 'auth_plugins', plugins)
172 Session().add(setting)
161 Session().add(setting)
173 Session().commit()
162 Session().commit()
174
163
175 h.flash(_('Auth settings updated successfully.'), category='success')
164 h.flash(_('Auth settings updated successfully.'), category='success')
176 except formencode.Invalid as errors:
165 except formencode.Invalid as errors:
177 e = errors.error_dict or {}
166 e = errors.error_dict or {}
178 h.flash(_('Errors exist when saving plugin setting. '
167 h.flash(_('Errors exist when saving plugin setting. '
179 'Please check the form inputs.'), category='error')
168 'Please check the form inputs.'), category='error')
180 return self.index(
169 return self.index(
181 defaults=errors.value,
170 defaults=errors.value,
182 errors=e,
171 errors=e,
183 prefix_error=False)
172 prefix_error=False)
184 except Exception:
173 except Exception:
185 log.exception('Exception in auth_settings')
174 log.exception('Exception in auth_settings')
186 h.flash(_('Error occurred during update of auth settings.'),
175 h.flash(_('Error occurred during update of auth settings.'),
187 category='error')
176 category='error')
188
177
189 redirect_to = self.request.resource_path(
178 redirect_to = self.request.resource_path(
190 self.context, route_name='auth_home')
179 self.context, route_name='auth_home')
191 return HTTPFound(redirect_to)
180 return HTTPFound(redirect_to)
@@ -1,461 +1,476 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import logging
22 import logging
23 import traceback
23 import traceback
24 import collections
24 import collections
25 import tempfile
25
26
26 from paste.gzipper import make_gzip_middleware
27 from paste.gzipper import make_gzip_middleware
27 from pyramid.wsgi import wsgiapp
28 from pyramid.wsgi import wsgiapp
28 from pyramid.authorization import ACLAuthorizationPolicy
29 from pyramid.authorization import ACLAuthorizationPolicy
29 from pyramid.config import Configurator
30 from pyramid.config import Configurator
30 from pyramid.settings import asbool, aslist
31 from pyramid.settings import asbool, aslist
31 from pyramid.httpexceptions import (
32 from pyramid.httpexceptions import (
32 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
33 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
33 from pyramid.events import ApplicationCreated
34 from pyramid.events import ApplicationCreated
34 from pyramid.renderers import render_to_response
35 from pyramid.renderers import render_to_response
35
36
36 from rhodecode.model import meta
37 from rhodecode.model import meta
37 from rhodecode.config import patches
38 from rhodecode.config import patches
38 from rhodecode.config import utils as config_utils
39 from rhodecode.config import utils as config_utils
39 from rhodecode.config.environment import load_pyramid_environment
40 from rhodecode.config.environment import load_pyramid_environment
40
41
41 from rhodecode.lib.middleware.vcs import VCSMiddleware
42 from rhodecode.lib.middleware.vcs import VCSMiddleware
42 from rhodecode.lib.request import Request
43 from rhodecode.lib.request import Request
43 from rhodecode.lib.vcs import VCSCommunicationError
44 from rhodecode.lib.vcs import VCSCommunicationError
44 from rhodecode.lib.exceptions import VCSServerUnavailable
45 from rhodecode.lib.exceptions import VCSServerUnavailable
45 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
46 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
46 from rhodecode.lib.middleware.https_fixup import HttpsFixup
47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
47 from rhodecode.lib.celerylib.loader import configure_celery
48 from rhodecode.lib.celerylib.loader import configure_celery
48 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
49 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
49 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
50 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
50 from rhodecode.subscribers import (
51 from rhodecode.subscribers import (
51 scan_repositories_if_enabled, write_js_routes_if_enabled,
52 scan_repositories_if_enabled, write_js_routes_if_enabled,
52 write_metadata_if_needed, inject_app_settings)
53 write_metadata_if_needed, inject_app_settings)
53
54
54
55
55 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
56
57
57
58
58 def is_http_error(response):
59 def is_http_error(response):
59 # error which should have traceback
60 # error which should have traceback
60 return response.status_code > 499
61 return response.status_code > 499
61
62
62
63
63 def make_pyramid_app(global_config, **settings):
64 def make_pyramid_app(global_config, **settings):
64 """
65 """
65 Constructs the WSGI application based on Pyramid.
66 Constructs the WSGI application based on Pyramid.
66
67
67 Specials:
68 Specials:
68
69
69 * The application can also be integrated like a plugin via the call to
70 * The application can also be integrated like a plugin via the call to
70 `includeme`. This is accompanied with the other utility functions which
71 `includeme`. This is accompanied with the other utility functions which
71 are called. Changing this should be done with great care to not break
72 are called. Changing this should be done with great care to not break
72 cases when these fragments are assembled from another place.
73 cases when these fragments are assembled from another place.
73
74
74 """
75 """
75
76
76 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
77 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
77 # will be replaced by the value of the environment variable "NAME" in this case.
78 # will be replaced by the value of the environment variable "NAME" in this case.
78 environ = {
79 environ = {
79 'ENV_{}'.format(key): value for key, value in os.environ.items()}
80 'ENV_{}'.format(key): value for key, value in os.environ.items()}
80
81
81 global_config = _substitute_values(global_config, environ)
82 global_config = _substitute_values(global_config, environ)
82 settings = _substitute_values(settings, environ)
83 settings = _substitute_values(settings, environ)
83
84
84 sanitize_settings_and_apply_defaults(settings)
85 sanitize_settings_and_apply_defaults(settings)
85
86
86 config = Configurator(settings=settings)
87 config = Configurator(settings=settings)
87
88
88 # Apply compatibility patches
89 # Apply compatibility patches
89 patches.inspect_getargspec()
90 patches.inspect_getargspec()
90
91
91 load_pyramid_environment(global_config, settings)
92 load_pyramid_environment(global_config, settings)
92
93
93 # Static file view comes first
94 # Static file view comes first
94 includeme_first(config)
95 includeme_first(config)
95
96
96 includeme(config)
97 includeme(config)
97
98
98 pyramid_app = config.make_wsgi_app()
99 pyramid_app = config.make_wsgi_app()
99 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
100 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
100 pyramid_app.config = config
101 pyramid_app.config = config
101
102
102 config.configure_celery(global_config['__file__'])
103 config.configure_celery(global_config['__file__'])
103 # creating the app uses a connection - return it after we are done
104 # creating the app uses a connection - return it after we are done
104 meta.Session.remove()
105 meta.Session.remove()
105
106
106 log.info('Pyramid app %s created and configured.', pyramid_app)
107 log.info('Pyramid app %s created and configured.', pyramid_app)
107 return pyramid_app
108 return pyramid_app
108
109
109
110
110 def not_found_view(request):
111 def not_found_view(request):
111 """
112 """
112 This creates the view which should be registered as not-found-view to
113 This creates the view which should be registered as not-found-view to
113 pyramid.
114 pyramid.
114 """
115 """
115
116
116 if not getattr(request, 'vcs_call', None):
117 if not getattr(request, 'vcs_call', None):
117 # handle like regular case with our error_handler
118 # handle like regular case with our error_handler
118 return error_handler(HTTPNotFound(), request)
119 return error_handler(HTTPNotFound(), request)
119
120
120 # handle not found view as a vcs call
121 # handle not found view as a vcs call
121 settings = request.registry.settings
122 settings = request.registry.settings
122 ae_client = getattr(request, 'ae_client', None)
123 ae_client = getattr(request, 'ae_client', None)
123 vcs_app = VCSMiddleware(
124 vcs_app = VCSMiddleware(
124 HTTPNotFound(), request.registry, settings,
125 HTTPNotFound(), request.registry, settings,
125 appenlight_client=ae_client)
126 appenlight_client=ae_client)
126
127
127 return wsgiapp(vcs_app)(None, request)
128 return wsgiapp(vcs_app)(None, request)
128
129
129
130
130 def error_handler(exception, request):
131 def error_handler(exception, request):
131 import rhodecode
132 import rhodecode
132 from rhodecode.lib import helpers
133 from rhodecode.lib import helpers
133
134
134 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
135 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
135
136
136 base_response = HTTPInternalServerError()
137 base_response = HTTPInternalServerError()
137 # prefer original exception for the response since it may have headers set
138 # prefer original exception for the response since it may have headers set
138 if isinstance(exception, HTTPException):
139 if isinstance(exception, HTTPException):
139 base_response = exception
140 base_response = exception
140 elif isinstance(exception, VCSCommunicationError):
141 elif isinstance(exception, VCSCommunicationError):
141 base_response = VCSServerUnavailable()
142 base_response = VCSServerUnavailable()
142
143
143 if is_http_error(base_response):
144 if is_http_error(base_response):
144 log.exception(
145 log.exception(
145 'error occurred handling this request for path: %s', request.path)
146 'error occurred handling this request for path: %s', request.path)
146
147
147 error_explanation = base_response.explanation or str(base_response)
148 error_explanation = base_response.explanation or str(base_response)
148 if base_response.status_code == 404:
149 if base_response.status_code == 404:
149 error_explanation += " Or you don't have permission to access it."
150 error_explanation += " Or you don't have permission to access it."
150 c = AttributeDict()
151 c = AttributeDict()
151 c.error_message = base_response.status
152 c.error_message = base_response.status
152 c.error_explanation = error_explanation
153 c.error_explanation = error_explanation
153 c.visual = AttributeDict()
154 c.visual = AttributeDict()
154
155
155 c.visual.rhodecode_support_url = (
156 c.visual.rhodecode_support_url = (
156 request.registry.settings.get('rhodecode_support_url') or
157 request.registry.settings.get('rhodecode_support_url') or
157 request.route_url('rhodecode_support')
158 request.route_url('rhodecode_support')
158 )
159 )
159 c.redirect_time = 0
160 c.redirect_time = 0
160 c.rhodecode_name = rhodecode_title
161 c.rhodecode_name = rhodecode_title
161 if not c.rhodecode_name:
162 if not c.rhodecode_name:
162 c.rhodecode_name = 'Rhodecode'
163 c.rhodecode_name = 'Rhodecode'
163
164
164 c.causes = []
165 c.causes = []
165 if is_http_error(base_response):
166 if is_http_error(base_response):
166 c.causes.append('Server is overloaded.')
167 c.causes.append('Server is overloaded.')
167 c.causes.append('Server database connection is lost.')
168 c.causes.append('Server database connection is lost.')
168 c.causes.append('Server expected unhandled error.')
169 c.causes.append('Server expected unhandled error.')
169
170
170 if hasattr(base_response, 'causes'):
171 if hasattr(base_response, 'causes'):
171 c.causes = base_response.causes
172 c.causes = base_response.causes
172
173
173 c.messages = helpers.flash.pop_messages(request=request)
174 c.messages = helpers.flash.pop_messages(request=request)
174 c.traceback = traceback.format_exc()
175 c.traceback = traceback.format_exc()
175 response = render_to_response(
176 response = render_to_response(
176 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
177 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
177 response=base_response)
178 response=base_response)
178
179
179 return response
180 return response
180
181
181
182
182 def includeme_first(config):
183 def includeme_first(config):
183 # redirect automatic browser favicon.ico requests to correct place
184 # redirect automatic browser favicon.ico requests to correct place
184 def favicon_redirect(context, request):
185 def favicon_redirect(context, request):
185 return HTTPFound(
186 return HTTPFound(
186 request.static_path('rhodecode:public/images/favicon.ico'))
187 request.static_path('rhodecode:public/images/favicon.ico'))
187
188
188 config.add_view(favicon_redirect, route_name='favicon')
189 config.add_view(favicon_redirect, route_name='favicon')
189 config.add_route('favicon', '/favicon.ico')
190 config.add_route('favicon', '/favicon.ico')
190
191
191 def robots_redirect(context, request):
192 def robots_redirect(context, request):
192 return HTTPFound(
193 return HTTPFound(
193 request.static_path('rhodecode:public/robots.txt'))
194 request.static_path('rhodecode:public/robots.txt'))
194
195
195 config.add_view(robots_redirect, route_name='robots')
196 config.add_view(robots_redirect, route_name='robots')
196 config.add_route('robots', '/robots.txt')
197 config.add_route('robots', '/robots.txt')
197
198
198 config.add_static_view(
199 config.add_static_view(
199 '_static/deform', 'deform:static')
200 '_static/deform', 'deform:static')
200 config.add_static_view(
201 config.add_static_view(
201 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
202 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
202
203
203
204
204 def includeme(config):
205 def includeme(config):
205 settings = config.registry.settings
206 settings = config.registry.settings
206 config.set_request_factory(Request)
207 config.set_request_factory(Request)
207
208
208 # plugin information
209 # plugin information
209 config.registry.rhodecode_plugins = collections.OrderedDict()
210 config.registry.rhodecode_plugins = collections.OrderedDict()
210
211
211 config.add_directive(
212 config.add_directive(
212 'register_rhodecode_plugin', register_rhodecode_plugin)
213 'register_rhodecode_plugin', register_rhodecode_plugin)
213
214
214 config.add_directive('configure_celery', configure_celery)
215 config.add_directive('configure_celery', configure_celery)
215
216
216 if asbool(settings.get('appenlight', 'false')):
217 if asbool(settings.get('appenlight', 'false')):
217 config.include('appenlight_client.ext.pyramid_tween')
218 config.include('appenlight_client.ext.pyramid_tween')
218
219
219 # Includes which are required. The application would fail without them.
220 # Includes which are required. The application would fail without them.
220 config.include('pyramid_mako')
221 config.include('pyramid_mako')
221 config.include('pyramid_beaker')
222 config.include('pyramid_beaker')
222 config.include('rhodecode.lib.caches')
223 config.include('rhodecode.lib.caches')
224 config.include('rhodecode.lib.rc_cache')
223
225
224 config.include('rhodecode.authentication')
226 config.include('rhodecode.authentication')
225 config.include('rhodecode.integrations')
227 config.include('rhodecode.integrations')
226
228
227 # apps
229 # apps
228 config.include('rhodecode.apps._base')
230 config.include('rhodecode.apps._base')
229 config.include('rhodecode.apps.ops')
231 config.include('rhodecode.apps.ops')
230
232
231 config.include('rhodecode.apps.admin')
233 config.include('rhodecode.apps.admin')
232 config.include('rhodecode.apps.channelstream')
234 config.include('rhodecode.apps.channelstream')
233 config.include('rhodecode.apps.login')
235 config.include('rhodecode.apps.login')
234 config.include('rhodecode.apps.home')
236 config.include('rhodecode.apps.home')
235 config.include('rhodecode.apps.journal')
237 config.include('rhodecode.apps.journal')
236 config.include('rhodecode.apps.repository')
238 config.include('rhodecode.apps.repository')
237 config.include('rhodecode.apps.repo_group')
239 config.include('rhodecode.apps.repo_group')
238 config.include('rhodecode.apps.user_group')
240 config.include('rhodecode.apps.user_group')
239 config.include('rhodecode.apps.search')
241 config.include('rhodecode.apps.search')
240 config.include('rhodecode.apps.user_profile')
242 config.include('rhodecode.apps.user_profile')
241 config.include('rhodecode.apps.user_group_profile')
243 config.include('rhodecode.apps.user_group_profile')
242 config.include('rhodecode.apps.my_account')
244 config.include('rhodecode.apps.my_account')
243 config.include('rhodecode.apps.svn_support')
245 config.include('rhodecode.apps.svn_support')
244 config.include('rhodecode.apps.ssh_support')
246 config.include('rhodecode.apps.ssh_support')
245 config.include('rhodecode.apps.gist')
247 config.include('rhodecode.apps.gist')
246
248
247 config.include('rhodecode.apps.debug_style')
249 config.include('rhodecode.apps.debug_style')
248 config.include('rhodecode.tweens')
250 config.include('rhodecode.tweens')
249 config.include('rhodecode.api')
251 config.include('rhodecode.api')
250
252
251 config.add_route(
253 config.add_route(
252 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
254 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
253
255
254 config.add_translation_dirs('rhodecode:i18n/')
256 config.add_translation_dirs('rhodecode:i18n/')
255 settings['default_locale_name'] = settings.get('lang', 'en')
257 settings['default_locale_name'] = settings.get('lang', 'en')
256
258
257 # Add subscribers.
259 # Add subscribers.
258 config.add_subscriber(inject_app_settings, ApplicationCreated)
260 config.add_subscriber(inject_app_settings, ApplicationCreated)
259 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
261 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
260 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
262 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
261 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
263 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
262
264
263 # events
265 # events
264 # TODO(marcink): this should be done when pyramid migration is finished
266 # TODO(marcink): this should be done when pyramid migration is finished
265 # config.add_subscriber(
267 # config.add_subscriber(
266 # 'rhodecode.integrations.integrations_event_handler',
268 # 'rhodecode.integrations.integrations_event_handler',
267 # 'rhodecode.events.RhodecodeEvent')
269 # 'rhodecode.events.RhodecodeEvent')
268
270
269 # request custom methods
271 # request custom methods
270 config.add_request_method(
272 config.add_request_method(
271 'rhodecode.lib.partial_renderer.get_partial_renderer',
273 'rhodecode.lib.partial_renderer.get_partial_renderer',
272 'get_partial_renderer')
274 'get_partial_renderer')
273
275
274 # Set the authorization policy.
276 # Set the authorization policy.
275 authz_policy = ACLAuthorizationPolicy()
277 authz_policy = ACLAuthorizationPolicy()
276 config.set_authorization_policy(authz_policy)
278 config.set_authorization_policy(authz_policy)
277
279
278 # Set the default renderer for HTML templates to mako.
280 # Set the default renderer for HTML templates to mako.
279 config.add_mako_renderer('.html')
281 config.add_mako_renderer('.html')
280
282
281 config.add_renderer(
283 config.add_renderer(
282 name='json_ext',
284 name='json_ext',
283 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
285 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
284
286
285 # include RhodeCode plugins
287 # include RhodeCode plugins
286 includes = aslist(settings.get('rhodecode.includes', []))
288 includes = aslist(settings.get('rhodecode.includes', []))
287 for inc in includes:
289 for inc in includes:
288 config.include(inc)
290 config.include(inc)
289
291
290 # custom not found view, if our pyramid app doesn't know how to handle
292 # custom not found view, if our pyramid app doesn't know how to handle
291 # the request pass it to potential VCS handling ap
293 # the request pass it to potential VCS handling ap
292 config.add_notfound_view(not_found_view)
294 config.add_notfound_view(not_found_view)
293 if not settings.get('debugtoolbar.enabled', False):
295 if not settings.get('debugtoolbar.enabled', False):
294 # disabled debugtoolbar handle all exceptions via the error_handlers
296 # disabled debugtoolbar handle all exceptions via the error_handlers
295 config.add_view(error_handler, context=Exception)
297 config.add_view(error_handler, context=Exception)
296
298
297 # all errors including 403/404/50X
299 # all errors including 403/404/50X
298 config.add_view(error_handler, context=HTTPError)
300 config.add_view(error_handler, context=HTTPError)
299
301
300
302
301 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
303 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
302 """
304 """
303 Apply outer WSGI middlewares around the application.
305 Apply outer WSGI middlewares around the application.
304 """
306 """
305 settings = config.registry.settings
307 settings = config.registry.settings
306
308
307 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
309 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
308 pyramid_app = HttpsFixup(pyramid_app, settings)
310 pyramid_app = HttpsFixup(pyramid_app, settings)
309
311
310 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
312 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
311 pyramid_app, settings)
313 pyramid_app, settings)
312 config.registry.ae_client = _ae_client
314 config.registry.ae_client = _ae_client
313
315
314 if settings['gzip_responses']:
316 if settings['gzip_responses']:
315 pyramid_app = make_gzip_middleware(
317 pyramid_app = make_gzip_middleware(
316 pyramid_app, settings, compress_level=1)
318 pyramid_app, settings, compress_level=1)
317
319
318 # this should be the outer most middleware in the wsgi stack since
320 # this should be the outer most middleware in the wsgi stack since
319 # middleware like Routes make database calls
321 # middleware like Routes make database calls
320 def pyramid_app_with_cleanup(environ, start_response):
322 def pyramid_app_with_cleanup(environ, start_response):
321 try:
323 try:
322 return pyramid_app(environ, start_response)
324 return pyramid_app(environ, start_response)
323 finally:
325 finally:
324 # Dispose current database session and rollback uncommitted
326 # Dispose current database session and rollback uncommitted
325 # transactions.
327 # transactions.
326 meta.Session.remove()
328 meta.Session.remove()
327
329
328 # In a single threaded mode server, on non sqlite db we should have
330 # In a single threaded mode server, on non sqlite db we should have
329 # '0 Current Checked out connections' at the end of a request,
331 # '0 Current Checked out connections' at the end of a request,
330 # if not, then something, somewhere is leaving a connection open
332 # if not, then something, somewhere is leaving a connection open
331 pool = meta.Base.metadata.bind.engine.pool
333 pool = meta.Base.metadata.bind.engine.pool
332 log.debug('sa pool status: %s', pool.status())
334 log.debug('sa pool status: %s', pool.status())
333
335
334 return pyramid_app_with_cleanup
336 return pyramid_app_with_cleanup
335
337
336
338
337 def sanitize_settings_and_apply_defaults(settings):
339 def sanitize_settings_and_apply_defaults(settings):
338 """
340 """
339 Applies settings defaults and does all type conversion.
341 Applies settings defaults and does all type conversion.
340
342
341 We would move all settings parsing and preparation into this place, so that
343 We would move all settings parsing and preparation into this place, so that
342 we have only one place left which deals with this part. The remaining parts
344 we have only one place left which deals with this part. The remaining parts
343 of the application would start to rely fully on well prepared settings.
345 of the application would start to rely fully on well prepared settings.
344
346
345 This piece would later be split up per topic to avoid a big fat monster
347 This piece would later be split up per topic to avoid a big fat monster
346 function.
348 function.
347 """
349 """
348
350
349 settings.setdefault('rhodecode.edition', 'Community Edition')
351 settings.setdefault('rhodecode.edition', 'Community Edition')
350
352
351 if 'mako.default_filters' not in settings:
353 if 'mako.default_filters' not in settings:
352 # set custom default filters if we don't have it defined
354 # set custom default filters if we don't have it defined
353 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
355 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
354 settings['mako.default_filters'] = 'h_filter'
356 settings['mako.default_filters'] = 'h_filter'
355
357
356 if 'mako.directories' not in settings:
358 if 'mako.directories' not in settings:
357 mako_directories = settings.setdefault('mako.directories', [
359 mako_directories = settings.setdefault('mako.directories', [
358 # Base templates of the original application
360 # Base templates of the original application
359 'rhodecode:templates',
361 'rhodecode:templates',
360 ])
362 ])
361 log.debug(
363 log.debug(
362 "Using the following Mako template directories: %s",
364 "Using the following Mako template directories: %s",
363 mako_directories)
365 mako_directories)
364
366
365 # Default includes, possible to change as a user
367 # Default includes, possible to change as a user
366 pyramid_includes = settings.setdefault('pyramid.includes', [
368 pyramid_includes = settings.setdefault('pyramid.includes', [
367 'rhodecode.lib.middleware.request_wrapper',
369 'rhodecode.lib.middleware.request_wrapper',
368 ])
370 ])
369 log.debug(
371 log.debug(
370 "Using the following pyramid.includes: %s",
372 "Using the following pyramid.includes: %s",
371 pyramid_includes)
373 pyramid_includes)
372
374
373 # TODO: johbo: Re-think this, usually the call to config.include
375 # TODO: johbo: Re-think this, usually the call to config.include
374 # should allow to pass in a prefix.
376 # should allow to pass in a prefix.
375 settings.setdefault('rhodecode.api.url', '/_admin/api')
377 settings.setdefault('rhodecode.api.url', '/_admin/api')
376
378
377 # Sanitize generic settings.
379 # Sanitize generic settings.
378 _list_setting(settings, 'default_encoding', 'UTF-8')
380 _list_setting(settings, 'default_encoding', 'UTF-8')
379 _bool_setting(settings, 'is_test', 'false')
381 _bool_setting(settings, 'is_test', 'false')
380 _bool_setting(settings, 'gzip_responses', 'false')
382 _bool_setting(settings, 'gzip_responses', 'false')
381
383
382 # Call split out functions that sanitize settings for each topic.
384 # Call split out functions that sanitize settings for each topic.
383 _sanitize_appenlight_settings(settings)
385 _sanitize_appenlight_settings(settings)
384 _sanitize_vcs_settings(settings)
386 _sanitize_vcs_settings(settings)
387 _sanitize_cache_settings(settings)
385
388
386 # configure instance id
389 # configure instance id
387 config_utils.set_instance_id(settings)
390 config_utils.set_instance_id(settings)
388
391
389 return settings
392 return settings
390
393
391
394
392 def _sanitize_appenlight_settings(settings):
395 def _sanitize_appenlight_settings(settings):
393 _bool_setting(settings, 'appenlight', 'false')
396 _bool_setting(settings, 'appenlight', 'false')
394
397
395
398
396 def _sanitize_vcs_settings(settings):
399 def _sanitize_vcs_settings(settings):
397 """
400 """
398 Applies settings defaults and does type conversion for all VCS related
401 Applies settings defaults and does type conversion for all VCS related
399 settings.
402 settings.
400 """
403 """
401 _string_setting(settings, 'vcs.svn.compatible_version', '')
404 _string_setting(settings, 'vcs.svn.compatible_version', '')
402 _string_setting(settings, 'git_rev_filter', '--all')
405 _string_setting(settings, 'git_rev_filter', '--all')
403 _string_setting(settings, 'vcs.hooks.protocol', 'http')
406 _string_setting(settings, 'vcs.hooks.protocol', 'http')
404 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
407 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
405 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
408 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
406 _string_setting(settings, 'vcs.server', '')
409 _string_setting(settings, 'vcs.server', '')
407 _string_setting(settings, 'vcs.server.log_level', 'debug')
410 _string_setting(settings, 'vcs.server.log_level', 'debug')
408 _string_setting(settings, 'vcs.server.protocol', 'http')
411 _string_setting(settings, 'vcs.server.protocol', 'http')
409 _bool_setting(settings, 'startup.import_repos', 'false')
412 _bool_setting(settings, 'startup.import_repos', 'false')
410 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
413 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
411 _bool_setting(settings, 'vcs.server.enable', 'true')
414 _bool_setting(settings, 'vcs.server.enable', 'true')
412 _bool_setting(settings, 'vcs.start_server', 'false')
415 _bool_setting(settings, 'vcs.start_server', 'false')
413 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
416 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
414 _int_setting(settings, 'vcs.connection_timeout', 3600)
417 _int_setting(settings, 'vcs.connection_timeout', 3600)
415
418
416 # Support legacy values of vcs.scm_app_implementation. Legacy
419 # Support legacy values of vcs.scm_app_implementation. Legacy
417 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
420 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
418 # which is now mapped to 'http'.
421 # which is now mapped to 'http'.
419 scm_app_impl = settings['vcs.scm_app_implementation']
422 scm_app_impl = settings['vcs.scm_app_implementation']
420 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
423 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
421 settings['vcs.scm_app_implementation'] = 'http'
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 def _int_setting(settings, name, default):
439 def _int_setting(settings, name, default):
425 settings[name] = int(settings.get(name, default))
440 settings[name] = int(settings.get(name, default))
426
441
427
442
428 def _bool_setting(settings, name, default):
443 def _bool_setting(settings, name, default):
429 input_val = settings.get(name, default)
444 input_val = settings.get(name, default)
430 if isinstance(input_val, unicode):
445 if isinstance(input_val, unicode):
431 input_val = input_val.encode('utf8')
446 input_val = input_val.encode('utf8')
432 settings[name] = asbool(input_val)
447 settings[name] = asbool(input_val)
433
448
434
449
435 def _list_setting(settings, name, default):
450 def _list_setting(settings, name, default):
436 raw_value = settings.get(name, default)
451 raw_value = settings.get(name, default)
437
452
438 old_separator = ','
453 old_separator = ','
439 if old_separator in raw_value:
454 if old_separator in raw_value:
440 # If we get a comma separated list, pass it to our own function.
455 # If we get a comma separated list, pass it to our own function.
441 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
456 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
442 else:
457 else:
443 # Otherwise we assume it uses pyramids space/newline separation.
458 # Otherwise we assume it uses pyramids space/newline separation.
444 settings[name] = aslist(raw_value)
459 settings[name] = aslist(raw_value)
445
460
446
461
447 def _string_setting(settings, name, default, lower=True):
462 def _string_setting(settings, name, default, lower=True):
448 value = settings.get(name, default)
463 value = settings.get(name, default)
449 if lower:
464 if lower:
450 value = value.lower()
465 value = value.lower()
451 settings[name] = value
466 settings[name] = value
452
467
453
468
454 def _substitute_values(mapping, substitutions):
469 def _substitute_values(mapping, substitutions):
455 result = {
470 result = {
456 # Note: Cannot use regular replacements, since they would clash
471 # Note: Cannot use regular replacements, since they would clash
457 # with the implementation of ConfigParser. Using "format" instead.
472 # with the implementation of ConfigParser. Using "format" instead.
458 key: value.format(**substitutions)
473 key: value.format(**substitutions)
459 for key, value in mapping.items()
474 for key, value in mapping.items()
460 }
475 }
461 return result
476 return result
@@ -1,2202 +1,2202 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import inspect
27 import inspect
28 import collections
28 import collections
29 import fnmatch
29 import fnmatch
30 import hashlib
30 import hashlib
31 import itertools
31 import itertools
32 import logging
32 import logging
33 import random
33 import random
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38
38
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 from sqlalchemy.orm.exc import ObjectDeletedError
40 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm import joinedload
41 from sqlalchemy.orm import joinedload
42 from zope.cachedescriptors.property import Lazy as LazyProperty
42 from zope.cachedescriptors.property import Lazy as LazyProperty
43
43
44 import rhodecode
44 import rhodecode
45 from rhodecode.model import meta
45 from rhodecode.model import meta
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 from rhodecode.lib import caches
51 from rhodecode.lib import rc_cache
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 from rhodecode.lib.caching_query import FromCache
55 from rhodecode.lib.caching_query import FromCache
56
56
57
57
58 if rhodecode.is_unix:
58 if rhodecode.is_unix:
59 import bcrypt
59 import bcrypt
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63 csrf_token_key = "csrf_token"
63 csrf_token_key = "csrf_token"
64
64
65
65
66 class PasswordGenerator(object):
66 class PasswordGenerator(object):
67 """
67 """
68 This is a simple class for generating password from different sets of
68 This is a simple class for generating password from different sets of
69 characters
69 characters
70 usage::
70 usage::
71
71
72 passwd_gen = PasswordGenerator()
72 passwd_gen = PasswordGenerator()
73 #print 8-letter password containing only big and small letters
73 #print 8-letter password containing only big and small letters
74 of alphabet
74 of alphabet
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
76 """
76 """
77 ALPHABETS_NUM = r'''1234567890'''
77 ALPHABETS_NUM = r'''1234567890'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
87
87
88 def __init__(self, passwd=''):
88 def __init__(self, passwd=''):
89 self.passwd = passwd
89 self.passwd = passwd
90
90
91 def gen_password(self, length, type_=None):
91 def gen_password(self, length, type_=None):
92 if type_ is None:
92 if type_ is None:
93 type_ = self.ALPHABETS_FULL
93 type_ = self.ALPHABETS_FULL
94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
95 return self.passwd
95 return self.passwd
96
96
97
97
98 class _RhodeCodeCryptoBase(object):
98 class _RhodeCodeCryptoBase(object):
99 ENC_PREF = None
99 ENC_PREF = None
100
100
101 def hash_create(self, str_):
101 def hash_create(self, str_):
102 """
102 """
103 hash the string using
103 hash the string using
104
104
105 :param str_: password to hash
105 :param str_: password to hash
106 """
106 """
107 raise NotImplementedError
107 raise NotImplementedError
108
108
109 def hash_check_with_upgrade(self, password, hashed):
109 def hash_check_with_upgrade(self, password, hashed):
110 """
110 """
111 Returns tuple in which first element is boolean that states that
111 Returns tuple in which first element is boolean that states that
112 given password matches it's hashed version, and the second is new hash
112 given password matches it's hashed version, and the second is new hash
113 of the password, in case this password should be migrated to new
113 of the password, in case this password should be migrated to new
114 cipher.
114 cipher.
115 """
115 """
116 checked_hash = self.hash_check(password, hashed)
116 checked_hash = self.hash_check(password, hashed)
117 return checked_hash, None
117 return checked_hash, None
118
118
119 def hash_check(self, password, hashed):
119 def hash_check(self, password, hashed):
120 """
120 """
121 Checks matching password with it's hashed value.
121 Checks matching password with it's hashed value.
122
122
123 :param password: password
123 :param password: password
124 :param hashed: password in hashed form
124 :param hashed: password in hashed form
125 """
125 """
126 raise NotImplementedError
126 raise NotImplementedError
127
127
128 def _assert_bytes(self, value):
128 def _assert_bytes(self, value):
129 """
129 """
130 Passing in an `unicode` object can lead to hard to detect issues
130 Passing in an `unicode` object can lead to hard to detect issues
131 if passwords contain non-ascii characters. Doing a type check
131 if passwords contain non-ascii characters. Doing a type check
132 during runtime, so that such mistakes are detected early on.
132 during runtime, so that such mistakes are detected early on.
133 """
133 """
134 if not isinstance(value, str):
134 if not isinstance(value, str):
135 raise TypeError(
135 raise TypeError(
136 "Bytestring required as input, got %r." % (value, ))
136 "Bytestring required as input, got %r." % (value, ))
137
137
138
138
139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 ENC_PREF = ('$2a$10', '$2b$10')
140 ENC_PREF = ('$2a$10', '$2b$10')
141
141
142 def hash_create(self, str_):
142 def hash_create(self, str_):
143 self._assert_bytes(str_)
143 self._assert_bytes(str_)
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
145
145
146 def hash_check_with_upgrade(self, password, hashed):
146 def hash_check_with_upgrade(self, password, hashed):
147 """
147 """
148 Returns tuple in which first element is boolean that states that
148 Returns tuple in which first element is boolean that states that
149 given password matches it's hashed version, and the second is new hash
149 given password matches it's hashed version, and the second is new hash
150 of the password, in case this password should be migrated to new
150 of the password, in case this password should be migrated to new
151 cipher.
151 cipher.
152
152
153 This implements special upgrade logic which works like that:
153 This implements special upgrade logic which works like that:
154 - check if the given password == bcrypted hash, if yes then we
154 - check if the given password == bcrypted hash, if yes then we
155 properly used password and it was already in bcrypt. Proceed
155 properly used password and it was already in bcrypt. Proceed
156 without any changes
156 without any changes
157 - if bcrypt hash check is not working try with sha256. If hash compare
157 - if bcrypt hash check is not working try with sha256. If hash compare
158 is ok, it means we using correct but old hashed password. indicate
158 is ok, it means we using correct but old hashed password. indicate
159 hash change and proceed
159 hash change and proceed
160 """
160 """
161
161
162 new_hash = None
162 new_hash = None
163
163
164 # regular pw check
164 # regular pw check
165 password_match_bcrypt = self.hash_check(password, hashed)
165 password_match_bcrypt = self.hash_check(password, hashed)
166
166
167 # now we want to know if the password was maybe from sha256
167 # now we want to know if the password was maybe from sha256
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
169 if not password_match_bcrypt:
169 if not password_match_bcrypt:
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
171 new_hash = self.hash_create(password) # make new bcrypt hash
171 new_hash = self.hash_create(password) # make new bcrypt hash
172 password_match_bcrypt = True
172 password_match_bcrypt = True
173
173
174 return password_match_bcrypt, new_hash
174 return password_match_bcrypt, new_hash
175
175
176 def hash_check(self, password, hashed):
176 def hash_check(self, password, hashed):
177 """
177 """
178 Checks matching password with it's hashed value.
178 Checks matching password with it's hashed value.
179
179
180 :param password: password
180 :param password: password
181 :param hashed: password in hashed form
181 :param hashed: password in hashed form
182 """
182 """
183 self._assert_bytes(password)
183 self._assert_bytes(password)
184 try:
184 try:
185 return bcrypt.hashpw(password, hashed) == hashed
185 return bcrypt.hashpw(password, hashed) == hashed
186 except ValueError as e:
186 except ValueError as e:
187 # we're having a invalid salt here probably, we should not crash
187 # we're having a invalid salt here probably, we should not crash
188 # just return with False as it would be a wrong password.
188 # just return with False as it would be a wrong password.
189 log.debug('Failed to check password hash using bcrypt %s',
189 log.debug('Failed to check password hash using bcrypt %s',
190 safe_str(e))
190 safe_str(e))
191
191
192 return False
192 return False
193
193
194
194
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196 ENC_PREF = '_'
196 ENC_PREF = '_'
197
197
198 def hash_create(self, str_):
198 def hash_create(self, str_):
199 self._assert_bytes(str_)
199 self._assert_bytes(str_)
200 return hashlib.sha256(str_).hexdigest()
200 return hashlib.sha256(str_).hexdigest()
201
201
202 def hash_check(self, password, hashed):
202 def hash_check(self, password, hashed):
203 """
203 """
204 Checks matching password with it's hashed value.
204 Checks matching password with it's hashed value.
205
205
206 :param password: password
206 :param password: password
207 :param hashed: password in hashed form
207 :param hashed: password in hashed form
208 """
208 """
209 self._assert_bytes(password)
209 self._assert_bytes(password)
210 return hashlib.sha256(password).hexdigest() == hashed
210 return hashlib.sha256(password).hexdigest() == hashed
211
211
212
212
213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
214 ENC_PREF = '_'
214 ENC_PREF = '_'
215
215
216 def hash_create(self, str_):
216 def hash_create(self, str_):
217 self._assert_bytes(str_)
217 self._assert_bytes(str_)
218 return sha1(str_)
218 return sha1(str_)
219
219
220 def hash_check(self, password, hashed):
220 def hash_check(self, password, hashed):
221 """
221 """
222 Checks matching password with it's hashed value.
222 Checks matching password with it's hashed value.
223
223
224 :param password: password
224 :param password: password
225 :param hashed: password in hashed form
225 :param hashed: password in hashed form
226 """
226 """
227 self._assert_bytes(password)
227 self._assert_bytes(password)
228 return sha1(password) == hashed
228 return sha1(password) == hashed
229
229
230
230
231 def crypto_backend():
231 def crypto_backend():
232 """
232 """
233 Return the matching crypto backend.
233 Return the matching crypto backend.
234
234
235 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 Selection is based on if we run tests or not, we pick sha1-test backend to run
236 tests faster since BCRYPT is expensive to calculate
236 tests faster since BCRYPT is expensive to calculate
237 """
237 """
238 if rhodecode.is_test:
238 if rhodecode.is_test:
239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
240 else:
240 else:
241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
242
242
243 return RhodeCodeCrypto
243 return RhodeCodeCrypto
244
244
245
245
246 def get_crypt_password(password):
246 def get_crypt_password(password):
247 """
247 """
248 Create the hash of `password` with the active crypto backend.
248 Create the hash of `password` with the active crypto backend.
249
249
250 :param password: The cleartext password.
250 :param password: The cleartext password.
251 :type password: unicode
251 :type password: unicode
252 """
252 """
253 password = safe_str(password)
253 password = safe_str(password)
254 return crypto_backend().hash_create(password)
254 return crypto_backend().hash_create(password)
255
255
256
256
257 def check_password(password, hashed):
257 def check_password(password, hashed):
258 """
258 """
259 Check if the value in `password` matches the hash in `hashed`.
259 Check if the value in `password` matches the hash in `hashed`.
260
260
261 :param password: The cleartext password.
261 :param password: The cleartext password.
262 :type password: unicode
262 :type password: unicode
263
263
264 :param hashed: The expected hashed version of the password.
264 :param hashed: The expected hashed version of the password.
265 :type hashed: The hash has to be passed in in text representation.
265 :type hashed: The hash has to be passed in in text representation.
266 """
266 """
267 password = safe_str(password)
267 password = safe_str(password)
268 return crypto_backend().hash_check(password, hashed)
268 return crypto_backend().hash_check(password, hashed)
269
269
270
270
271 def generate_auth_token(data, salt=None):
271 def generate_auth_token(data, salt=None):
272 """
272 """
273 Generates API KEY from given string
273 Generates API KEY from given string
274 """
274 """
275
275
276 if salt is None:
276 if salt is None:
277 salt = os.urandom(16)
277 salt = os.urandom(16)
278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
279
279
280
280
281 def get_came_from(request):
281 def get_came_from(request):
282 """
282 """
283 get query_string+path from request sanitized after removing auth_token
283 get query_string+path from request sanitized after removing auth_token
284 """
284 """
285 _req = request
285 _req = request
286
286
287 path = _req.path
287 path = _req.path
288 if 'auth_token' in _req.GET:
288 if 'auth_token' in _req.GET:
289 # sanitize the request and remove auth_token for redirection
289 # sanitize the request and remove auth_token for redirection
290 _req.GET.pop('auth_token')
290 _req.GET.pop('auth_token')
291 qs = _req.query_string
291 qs = _req.query_string
292 if qs:
292 if qs:
293 path += '?' + qs
293 path += '?' + qs
294
294
295 return path
295 return path
296
296
297
297
298 class CookieStoreWrapper(object):
298 class CookieStoreWrapper(object):
299
299
300 def __init__(self, cookie_store):
300 def __init__(self, cookie_store):
301 self.cookie_store = cookie_store
301 self.cookie_store = cookie_store
302
302
303 def __repr__(self):
303 def __repr__(self):
304 return 'CookieStore<%s>' % (self.cookie_store)
304 return 'CookieStore<%s>' % (self.cookie_store)
305
305
306 def get(self, key, other=None):
306 def get(self, key, other=None):
307 if isinstance(self.cookie_store, dict):
307 if isinstance(self.cookie_store, dict):
308 return self.cookie_store.get(key, other)
308 return self.cookie_store.get(key, other)
309 elif isinstance(self.cookie_store, AuthUser):
309 elif isinstance(self.cookie_store, AuthUser):
310 return self.cookie_store.__dict__.get(key, other)
310 return self.cookie_store.__dict__.get(key, other)
311
311
312
312
313 def _cached_perms_data(user_id, scope, user_is_admin,
313 def _cached_perms_data(user_id, scope, user_is_admin,
314 user_inherit_default_permissions, explicit, algo,
314 user_inherit_default_permissions, explicit, algo,
315 calculate_super_admin):
315 calculate_super_admin):
316
316
317 permissions = PermissionCalculator(
317 permissions = PermissionCalculator(
318 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 user_id, scope, user_is_admin, user_inherit_default_permissions,
319 explicit, algo, calculate_super_admin)
319 explicit, algo, calculate_super_admin)
320 return permissions.calculate()
320 return permissions.calculate()
321
321
322
322
323 class PermOrigin(object):
323 class PermOrigin(object):
324 SUPER_ADMIN = 'superadmin'
324 SUPER_ADMIN = 'superadmin'
325
325
326 REPO_USER = 'user:%s'
326 REPO_USER = 'user:%s'
327 REPO_USERGROUP = 'usergroup:%s'
327 REPO_USERGROUP = 'usergroup:%s'
328 REPO_OWNER = 'repo.owner'
328 REPO_OWNER = 'repo.owner'
329 REPO_DEFAULT = 'repo.default'
329 REPO_DEFAULT = 'repo.default'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 REPO_PRIVATE = 'repo.private'
331 REPO_PRIVATE = 'repo.private'
332
332
333 REPOGROUP_USER = 'user:%s'
333 REPOGROUP_USER = 'user:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 REPOGROUP_OWNER = 'group.owner'
335 REPOGROUP_OWNER = 'group.owner'
336 REPOGROUP_DEFAULT = 'group.default'
336 REPOGROUP_DEFAULT = 'group.default'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338
338
339 USERGROUP_USER = 'user:%s'
339 USERGROUP_USER = 'user:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
341 USERGROUP_OWNER = 'usergroup.owner'
341 USERGROUP_OWNER = 'usergroup.owner'
342 USERGROUP_DEFAULT = 'usergroup.default'
342 USERGROUP_DEFAULT = 'usergroup.default'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344
344
345
345
346 class PermOriginDict(dict):
346 class PermOriginDict(dict):
347 """
347 """
348 A special dict used for tracking permissions along with their origins.
348 A special dict used for tracking permissions along with their origins.
349
349
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 `__getitem__` will return only the perm
351 `__getitem__` will return only the perm
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353
353
354 >>> perms = PermOriginDict()
354 >>> perms = PermOriginDict()
355 >>> perms['resource'] = 'read', 'default'
355 >>> perms['resource'] = 'read', 'default'
356 >>> perms['resource']
356 >>> perms['resource']
357 'read'
357 'read'
358 >>> perms['resource'] = 'write', 'admin'
358 >>> perms['resource'] = 'write', 'admin'
359 >>> perms['resource']
359 >>> perms['resource']
360 'write'
360 'write'
361 >>> perms.perm_origin_stack
361 >>> perms.perm_origin_stack
362 {'resource': [('read', 'default'), ('write', 'admin')]}
362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 """
363 """
364
364
365 def __init__(self, *args, **kw):
365 def __init__(self, *args, **kw):
366 dict.__init__(self, *args, **kw)
366 dict.__init__(self, *args, **kw)
367 self.perm_origin_stack = collections.OrderedDict()
367 self.perm_origin_stack = collections.OrderedDict()
368
368
369 def __setitem__(self, key, (perm, origin)):
369 def __setitem__(self, key, (perm, origin)):
370 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
370 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
371 dict.__setitem__(self, key, perm)
371 dict.__setitem__(self, key, perm)
372
372
373
373
374 class PermissionCalculator(object):
374 class PermissionCalculator(object):
375
375
376 def __init__(
376 def __init__(
377 self, user_id, scope, user_is_admin,
377 self, user_id, scope, user_is_admin,
378 user_inherit_default_permissions, explicit, algo,
378 user_inherit_default_permissions, explicit, algo,
379 calculate_super_admin=False):
379 calculate_super_admin=False):
380
380
381 self.user_id = user_id
381 self.user_id = user_id
382 self.user_is_admin = user_is_admin
382 self.user_is_admin = user_is_admin
383 self.inherit_default_permissions = user_inherit_default_permissions
383 self.inherit_default_permissions = user_inherit_default_permissions
384 self.explicit = explicit
384 self.explicit = explicit
385 self.algo = algo
385 self.algo = algo
386 self.calculate_super_admin = calculate_super_admin
386 self.calculate_super_admin = calculate_super_admin
387
387
388 scope = scope or {}
388 scope = scope or {}
389 self.scope_repo_id = scope.get('repo_id')
389 self.scope_repo_id = scope.get('repo_id')
390 self.scope_repo_group_id = scope.get('repo_group_id')
390 self.scope_repo_group_id = scope.get('repo_group_id')
391 self.scope_user_group_id = scope.get('user_group_id')
391 self.scope_user_group_id = scope.get('user_group_id')
392
392
393 self.default_user_id = User.get_default_user(cache=True).user_id
393 self.default_user_id = User.get_default_user(cache=True).user_id
394
394
395 self.permissions_repositories = PermOriginDict()
395 self.permissions_repositories = PermOriginDict()
396 self.permissions_repository_groups = PermOriginDict()
396 self.permissions_repository_groups = PermOriginDict()
397 self.permissions_user_groups = PermOriginDict()
397 self.permissions_user_groups = PermOriginDict()
398 self.permissions_global = set()
398 self.permissions_global = set()
399
399
400 self.default_repo_perms = Permission.get_default_repo_perms(
400 self.default_repo_perms = Permission.get_default_repo_perms(
401 self.default_user_id, self.scope_repo_id)
401 self.default_user_id, self.scope_repo_id)
402 self.default_repo_groups_perms = Permission.get_default_group_perms(
402 self.default_repo_groups_perms = Permission.get_default_group_perms(
403 self.default_user_id, self.scope_repo_group_id)
403 self.default_user_id, self.scope_repo_group_id)
404 self.default_user_group_perms = \
404 self.default_user_group_perms = \
405 Permission.get_default_user_group_perms(
405 Permission.get_default_user_group_perms(
406 self.default_user_id, self.scope_user_group_id)
406 self.default_user_id, self.scope_user_group_id)
407
407
408 def calculate(self):
408 def calculate(self):
409 if self.user_is_admin and not self.calculate_super_admin:
409 if self.user_is_admin and not self.calculate_super_admin:
410 return self._admin_permissions()
410 return self._admin_permissions()
411
411
412 self._calculate_global_default_permissions()
412 self._calculate_global_default_permissions()
413 self._calculate_global_permissions()
413 self._calculate_global_permissions()
414 self._calculate_default_permissions()
414 self._calculate_default_permissions()
415 self._calculate_repository_permissions()
415 self._calculate_repository_permissions()
416 self._calculate_repository_group_permissions()
416 self._calculate_repository_group_permissions()
417 self._calculate_user_group_permissions()
417 self._calculate_user_group_permissions()
418 return self._permission_structure()
418 return self._permission_structure()
419
419
420 def _admin_permissions(self):
420 def _admin_permissions(self):
421 """
421 """
422 admin user have all default rights for repositories
422 admin user have all default rights for repositories
423 and groups set to admin
423 and groups set to admin
424 """
424 """
425 self.permissions_global.add('hg.admin')
425 self.permissions_global.add('hg.admin')
426 self.permissions_global.add('hg.create.write_on_repogroup.true')
426 self.permissions_global.add('hg.create.write_on_repogroup.true')
427
427
428 # repositories
428 # repositories
429 for perm in self.default_repo_perms:
429 for perm in self.default_repo_perms:
430 r_k = perm.UserRepoToPerm.repository.repo_name
430 r_k = perm.UserRepoToPerm.repository.repo_name
431 p = 'repository.admin'
431 p = 'repository.admin'
432 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
432 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
433
433
434 # repository groups
434 # repository groups
435 for perm in self.default_repo_groups_perms:
435 for perm in self.default_repo_groups_perms:
436 rg_k = perm.UserRepoGroupToPerm.group.group_name
436 rg_k = perm.UserRepoGroupToPerm.group.group_name
437 p = 'group.admin'
437 p = 'group.admin'
438 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
438 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
439
439
440 # user groups
440 # user groups
441 for perm in self.default_user_group_perms:
441 for perm in self.default_user_group_perms:
442 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
442 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
443 p = 'usergroup.admin'
443 p = 'usergroup.admin'
444 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
444 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
445
445
446 return self._permission_structure()
446 return self._permission_structure()
447
447
448 def _calculate_global_default_permissions(self):
448 def _calculate_global_default_permissions(self):
449 """
449 """
450 global permissions taken from the default user
450 global permissions taken from the default user
451 """
451 """
452 default_global_perms = UserToPerm.query()\
452 default_global_perms = UserToPerm.query()\
453 .filter(UserToPerm.user_id == self.default_user_id)\
453 .filter(UserToPerm.user_id == self.default_user_id)\
454 .options(joinedload(UserToPerm.permission))
454 .options(joinedload(UserToPerm.permission))
455
455
456 for perm in default_global_perms:
456 for perm in default_global_perms:
457 self.permissions_global.add(perm.permission.permission_name)
457 self.permissions_global.add(perm.permission.permission_name)
458
458
459 if self.user_is_admin:
459 if self.user_is_admin:
460 self.permissions_global.add('hg.admin')
460 self.permissions_global.add('hg.admin')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462
462
463 def _calculate_global_permissions(self):
463 def _calculate_global_permissions(self):
464 """
464 """
465 Set global system permissions with user permissions or permissions
465 Set global system permissions with user permissions or permissions
466 taken from the user groups of the current user.
466 taken from the user groups of the current user.
467
467
468 The permissions include repo creating, repo group creating, forking
468 The permissions include repo creating, repo group creating, forking
469 etc.
469 etc.
470 """
470 """
471
471
472 # now we read the defined permissions and overwrite what we have set
472 # now we read the defined permissions and overwrite what we have set
473 # before those can be configured from groups or users explicitly.
473 # before those can be configured from groups or users explicitly.
474
474
475 # TODO: johbo: This seems to be out of sync, find out the reason
475 # TODO: johbo: This seems to be out of sync, find out the reason
476 # for the comment below and update it.
476 # for the comment below and update it.
477
477
478 # In case we want to extend this list we should be always in sync with
478 # In case we want to extend this list we should be always in sync with
479 # User.DEFAULT_USER_PERMISSIONS definitions
479 # User.DEFAULT_USER_PERMISSIONS definitions
480 _configurable = frozenset([
480 _configurable = frozenset([
481 'hg.fork.none', 'hg.fork.repository',
481 'hg.fork.none', 'hg.fork.repository',
482 'hg.create.none', 'hg.create.repository',
482 'hg.create.none', 'hg.create.repository',
483 'hg.usergroup.create.false', 'hg.usergroup.create.true',
483 'hg.usergroup.create.false', 'hg.usergroup.create.true',
484 'hg.repogroup.create.false', 'hg.repogroup.create.true',
484 'hg.repogroup.create.false', 'hg.repogroup.create.true',
485 'hg.create.write_on_repogroup.false',
485 'hg.create.write_on_repogroup.false',
486 'hg.create.write_on_repogroup.true',
486 'hg.create.write_on_repogroup.true',
487 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
487 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
488 ])
488 ])
489
489
490 # USER GROUPS comes first user group global permissions
490 # USER GROUPS comes first user group global permissions
491 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
491 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
492 .options(joinedload(UserGroupToPerm.permission))\
492 .options(joinedload(UserGroupToPerm.permission))\
493 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
493 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
494 UserGroupMember.users_group_id))\
494 UserGroupMember.users_group_id))\
495 .filter(UserGroupMember.user_id == self.user_id)\
495 .filter(UserGroupMember.user_id == self.user_id)\
496 .order_by(UserGroupToPerm.users_group_id)\
496 .order_by(UserGroupToPerm.users_group_id)\
497 .all()
497 .all()
498
498
499 # need to group here by groups since user can be in more than
499 # need to group here by groups since user can be in more than
500 # one group, so we get all groups
500 # one group, so we get all groups
501 _explicit_grouped_perms = [
501 _explicit_grouped_perms = [
502 [x, list(y)] for x, y in
502 [x, list(y)] for x, y in
503 itertools.groupby(user_perms_from_users_groups,
503 itertools.groupby(user_perms_from_users_groups,
504 lambda _x: _x.users_group)]
504 lambda _x: _x.users_group)]
505
505
506 for gr, perms in _explicit_grouped_perms:
506 for gr, perms in _explicit_grouped_perms:
507 # since user can be in multiple groups iterate over them and
507 # since user can be in multiple groups iterate over them and
508 # select the lowest permissions first (more explicit)
508 # select the lowest permissions first (more explicit)
509 # TODO: marcink: do this^^
509 # TODO: marcink: do this^^
510
510
511 # group doesn't inherit default permissions so we actually set them
511 # group doesn't inherit default permissions so we actually set them
512 if not gr.inherit_default_permissions:
512 if not gr.inherit_default_permissions:
513 # NEED TO IGNORE all previously set configurable permissions
513 # NEED TO IGNORE all previously set configurable permissions
514 # and replace them with explicitly set from this user
514 # and replace them with explicitly set from this user
515 # group permissions
515 # group permissions
516 self.permissions_global = self.permissions_global.difference(
516 self.permissions_global = self.permissions_global.difference(
517 _configurable)
517 _configurable)
518 for perm in perms:
518 for perm in perms:
519 self.permissions_global.add(perm.permission.permission_name)
519 self.permissions_global.add(perm.permission.permission_name)
520
520
521 # user explicit global permissions
521 # user explicit global permissions
522 user_perms = Session().query(UserToPerm)\
522 user_perms = Session().query(UserToPerm)\
523 .options(joinedload(UserToPerm.permission))\
523 .options(joinedload(UserToPerm.permission))\
524 .filter(UserToPerm.user_id == self.user_id).all()
524 .filter(UserToPerm.user_id == self.user_id).all()
525
525
526 if not self.inherit_default_permissions:
526 if not self.inherit_default_permissions:
527 # NEED TO IGNORE all configurable permissions and
527 # NEED TO IGNORE all configurable permissions and
528 # replace them with explicitly set from this user permissions
528 # replace them with explicitly set from this user permissions
529 self.permissions_global = self.permissions_global.difference(
529 self.permissions_global = self.permissions_global.difference(
530 _configurable)
530 _configurable)
531 for perm in user_perms:
531 for perm in user_perms:
532 self.permissions_global.add(perm.permission.permission_name)
532 self.permissions_global.add(perm.permission.permission_name)
533
533
534 def _calculate_default_permissions(self):
534 def _calculate_default_permissions(self):
535 """
535 """
536 Set default user permissions for repositories, repository groups
536 Set default user permissions for repositories, repository groups
537 taken from the default user.
537 taken from the default user.
538
538
539 Calculate inheritance of object permissions based on what we have now
539 Calculate inheritance of object permissions based on what we have now
540 in GLOBAL permissions. We check if .false is in GLOBAL since this is
540 in GLOBAL permissions. We check if .false is in GLOBAL since this is
541 explicitly set. Inherit is the opposite of .false being there.
541 explicitly set. Inherit is the opposite of .false being there.
542
542
543 .. note::
543 .. note::
544
544
545 the syntax is little bit odd but what we need to check here is
545 the syntax is little bit odd but what we need to check here is
546 the opposite of .false permission being in the list so even for
546 the opposite of .false permission being in the list so even for
547 inconsistent state when both .true/.false is there
547 inconsistent state when both .true/.false is there
548 .false is more important
548 .false is more important
549
549
550 """
550 """
551 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
551 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
552 in self.permissions_global)
552 in self.permissions_global)
553
553
554 # defaults for repositories, taken from `default` user permissions
554 # defaults for repositories, taken from `default` user permissions
555 # on given repo
555 # on given repo
556 for perm in self.default_repo_perms:
556 for perm in self.default_repo_perms:
557 r_k = perm.UserRepoToPerm.repository.repo_name
557 r_k = perm.UserRepoToPerm.repository.repo_name
558 p = perm.Permission.permission_name
558 p = perm.Permission.permission_name
559 o = PermOrigin.REPO_DEFAULT
559 o = PermOrigin.REPO_DEFAULT
560 self.permissions_repositories[r_k] = p, o
560 self.permissions_repositories[r_k] = p, o
561
561
562 # if we decide this user isn't inheriting permissions from
562 # if we decide this user isn't inheriting permissions from
563 # default user we set him to .none so only explicit
563 # default user we set him to .none so only explicit
564 # permissions work
564 # permissions work
565 if not user_inherit_object_permissions:
565 if not user_inherit_object_permissions:
566 p = 'repository.none'
566 p = 'repository.none'
567 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
567 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
568 self.permissions_repositories[r_k] = p, o
568 self.permissions_repositories[r_k] = p, o
569
569
570 if perm.Repository.private and not (
570 if perm.Repository.private and not (
571 perm.Repository.user_id == self.user_id):
571 perm.Repository.user_id == self.user_id):
572 # disable defaults for private repos,
572 # disable defaults for private repos,
573 p = 'repository.none'
573 p = 'repository.none'
574 o = PermOrigin.REPO_PRIVATE
574 o = PermOrigin.REPO_PRIVATE
575 self.permissions_repositories[r_k] = p, o
575 self.permissions_repositories[r_k] = p, o
576
576
577 elif perm.Repository.user_id == self.user_id:
577 elif perm.Repository.user_id == self.user_id:
578 # set admin if owner
578 # set admin if owner
579 p = 'repository.admin'
579 p = 'repository.admin'
580 o = PermOrigin.REPO_OWNER
580 o = PermOrigin.REPO_OWNER
581 self.permissions_repositories[r_k] = p, o
581 self.permissions_repositories[r_k] = p, o
582
582
583 if self.user_is_admin:
583 if self.user_is_admin:
584 p = 'repository.admin'
584 p = 'repository.admin'
585 o = PermOrigin.SUPER_ADMIN
585 o = PermOrigin.SUPER_ADMIN
586 self.permissions_repositories[r_k] = p, o
586 self.permissions_repositories[r_k] = p, o
587
587
588 # defaults for repository groups taken from `default` user permission
588 # defaults for repository groups taken from `default` user permission
589 # on given group
589 # on given group
590 for perm in self.default_repo_groups_perms:
590 for perm in self.default_repo_groups_perms:
591 rg_k = perm.UserRepoGroupToPerm.group.group_name
591 rg_k = perm.UserRepoGroupToPerm.group.group_name
592 p = perm.Permission.permission_name
592 p = perm.Permission.permission_name
593 o = PermOrigin.REPOGROUP_DEFAULT
593 o = PermOrigin.REPOGROUP_DEFAULT
594 self.permissions_repository_groups[rg_k] = p, o
594 self.permissions_repository_groups[rg_k] = p, o
595
595
596 # if we decide this user isn't inheriting permissions from default
596 # if we decide this user isn't inheriting permissions from default
597 # user we set him to .none so only explicit permissions work
597 # user we set him to .none so only explicit permissions work
598 if not user_inherit_object_permissions:
598 if not user_inherit_object_permissions:
599 p = 'group.none'
599 p = 'group.none'
600 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
600 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
601 self.permissions_repository_groups[rg_k] = p, o
601 self.permissions_repository_groups[rg_k] = p, o
602
602
603 if perm.RepoGroup.user_id == self.user_id:
603 if perm.RepoGroup.user_id == self.user_id:
604 # set admin if owner
604 # set admin if owner
605 p = 'group.admin'
605 p = 'group.admin'
606 o = PermOrigin.REPOGROUP_OWNER
606 o = PermOrigin.REPOGROUP_OWNER
607 self.permissions_repository_groups[rg_k] = p, o
607 self.permissions_repository_groups[rg_k] = p, o
608
608
609 if self.user_is_admin:
609 if self.user_is_admin:
610 p = 'group.admin'
610 p = 'group.admin'
611 o = PermOrigin.SUPER_ADMIN
611 o = PermOrigin.SUPER_ADMIN
612 self.permissions_repository_groups[rg_k] = p, o
612 self.permissions_repository_groups[rg_k] = p, o
613
613
614 # defaults for user groups taken from `default` user permission
614 # defaults for user groups taken from `default` user permission
615 # on given user group
615 # on given user group
616 for perm in self.default_user_group_perms:
616 for perm in self.default_user_group_perms:
617 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
617 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
618 p = perm.Permission.permission_name
618 p = perm.Permission.permission_name
619 o = PermOrigin.USERGROUP_DEFAULT
619 o = PermOrigin.USERGROUP_DEFAULT
620 self.permissions_user_groups[u_k] = p, o
620 self.permissions_user_groups[u_k] = p, o
621
621
622 # if we decide this user isn't inheriting permissions from default
622 # if we decide this user isn't inheriting permissions from default
623 # user we set him to .none so only explicit permissions work
623 # user we set him to .none so only explicit permissions work
624 if not user_inherit_object_permissions:
624 if not user_inherit_object_permissions:
625 p = 'usergroup.none'
625 p = 'usergroup.none'
626 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
626 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
627 self.permissions_user_groups[u_k] = p, o
627 self.permissions_user_groups[u_k] = p, o
628
628
629 if perm.UserGroup.user_id == self.user_id:
629 if perm.UserGroup.user_id == self.user_id:
630 # set admin if owner
630 # set admin if owner
631 p = 'usergroup.admin'
631 p = 'usergroup.admin'
632 o = PermOrigin.USERGROUP_OWNER
632 o = PermOrigin.USERGROUP_OWNER
633 self.permissions_user_groups[u_k] = p, o
633 self.permissions_user_groups[u_k] = p, o
634
634
635 if self.user_is_admin:
635 if self.user_is_admin:
636 p = 'usergroup.admin'
636 p = 'usergroup.admin'
637 o = PermOrigin.SUPER_ADMIN
637 o = PermOrigin.SUPER_ADMIN
638 self.permissions_user_groups[u_k] = p, o
638 self.permissions_user_groups[u_k] = p, o
639
639
640 def _calculate_repository_permissions(self):
640 def _calculate_repository_permissions(self):
641 """
641 """
642 Repository permissions for the current user.
642 Repository permissions for the current user.
643
643
644 Check if the user is part of user groups for this repository and
644 Check if the user is part of user groups for this repository and
645 fill in the permission from it. `_choose_permission` decides of which
645 fill in the permission from it. `_choose_permission` decides of which
646 permission should be selected based on selected method.
646 permission should be selected based on selected method.
647 """
647 """
648
648
649 # user group for repositories permissions
649 # user group for repositories permissions
650 user_repo_perms_from_user_group = Permission\
650 user_repo_perms_from_user_group = Permission\
651 .get_default_repo_perms_from_user_group(
651 .get_default_repo_perms_from_user_group(
652 self.user_id, self.scope_repo_id)
652 self.user_id, self.scope_repo_id)
653
653
654 multiple_counter = collections.defaultdict(int)
654 multiple_counter = collections.defaultdict(int)
655 for perm in user_repo_perms_from_user_group:
655 for perm in user_repo_perms_from_user_group:
656 r_k = perm.UserGroupRepoToPerm.repository.repo_name
656 r_k = perm.UserGroupRepoToPerm.repository.repo_name
657 multiple_counter[r_k] += 1
657 multiple_counter[r_k] += 1
658 p = perm.Permission.permission_name
658 p = perm.Permission.permission_name
659 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
659 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
660 .users_group.users_group_name
660 .users_group.users_group_name
661
661
662 if multiple_counter[r_k] > 1:
662 if multiple_counter[r_k] > 1:
663 cur_perm = self.permissions_repositories[r_k]
663 cur_perm = self.permissions_repositories[r_k]
664 p = self._choose_permission(p, cur_perm)
664 p = self._choose_permission(p, cur_perm)
665
665
666 self.permissions_repositories[r_k] = p, o
666 self.permissions_repositories[r_k] = p, o
667
667
668 if perm.Repository.user_id == self.user_id:
668 if perm.Repository.user_id == self.user_id:
669 # set admin if owner
669 # set admin if owner
670 p = 'repository.admin'
670 p = 'repository.admin'
671 o = PermOrigin.REPO_OWNER
671 o = PermOrigin.REPO_OWNER
672 self.permissions_repositories[r_k] = p, o
672 self.permissions_repositories[r_k] = p, o
673
673
674 if self.user_is_admin:
674 if self.user_is_admin:
675 p = 'repository.admin'
675 p = 'repository.admin'
676 o = PermOrigin.SUPER_ADMIN
676 o = PermOrigin.SUPER_ADMIN
677 self.permissions_repositories[r_k] = p, o
677 self.permissions_repositories[r_k] = p, o
678
678
679 # user explicit permissions for repositories, overrides any specified
679 # user explicit permissions for repositories, overrides any specified
680 # by the group permission
680 # by the group permission
681 user_repo_perms = Permission.get_default_repo_perms(
681 user_repo_perms = Permission.get_default_repo_perms(
682 self.user_id, self.scope_repo_id)
682 self.user_id, self.scope_repo_id)
683 for perm in user_repo_perms:
683 for perm in user_repo_perms:
684 r_k = perm.UserRepoToPerm.repository.repo_name
684 r_k = perm.UserRepoToPerm.repository.repo_name
685 p = perm.Permission.permission_name
685 p = perm.Permission.permission_name
686 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
686 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
687
687
688 if not self.explicit:
688 if not self.explicit:
689 cur_perm = self.permissions_repositories.get(
689 cur_perm = self.permissions_repositories.get(
690 r_k, 'repository.none')
690 r_k, 'repository.none')
691 p = self._choose_permission(p, cur_perm)
691 p = self._choose_permission(p, cur_perm)
692
692
693 self.permissions_repositories[r_k] = p, o
693 self.permissions_repositories[r_k] = p, o
694
694
695 if perm.Repository.user_id == self.user_id:
695 if perm.Repository.user_id == self.user_id:
696 # set admin if owner
696 # set admin if owner
697 p = 'repository.admin'
697 p = 'repository.admin'
698 o = PermOrigin.REPO_OWNER
698 o = PermOrigin.REPO_OWNER
699 self.permissions_repositories[r_k] = p, o
699 self.permissions_repositories[r_k] = p, o
700
700
701 if self.user_is_admin:
701 if self.user_is_admin:
702 p = 'repository.admin'
702 p = 'repository.admin'
703 o = PermOrigin.SUPER_ADMIN
703 o = PermOrigin.SUPER_ADMIN
704 self.permissions_repositories[r_k] = p, o
704 self.permissions_repositories[r_k] = p, o
705
705
706 def _calculate_repository_group_permissions(self):
706 def _calculate_repository_group_permissions(self):
707 """
707 """
708 Repository group permissions for the current user.
708 Repository group permissions for the current user.
709
709
710 Check if the user is part of user groups for repository groups and
710 Check if the user is part of user groups for repository groups and
711 fill in the permissions from it. `_choose_permission` decides of which
711 fill in the permissions from it. `_choose_permission` decides of which
712 permission should be selected based on selected method.
712 permission should be selected based on selected method.
713 """
713 """
714 # user group for repo groups permissions
714 # user group for repo groups permissions
715 user_repo_group_perms_from_user_group = Permission\
715 user_repo_group_perms_from_user_group = Permission\
716 .get_default_group_perms_from_user_group(
716 .get_default_group_perms_from_user_group(
717 self.user_id, self.scope_repo_group_id)
717 self.user_id, self.scope_repo_group_id)
718
718
719 multiple_counter = collections.defaultdict(int)
719 multiple_counter = collections.defaultdict(int)
720 for perm in user_repo_group_perms_from_user_group:
720 for perm in user_repo_group_perms_from_user_group:
721 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
721 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
722 multiple_counter[rg_k] += 1
722 multiple_counter[rg_k] += 1
723 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
723 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
724 .users_group.users_group_name
724 .users_group.users_group_name
725 p = perm.Permission.permission_name
725 p = perm.Permission.permission_name
726
726
727 if multiple_counter[rg_k] > 1:
727 if multiple_counter[rg_k] > 1:
728 cur_perm = self.permissions_repository_groups[rg_k]
728 cur_perm = self.permissions_repository_groups[rg_k]
729 p = self._choose_permission(p, cur_perm)
729 p = self._choose_permission(p, cur_perm)
730 self.permissions_repository_groups[rg_k] = p, o
730 self.permissions_repository_groups[rg_k] = p, o
731
731
732 if perm.RepoGroup.user_id == self.user_id:
732 if perm.RepoGroup.user_id == self.user_id:
733 # set admin if owner, even for member of other user group
733 # set admin if owner, even for member of other user group
734 p = 'group.admin'
734 p = 'group.admin'
735 o = PermOrigin.REPOGROUP_OWNER
735 o = PermOrigin.REPOGROUP_OWNER
736 self.permissions_repository_groups[rg_k] = p, o
736 self.permissions_repository_groups[rg_k] = p, o
737
737
738 if self.user_is_admin:
738 if self.user_is_admin:
739 p = 'group.admin'
739 p = 'group.admin'
740 o = PermOrigin.SUPER_ADMIN
740 o = PermOrigin.SUPER_ADMIN
741 self.permissions_repository_groups[rg_k] = p, o
741 self.permissions_repository_groups[rg_k] = p, o
742
742
743 # user explicit permissions for repository groups
743 # user explicit permissions for repository groups
744 user_repo_groups_perms = Permission.get_default_group_perms(
744 user_repo_groups_perms = Permission.get_default_group_perms(
745 self.user_id, self.scope_repo_group_id)
745 self.user_id, self.scope_repo_group_id)
746 for perm in user_repo_groups_perms:
746 for perm in user_repo_groups_perms:
747 rg_k = perm.UserRepoGroupToPerm.group.group_name
747 rg_k = perm.UserRepoGroupToPerm.group.group_name
748 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
748 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
749 .user.username
749 .user.username
750 p = perm.Permission.permission_name
750 p = perm.Permission.permission_name
751
751
752 if not self.explicit:
752 if not self.explicit:
753 cur_perm = self.permissions_repository_groups.get(
753 cur_perm = self.permissions_repository_groups.get(
754 rg_k, 'group.none')
754 rg_k, 'group.none')
755 p = self._choose_permission(p, cur_perm)
755 p = self._choose_permission(p, cur_perm)
756
756
757 self.permissions_repository_groups[rg_k] = p, o
757 self.permissions_repository_groups[rg_k] = p, o
758
758
759 if perm.RepoGroup.user_id == self.user_id:
759 if perm.RepoGroup.user_id == self.user_id:
760 # set admin if owner
760 # set admin if owner
761 p = 'group.admin'
761 p = 'group.admin'
762 o = PermOrigin.REPOGROUP_OWNER
762 o = PermOrigin.REPOGROUP_OWNER
763 self.permissions_repository_groups[rg_k] = p, o
763 self.permissions_repository_groups[rg_k] = p, o
764
764
765 if self.user_is_admin:
765 if self.user_is_admin:
766 p = 'group.admin'
766 p = 'group.admin'
767 o = PermOrigin.SUPER_ADMIN
767 o = PermOrigin.SUPER_ADMIN
768 self.permissions_repository_groups[rg_k] = p, o
768 self.permissions_repository_groups[rg_k] = p, o
769
769
770 def _calculate_user_group_permissions(self):
770 def _calculate_user_group_permissions(self):
771 """
771 """
772 User group permissions for the current user.
772 User group permissions for the current user.
773 """
773 """
774 # user group for user group permissions
774 # user group for user group permissions
775 user_group_from_user_group = Permission\
775 user_group_from_user_group = Permission\
776 .get_default_user_group_perms_from_user_group(
776 .get_default_user_group_perms_from_user_group(
777 self.user_id, self.scope_user_group_id)
777 self.user_id, self.scope_user_group_id)
778
778
779 multiple_counter = collections.defaultdict(int)
779 multiple_counter = collections.defaultdict(int)
780 for perm in user_group_from_user_group:
780 for perm in user_group_from_user_group:
781 ug_k = perm.UserGroupUserGroupToPerm\
781 ug_k = perm.UserGroupUserGroupToPerm\
782 .target_user_group.users_group_name
782 .target_user_group.users_group_name
783 multiple_counter[ug_k] += 1
783 multiple_counter[ug_k] += 1
784 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
784 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
785 .user_group.users_group_name
785 .user_group.users_group_name
786 p = perm.Permission.permission_name
786 p = perm.Permission.permission_name
787
787
788 if multiple_counter[ug_k] > 1:
788 if multiple_counter[ug_k] > 1:
789 cur_perm = self.permissions_user_groups[ug_k]
789 cur_perm = self.permissions_user_groups[ug_k]
790 p = self._choose_permission(p, cur_perm)
790 p = self._choose_permission(p, cur_perm)
791
791
792 self.permissions_user_groups[ug_k] = p, o
792 self.permissions_user_groups[ug_k] = p, o
793
793
794 if perm.UserGroup.user_id == self.user_id:
794 if perm.UserGroup.user_id == self.user_id:
795 # set admin if owner, even for member of other user group
795 # set admin if owner, even for member of other user group
796 p = 'usergroup.admin'
796 p = 'usergroup.admin'
797 o = PermOrigin.USERGROUP_OWNER
797 o = PermOrigin.USERGROUP_OWNER
798 self.permissions_user_groups[ug_k] = p, o
798 self.permissions_user_groups[ug_k] = p, o
799
799
800 if self.user_is_admin:
800 if self.user_is_admin:
801 p = 'usergroup.admin'
801 p = 'usergroup.admin'
802 o = PermOrigin.SUPER_ADMIN
802 o = PermOrigin.SUPER_ADMIN
803 self.permissions_user_groups[ug_k] = p, o
803 self.permissions_user_groups[ug_k] = p, o
804
804
805 # user explicit permission for user groups
805 # user explicit permission for user groups
806 user_user_groups_perms = Permission.get_default_user_group_perms(
806 user_user_groups_perms = Permission.get_default_user_group_perms(
807 self.user_id, self.scope_user_group_id)
807 self.user_id, self.scope_user_group_id)
808 for perm in user_user_groups_perms:
808 for perm in user_user_groups_perms:
809 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
809 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
810 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
810 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
811 .user.username
811 .user.username
812 p = perm.Permission.permission_name
812 p = perm.Permission.permission_name
813
813
814 if not self.explicit:
814 if not self.explicit:
815 cur_perm = self.permissions_user_groups.get(
815 cur_perm = self.permissions_user_groups.get(
816 ug_k, 'usergroup.none')
816 ug_k, 'usergroup.none')
817 p = self._choose_permission(p, cur_perm)
817 p = self._choose_permission(p, cur_perm)
818
818
819 self.permissions_user_groups[ug_k] = p, o
819 self.permissions_user_groups[ug_k] = p, o
820
820
821 if perm.UserGroup.user_id == self.user_id:
821 if perm.UserGroup.user_id == self.user_id:
822 # set admin if owner
822 # set admin if owner
823 p = 'usergroup.admin'
823 p = 'usergroup.admin'
824 o = PermOrigin.USERGROUP_OWNER
824 o = PermOrigin.USERGROUP_OWNER
825 self.permissions_user_groups[ug_k] = p, o
825 self.permissions_user_groups[ug_k] = p, o
826
826
827 if self.user_is_admin:
827 if self.user_is_admin:
828 p = 'usergroup.admin'
828 p = 'usergroup.admin'
829 o = PermOrigin.SUPER_ADMIN
829 o = PermOrigin.SUPER_ADMIN
830 self.permissions_user_groups[ug_k] = p, o
830 self.permissions_user_groups[ug_k] = p, o
831
831
832 def _choose_permission(self, new_perm, cur_perm):
832 def _choose_permission(self, new_perm, cur_perm):
833 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
833 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
834 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
834 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
835 if self.algo == 'higherwin':
835 if self.algo == 'higherwin':
836 if new_perm_val > cur_perm_val:
836 if new_perm_val > cur_perm_val:
837 return new_perm
837 return new_perm
838 return cur_perm
838 return cur_perm
839 elif self.algo == 'lowerwin':
839 elif self.algo == 'lowerwin':
840 if new_perm_val < cur_perm_val:
840 if new_perm_val < cur_perm_val:
841 return new_perm
841 return new_perm
842 return cur_perm
842 return cur_perm
843
843
844 def _permission_structure(self):
844 def _permission_structure(self):
845 return {
845 return {
846 'global': self.permissions_global,
846 'global': self.permissions_global,
847 'repositories': self.permissions_repositories,
847 'repositories': self.permissions_repositories,
848 'repositories_groups': self.permissions_repository_groups,
848 'repositories_groups': self.permissions_repository_groups,
849 'user_groups': self.permissions_user_groups,
849 'user_groups': self.permissions_user_groups,
850 }
850 }
851
851
852
852
853 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
853 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
854 """
854 """
855 Check if given controller_name is in whitelist of auth token access
855 Check if given controller_name is in whitelist of auth token access
856 """
856 """
857 if not whitelist:
857 if not whitelist:
858 from rhodecode import CONFIG
858 from rhodecode import CONFIG
859 whitelist = aslist(
859 whitelist = aslist(
860 CONFIG.get('api_access_controllers_whitelist'), sep=',')
860 CONFIG.get('api_access_controllers_whitelist'), sep=',')
861 # backward compat translation
861 # backward compat translation
862 compat = {
862 compat = {
863 # old controller, new VIEW
863 # old controller, new VIEW
864 'ChangesetController:*': 'RepoCommitsView:*',
864 'ChangesetController:*': 'RepoCommitsView:*',
865 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
865 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
866 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
866 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
867 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
867 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
868 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
868 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
869 'GistsController:*': 'GistView:*',
869 'GistsController:*': 'GistView:*',
870 }
870 }
871
871
872 log.debug(
872 log.debug(
873 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
873 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
874 auth_token_access_valid = False
874 auth_token_access_valid = False
875
875
876 for entry in whitelist:
876 for entry in whitelist:
877 token_match = True
877 token_match = True
878 if entry in compat:
878 if entry in compat:
879 # translate from old Controllers to Pyramid Views
879 # translate from old Controllers to Pyramid Views
880 entry = compat[entry]
880 entry = compat[entry]
881
881
882 if '@' in entry:
882 if '@' in entry:
883 # specific AuthToken
883 # specific AuthToken
884 entry, allowed_token = entry.split('@', 1)
884 entry, allowed_token = entry.split('@', 1)
885 token_match = auth_token == allowed_token
885 token_match = auth_token == allowed_token
886
886
887 if fnmatch.fnmatch(view_name, entry) and token_match:
887 if fnmatch.fnmatch(view_name, entry) and token_match:
888 auth_token_access_valid = True
888 auth_token_access_valid = True
889 break
889 break
890
890
891 if auth_token_access_valid:
891 if auth_token_access_valid:
892 log.debug('view: `%s` matches entry in whitelist: %s'
892 log.debug('view: `%s` matches entry in whitelist: %s'
893 % (view_name, whitelist))
893 % (view_name, whitelist))
894 else:
894 else:
895 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
895 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
896 % (view_name, whitelist))
896 % (view_name, whitelist))
897 if auth_token:
897 if auth_token:
898 # if we use auth token key and don't have access it's a warning
898 # if we use auth token key and don't have access it's a warning
899 log.warning(msg)
899 log.warning(msg)
900 else:
900 else:
901 log.debug(msg)
901 log.debug(msg)
902
902
903 return auth_token_access_valid
903 return auth_token_access_valid
904
904
905
905
906 class AuthUser(object):
906 class AuthUser(object):
907 """
907 """
908 A simple object that handles all attributes of user in RhodeCode
908 A simple object that handles all attributes of user in RhodeCode
909
909
910 It does lookup based on API key,given user, or user present in session
910 It does lookup based on API key,given user, or user present in session
911 Then it fills all required information for such user. It also checks if
911 Then it fills all required information for such user. It also checks if
912 anonymous access is enabled and if so, it returns default user as logged in
912 anonymous access is enabled and if so, it returns default user as logged in
913 """
913 """
914 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
914 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
915
915
916 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
916 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
917
917
918 self.user_id = user_id
918 self.user_id = user_id
919 self._api_key = api_key
919 self._api_key = api_key
920
920
921 self.api_key = None
921 self.api_key = None
922 self.username = username
922 self.username = username
923 self.ip_addr = ip_addr
923 self.ip_addr = ip_addr
924 self.name = ''
924 self.name = ''
925 self.lastname = ''
925 self.lastname = ''
926 self.first_name = ''
926 self.first_name = ''
927 self.last_name = ''
927 self.last_name = ''
928 self.email = ''
928 self.email = ''
929 self.is_authenticated = False
929 self.is_authenticated = False
930 self.admin = False
930 self.admin = False
931 self.inherit_default_permissions = False
931 self.inherit_default_permissions = False
932 self.password = ''
932 self.password = ''
933
933
934 self.anonymous_user = None # propagated on propagate_data
934 self.anonymous_user = None # propagated on propagate_data
935 self.propagate_data()
935 self.propagate_data()
936 self._instance = None
936 self._instance = None
937 self._permissions_scoped_cache = {} # used to bind scoped calculation
937 self._permissions_scoped_cache = {} # used to bind scoped calculation
938
938
939 @LazyProperty
939 @LazyProperty
940 def permissions(self):
940 def permissions(self):
941 return self.get_perms(user=self, cache=False)
941 return self.get_perms(user=self, cache=False)
942
942
943 @LazyProperty
943 @LazyProperty
944 def permissions_safe(self):
944 def permissions_safe(self):
945 """
945 """
946 Filtered permissions excluding not allowed repositories
946 Filtered permissions excluding not allowed repositories
947 """
947 """
948 perms = self.get_perms(user=self, cache=False)
948 perms = self.get_perms(user=self, cache=False)
949
949
950 perms['repositories'] = {
950 perms['repositories'] = {
951 k: v for k, v in perms['repositories'].items()
951 k: v for k, v in perms['repositories'].items()
952 if v != 'repository.none'}
952 if v != 'repository.none'}
953 perms['repositories_groups'] = {
953 perms['repositories_groups'] = {
954 k: v for k, v in perms['repositories_groups'].items()
954 k: v for k, v in perms['repositories_groups'].items()
955 if v != 'group.none'}
955 if v != 'group.none'}
956 perms['user_groups'] = {
956 perms['user_groups'] = {
957 k: v for k, v in perms['user_groups'].items()
957 k: v for k, v in perms['user_groups'].items()
958 if v != 'usergroup.none'}
958 if v != 'usergroup.none'}
959 return perms
959 return perms
960
960
961 @LazyProperty
961 @LazyProperty
962 def permissions_full_details(self):
962 def permissions_full_details(self):
963 return self.get_perms(
963 return self.get_perms(
964 user=self, cache=False, calculate_super_admin=True)
964 user=self, cache=False, calculate_super_admin=True)
965
965
966 def permissions_with_scope(self, scope):
966 def permissions_with_scope(self, scope):
967 """
967 """
968 Call the get_perms function with scoped data. The scope in that function
968 Call the get_perms function with scoped data. The scope in that function
969 narrows the SQL calls to the given ID of objects resulting in fetching
969 narrows the SQL calls to the given ID of objects resulting in fetching
970 Just particular permission we want to obtain. If scope is an empty dict
970 Just particular permission we want to obtain. If scope is an empty dict
971 then it basically narrows the scope to GLOBAL permissions only.
971 then it basically narrows the scope to GLOBAL permissions only.
972
972
973 :param scope: dict
973 :param scope: dict
974 """
974 """
975 if 'repo_name' in scope:
975 if 'repo_name' in scope:
976 obj = Repository.get_by_repo_name(scope['repo_name'])
976 obj = Repository.get_by_repo_name(scope['repo_name'])
977 if obj:
977 if obj:
978 scope['repo_id'] = obj.repo_id
978 scope['repo_id'] = obj.repo_id
979 _scope = {
979 _scope = collections.OrderedDict()
980 'repo_id': -1,
980 _scope['repo_id'] = -1
981 'user_group_id': -1,
981 _scope['user_group_id'] = -1
982 'repo_group_id': -1,
982 _scope['repo_group_id'] = -1
983 }
983
984 _scope.update(scope)
984 for k in sorted(scope.keys()):
985 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
985 _scope[k] = scope[k]
986 _scope.items())))
986
987 if cache_key not in self._permissions_scoped_cache:
987 # store in cache to mimic how the @LazyProperty works,
988 # store in cache to mimic how the @LazyProperty works,
988 # the difference here is that we use the unique key calculated
989 # the difference here is that we use the unique key calculated
989 # from params and values
990 # from params and values
990 return self.get_perms(user=self, cache=False, scope=_scope)
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]
994
991
995 def get_instance(self):
992 def get_instance(self):
996 return User.get(self.user_id)
993 return User.get(self.user_id)
997
994
998 def update_lastactivity(self):
995 def update_lastactivity(self):
999 if self.user_id:
996 if self.user_id:
1000 User.get(self.user_id).update_lastactivity()
997 User.get(self.user_id).update_lastactivity()
1001
998
1002 def propagate_data(self):
999 def propagate_data(self):
1003 """
1000 """
1004 Fills in user data and propagates values to this instance. Maps fetched
1001 Fills in user data and propagates values to this instance. Maps fetched
1005 user attributes to this class instance attributes
1002 user attributes to this class instance attributes
1006 """
1003 """
1007 log.debug('AuthUser: starting data propagation for new potential user')
1004 log.debug('AuthUser: starting data propagation for new potential user')
1008 user_model = UserModel()
1005 user_model = UserModel()
1009 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1006 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1010 is_user_loaded = False
1007 is_user_loaded = False
1011
1008
1012 # lookup by userid
1009 # lookup by userid
1013 if self.user_id is not None and self.user_id != anon_user.user_id:
1010 if self.user_id is not None and self.user_id != anon_user.user_id:
1014 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1011 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1015 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1012 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1016
1013
1017 # try go get user by api key
1014 # try go get user by api key
1018 elif self._api_key and self._api_key != anon_user.api_key:
1015 elif self._api_key and self._api_key != anon_user.api_key:
1019 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1016 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1020 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1017 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1021
1018
1022 # lookup by username
1019 # lookup by username
1023 elif self.username:
1020 elif self.username:
1024 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1021 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1025 is_user_loaded = user_model.fill_data(self, username=self.username)
1022 is_user_loaded = user_model.fill_data(self, username=self.username)
1026 else:
1023 else:
1027 log.debug('No data in %s that could been used to log in', self)
1024 log.debug('No data in %s that could been used to log in', self)
1028
1025
1029 if not is_user_loaded:
1026 if not is_user_loaded:
1030 log.debug(
1027 log.debug(
1031 'Failed to load user. Fallback to default user %s', anon_user)
1028 'Failed to load user. Fallback to default user %s', anon_user)
1032 # if we cannot authenticate user try anonymous
1029 # if we cannot authenticate user try anonymous
1033 if anon_user.active:
1030 if anon_user.active:
1034 log.debug('default user is active, using it as a session user')
1031 log.debug('default user is active, using it as a session user')
1035 user_model.fill_data(self, user_id=anon_user.user_id)
1032 user_model.fill_data(self, user_id=anon_user.user_id)
1036 # then we set this user is logged in
1033 # then we set this user is logged in
1037 self.is_authenticated = True
1034 self.is_authenticated = True
1038 else:
1035 else:
1039 log.debug('default user is NOT active')
1036 log.debug('default user is NOT active')
1040 # in case of disabled anonymous user we reset some of the
1037 # in case of disabled anonymous user we reset some of the
1041 # parameters so such user is "corrupted", skipping the fill_data
1038 # parameters so such user is "corrupted", skipping the fill_data
1042 for attr in ['user_id', 'username', 'admin', 'active']:
1039 for attr in ['user_id', 'username', 'admin', 'active']:
1043 setattr(self, attr, None)
1040 setattr(self, attr, None)
1044 self.is_authenticated = False
1041 self.is_authenticated = False
1045
1042
1046 if not self.username:
1043 if not self.username:
1047 self.username = 'None'
1044 self.username = 'None'
1048
1045
1049 log.debug('AuthUser: propagated user is now %s', self)
1046 log.debug('AuthUser: propagated user is now %s', self)
1050
1047
1051 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1048 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1052 calculate_super_admin=False, cache=False):
1049 calculate_super_admin=False, cache=False):
1053 """
1050 """
1054 Fills user permission attribute with permissions taken from database
1051 Fills user permission attribute with permissions taken from database
1055 works for permissions given for repositories, and for permissions that
1052 works for permissions given for repositories, and for permissions that
1056 are granted to groups
1053 are granted to groups
1057
1054
1058 :param user: instance of User object from database
1055 :param user: instance of User object from database
1059 :param explicit: In case there are permissions both for user and a group
1056 :param explicit: In case there are permissions both for user and a group
1060 that user is part of, explicit flag will defiine if user will
1057 that user is part of, explicit flag will defiine if user will
1061 explicitly override permissions from group, if it's False it will
1058 explicitly override permissions from group, if it's False it will
1062 make decision based on the algo
1059 make decision based on the algo
1063 :param algo: algorithm to decide what permission should be choose if
1060 :param algo: algorithm to decide what permission should be choose if
1064 it's multiple defined, eg user in two different groups. It also
1061 it's multiple defined, eg user in two different groups. It also
1065 decides if explicit flag is turned off how to specify the permission
1062 decides if explicit flag is turned off how to specify the permission
1066 for case when user is in a group + have defined separate permission
1063 for case when user is in a group + have defined separate permission
1067 """
1064 """
1068 user_id = user.user_id
1065 user_id = user.user_id
1069 user_is_admin = user.is_admin
1066 user_is_admin = user.is_admin
1070
1067
1071 # inheritance of global permissions like create repo/fork repo etc
1068 # inheritance of global permissions like create repo/fork repo etc
1072 user_inherit_default_permissions = user.inherit_default_permissions
1069 user_inherit_default_permissions = user.inherit_default_permissions
1073
1070
1074 cache_seconds = safe_int(
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 cache_on = cache or cache_seconds > 0
1074 cache_on = cache or cache_seconds > 0
1077 log.debug(
1075 log.debug(
1078 'Computing PERMISSION tree for user %s scope `%s` '
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 start = time.time()
1091 start = time.time()
1081 compute = caches.conditional_cache(
1092 result = compute_perm_tree('permissions', user_id, scope, user_is_admin,
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,
1085 user_inherit_default_permissions, explicit, algo,
1093 user_inherit_default_permissions, explicit, algo,
1086 calculate_super_admin)
1094 calculate_super_admin)
1087
1095
1088 result_repr = []
1096 result_repr = []
1089 for k in result:
1097 for k in result:
1090 result_repr.append((k, len(result[k])))
1098 result_repr.append((k, len(result[k])))
1091 total = time.time() - start
1099 total = time.time() - start
1092 log.debug('PERMISSION tree for user %s computed in %.3fs: %s' % (
1100 log.debug('PERMISSION tree for user %s computed in %.3fs: %s' % (
1093 user, total, result_repr))
1101 user, total, result_repr))
1102
1094 return result
1103 return result
1095
1104
1096 @property
1105 @property
1097 def is_default(self):
1106 def is_default(self):
1098 return self.username == User.DEFAULT_USER
1107 return self.username == User.DEFAULT_USER
1099
1108
1100 @property
1109 @property
1101 def is_admin(self):
1110 def is_admin(self):
1102 return self.admin
1111 return self.admin
1103
1112
1104 @property
1113 @property
1105 def is_user_object(self):
1114 def is_user_object(self):
1106 return self.user_id is not None
1115 return self.user_id is not None
1107
1116
1108 @property
1117 @property
1109 def repositories_admin(self):
1118 def repositories_admin(self):
1110 """
1119 """
1111 Returns list of repositories you're an admin of
1120 Returns list of repositories you're an admin of
1112 """
1121 """
1113 return [
1122 return [
1114 x[0] for x in self.permissions['repositories'].items()
1123 x[0] for x in self.permissions['repositories'].items()
1115 if x[1] == 'repository.admin']
1124 if x[1] == 'repository.admin']
1116
1125
1117 @property
1126 @property
1118 def repository_groups_admin(self):
1127 def repository_groups_admin(self):
1119 """
1128 """
1120 Returns list of repository groups you're an admin of
1129 Returns list of repository groups you're an admin of
1121 """
1130 """
1122 return [
1131 return [
1123 x[0] for x in self.permissions['repositories_groups'].items()
1132 x[0] for x in self.permissions['repositories_groups'].items()
1124 if x[1] == 'group.admin']
1133 if x[1] == 'group.admin']
1125
1134
1126 @property
1135 @property
1127 def user_groups_admin(self):
1136 def user_groups_admin(self):
1128 """
1137 """
1129 Returns list of user groups you're an admin of
1138 Returns list of user groups you're an admin of
1130 """
1139 """
1131 return [
1140 return [
1132 x[0] for x in self.permissions['user_groups'].items()
1141 x[0] for x in self.permissions['user_groups'].items()
1133 if x[1] == 'usergroup.admin']
1142 if x[1] == 'usergroup.admin']
1134
1143
1135 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1144 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1136 """
1145 """
1137 Returns list of repository ids that user have access to based on given
1146 Returns list of repository ids that user have access to based on given
1138 perms. The cache flag should be only used in cases that are used for
1147 perms. The cache flag should be only used in cases that are used for
1139 display purposes, NOT IN ANY CASE for permission checks.
1148 display purposes, NOT IN ANY CASE for permission checks.
1140 """
1149 """
1141 from rhodecode.model.scm import RepoList
1150 from rhodecode.model.scm import RepoList
1142 if not perms:
1151 if not perms:
1143 perms = [
1152 perms = [
1144 'repository.read', 'repository.write', 'repository.admin']
1153 'repository.read', 'repository.write', 'repository.admin']
1145
1154
1146 def _cached_repo_acl(user_id, perm_def, _name_filter):
1155 def _cached_repo_acl(user_id, perm_def, _name_filter):
1147 qry = Repository.query()
1156 qry = Repository.query()
1148 if _name_filter:
1157 if _name_filter:
1149 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1158 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1150 qry = qry.filter(
1159 qry = qry.filter(
1151 Repository.repo_name.ilike(ilike_expression))
1160 Repository.repo_name.ilike(ilike_expression))
1152
1161
1153 return [x.repo_id for x in
1162 return [x.repo_id for x in
1154 RepoList(qry, perm_set=perm_def)]
1163 RepoList(qry, perm_set=perm_def)]
1155
1164
1156 compute = caches.conditional_cache(
1165 return _cached_repo_acl(self.user_id, perms, name_filter)
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)
1160
1166
1161 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1167 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1162 """
1168 """
1163 Returns list of repository group ids that user have access to based on given
1169 Returns list of repository group ids that user have access to based on given
1164 perms. The cache flag should be only used in cases that are used for
1170 perms. The cache flag should be only used in cases that are used for
1165 display purposes, NOT IN ANY CASE for permission checks.
1171 display purposes, NOT IN ANY CASE for permission checks.
1166 """
1172 """
1167 from rhodecode.model.scm import RepoGroupList
1173 from rhodecode.model.scm import RepoGroupList
1168 if not perms:
1174 if not perms:
1169 perms = [
1175 perms = [
1170 'group.read', 'group.write', 'group.admin']
1176 'group.read', 'group.write', 'group.admin']
1171
1177
1172 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1178 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1173 qry = RepoGroup.query()
1179 qry = RepoGroup.query()
1174 if _name_filter:
1180 if _name_filter:
1175 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1181 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1176 qry = qry.filter(
1182 qry = qry.filter(
1177 RepoGroup.group_name.ilike(ilike_expression))
1183 RepoGroup.group_name.ilike(ilike_expression))
1178
1184
1179 return [x.group_id for x in
1185 return [x.group_id for x in
1180 RepoGroupList(qry, perm_set=perm_def)]
1186 RepoGroupList(qry, perm_set=perm_def)]
1181
1187
1182 compute = caches.conditional_cache(
1188 return _cached_repo_group_acl(self.user_id, perms, name_filter)
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)
1186
1189
1187 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1190 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1188 """
1191 """
1189 Returns list of user group ids that user have access to based on given
1192 Returns list of user group ids that user have access to based on given
1190 perms. The cache flag should be only used in cases that are used for
1193 perms. The cache flag should be only used in cases that are used for
1191 display purposes, NOT IN ANY CASE for permission checks.
1194 display purposes, NOT IN ANY CASE for permission checks.
1192 """
1195 """
1193 from rhodecode.model.scm import UserGroupList
1196 from rhodecode.model.scm import UserGroupList
1194 if not perms:
1197 if not perms:
1195 perms = [
1198 perms = [
1196 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1199 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1197
1200
1198 def _cached_user_group_acl(user_id, perm_def, name_filter):
1201 def _cached_user_group_acl(user_id, perm_def, name_filter):
1199 qry = UserGroup.query()
1202 qry = UserGroup.query()
1200 if name_filter:
1203 if name_filter:
1201 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1204 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1202 qry = qry.filter(
1205 qry = qry.filter(
1203 UserGroup.users_group_name.ilike(ilike_expression))
1206 UserGroup.users_group_name.ilike(ilike_expression))
1204
1207
1205 return [x.users_group_id for x in
1208 return [x.users_group_id for x in
1206 UserGroupList(qry, perm_set=perm_def)]
1209 UserGroupList(qry, perm_set=perm_def)]
1207
1210
1208 compute = caches.conditional_cache(
1211 return _cached_user_group_acl(self.user_id, perms, name_filter)
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)
1212
1212
1213 @property
1213 @property
1214 def ip_allowed(self):
1214 def ip_allowed(self):
1215 """
1215 """
1216 Checks if ip_addr used in constructor is allowed from defined list of
1216 Checks if ip_addr used in constructor is allowed from defined list of
1217 allowed ip_addresses for user
1217 allowed ip_addresses for user
1218
1218
1219 :returns: boolean, True if ip is in allowed ip range
1219 :returns: boolean, True if ip is in allowed ip range
1220 """
1220 """
1221 # check IP
1221 # check IP
1222 inherit = self.inherit_default_permissions
1222 inherit = self.inherit_default_permissions
1223 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1223 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1224 inherit_from_default=inherit)
1224 inherit_from_default=inherit)
1225 @property
1225 @property
1226 def personal_repo_group(self):
1226 def personal_repo_group(self):
1227 return RepoGroup.get_user_personal_repo_group(self.user_id)
1227 return RepoGroup.get_user_personal_repo_group(self.user_id)
1228
1228
1229 @LazyProperty
1229 @LazyProperty
1230 def feed_token(self):
1230 def feed_token(self):
1231 return self.get_instance().feed_token
1231 return self.get_instance().feed_token
1232
1232
1233 @classmethod
1233 @classmethod
1234 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1234 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1235 allowed_ips = AuthUser.get_allowed_ips(
1235 allowed_ips = AuthUser.get_allowed_ips(
1236 user_id, cache=True, inherit_from_default=inherit_from_default)
1236 user_id, cache=True, inherit_from_default=inherit_from_default)
1237 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1237 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1238 log.debug('IP:%s for user %s is in range of %s' % (
1238 log.debug('IP:%s for user %s is in range of %s' % (
1239 ip_addr, user_id, allowed_ips))
1239 ip_addr, user_id, allowed_ips))
1240 return True
1240 return True
1241 else:
1241 else:
1242 log.info('Access for IP:%s forbidden for user %s, '
1242 log.info('Access for IP:%s forbidden for user %s, '
1243 'not in %s' % (ip_addr, user_id, allowed_ips))
1243 'not in %s' % (ip_addr, user_id, allowed_ips))
1244 return False
1244 return False
1245
1245
1246 def __repr__(self):
1246 def __repr__(self):
1247 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1247 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1248 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1248 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1249
1249
1250 def set_authenticated(self, authenticated=True):
1250 def set_authenticated(self, authenticated=True):
1251 if self.user_id != self.anonymous_user.user_id:
1251 if self.user_id != self.anonymous_user.user_id:
1252 self.is_authenticated = authenticated
1252 self.is_authenticated = authenticated
1253
1253
1254 def get_cookie_store(self):
1254 def get_cookie_store(self):
1255 return {
1255 return {
1256 'username': self.username,
1256 'username': self.username,
1257 'password': md5(self.password or ''),
1257 'password': md5(self.password or ''),
1258 'user_id': self.user_id,
1258 'user_id': self.user_id,
1259 'is_authenticated': self.is_authenticated
1259 'is_authenticated': self.is_authenticated
1260 }
1260 }
1261
1261
1262 @classmethod
1262 @classmethod
1263 def from_cookie_store(cls, cookie_store):
1263 def from_cookie_store(cls, cookie_store):
1264 """
1264 """
1265 Creates AuthUser from a cookie store
1265 Creates AuthUser from a cookie store
1266
1266
1267 :param cls:
1267 :param cls:
1268 :param cookie_store:
1268 :param cookie_store:
1269 """
1269 """
1270 user_id = cookie_store.get('user_id')
1270 user_id = cookie_store.get('user_id')
1271 username = cookie_store.get('username')
1271 username = cookie_store.get('username')
1272 api_key = cookie_store.get('api_key')
1272 api_key = cookie_store.get('api_key')
1273 return AuthUser(user_id, api_key, username)
1273 return AuthUser(user_id, api_key, username)
1274
1274
1275 @classmethod
1275 @classmethod
1276 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1276 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1277 _set = set()
1277 _set = set()
1278
1278
1279 if inherit_from_default:
1279 if inherit_from_default:
1280 default_ips = UserIpMap.query().filter(
1280 default_ips = UserIpMap.query().filter(
1281 UserIpMap.user == User.get_default_user(cache=True))
1281 UserIpMap.user == User.get_default_user(cache=True))
1282 if cache:
1282 if cache:
1283 default_ips = default_ips.options(
1283 default_ips = default_ips.options(
1284 FromCache("sql_cache_short", "get_user_ips_default"))
1284 FromCache("sql_cache_short", "get_user_ips_default"))
1285
1285
1286 # populate from default user
1286 # populate from default user
1287 for ip in default_ips:
1287 for ip in default_ips:
1288 try:
1288 try:
1289 _set.add(ip.ip_addr)
1289 _set.add(ip.ip_addr)
1290 except ObjectDeletedError:
1290 except ObjectDeletedError:
1291 # since we use heavy caching sometimes it happens that
1291 # since we use heavy caching sometimes it happens that
1292 # we get deleted objects here, we just skip them
1292 # we get deleted objects here, we just skip them
1293 pass
1293 pass
1294
1294
1295 # NOTE:(marcink) we don't want to load any rules for empty
1295 # NOTE:(marcink) we don't want to load any rules for empty
1296 # user_id which is the case of access of non logged users when anonymous
1296 # user_id which is the case of access of non logged users when anonymous
1297 # access is disabled
1297 # access is disabled
1298 user_ips = []
1298 user_ips = []
1299 if user_id:
1299 if user_id:
1300 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1300 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1301 if cache:
1301 if cache:
1302 user_ips = user_ips.options(
1302 user_ips = user_ips.options(
1303 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1303 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1304
1304
1305 for ip in user_ips:
1305 for ip in user_ips:
1306 try:
1306 try:
1307 _set.add(ip.ip_addr)
1307 _set.add(ip.ip_addr)
1308 except ObjectDeletedError:
1308 except ObjectDeletedError:
1309 # since we use heavy caching sometimes it happens that we get
1309 # since we use heavy caching sometimes it happens that we get
1310 # deleted objects here, we just skip them
1310 # deleted objects here, we just skip them
1311 pass
1311 pass
1312 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1312 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1313
1313
1314
1314
1315 def set_available_permissions(settings):
1315 def set_available_permissions(settings):
1316 """
1316 """
1317 This function will propagate pyramid settings with all available defined
1317 This function will propagate pyramid settings with all available defined
1318 permission given in db. We don't want to check each time from db for new
1318 permission given in db. We don't want to check each time from db for new
1319 permissions since adding a new permission also requires application restart
1319 permissions since adding a new permission also requires application restart
1320 ie. to decorate new views with the newly created permission
1320 ie. to decorate new views with the newly created permission
1321
1321
1322 :param settings: current pyramid registry.settings
1322 :param settings: current pyramid registry.settings
1323
1323
1324 """
1324 """
1325 log.debug('auth: getting information about all available permissions')
1325 log.debug('auth: getting information about all available permissions')
1326 try:
1326 try:
1327 sa = meta.Session
1327 sa = meta.Session
1328 all_perms = sa.query(Permission).all()
1328 all_perms = sa.query(Permission).all()
1329 settings.setdefault('available_permissions',
1329 settings.setdefault('available_permissions',
1330 [x.permission_name for x in all_perms])
1330 [x.permission_name for x in all_perms])
1331 log.debug('auth: set available permissions')
1331 log.debug('auth: set available permissions')
1332 except Exception:
1332 except Exception:
1333 log.exception('Failed to fetch permissions from the database.')
1333 log.exception('Failed to fetch permissions from the database.')
1334 raise
1334 raise
1335
1335
1336
1336
1337 def get_csrf_token(session, force_new=False, save_if_missing=True):
1337 def get_csrf_token(session, force_new=False, save_if_missing=True):
1338 """
1338 """
1339 Return the current authentication token, creating one if one doesn't
1339 Return the current authentication token, creating one if one doesn't
1340 already exist and the save_if_missing flag is present.
1340 already exist and the save_if_missing flag is present.
1341
1341
1342 :param session: pass in the pyramid session, else we use the global ones
1342 :param session: pass in the pyramid session, else we use the global ones
1343 :param force_new: force to re-generate the token and store it in session
1343 :param force_new: force to re-generate the token and store it in session
1344 :param save_if_missing: save the newly generated token if it's missing in
1344 :param save_if_missing: save the newly generated token if it's missing in
1345 session
1345 session
1346 """
1346 """
1347 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1347 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1348 # from pyramid.csrf import get_csrf_token
1348 # from pyramid.csrf import get_csrf_token
1349
1349
1350 if (csrf_token_key not in session and save_if_missing) or force_new:
1350 if (csrf_token_key not in session and save_if_missing) or force_new:
1351 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1351 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1352 session[csrf_token_key] = token
1352 session[csrf_token_key] = token
1353 if hasattr(session, 'save'):
1353 if hasattr(session, 'save'):
1354 session.save()
1354 session.save()
1355 return session.get(csrf_token_key)
1355 return session.get(csrf_token_key)
1356
1356
1357
1357
1358 def get_request(perm_class_instance):
1358 def get_request(perm_class_instance):
1359 from pyramid.threadlocal import get_current_request
1359 from pyramid.threadlocal import get_current_request
1360 pyramid_request = get_current_request()
1360 pyramid_request = get_current_request()
1361 return pyramid_request
1361 return pyramid_request
1362
1362
1363
1363
1364 # CHECK DECORATORS
1364 # CHECK DECORATORS
1365 class CSRFRequired(object):
1365 class CSRFRequired(object):
1366 """
1366 """
1367 Decorator for authenticating a form
1367 Decorator for authenticating a form
1368
1368
1369 This decorator uses an authorization token stored in the client's
1369 This decorator uses an authorization token stored in the client's
1370 session for prevention of certain Cross-site request forgery (CSRF)
1370 session for prevention of certain Cross-site request forgery (CSRF)
1371 attacks (See
1371 attacks (See
1372 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1372 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1373 information).
1373 information).
1374
1374
1375 For use with the ``webhelpers.secure_form`` helper functions.
1375 For use with the ``webhelpers.secure_form`` helper functions.
1376
1376
1377 """
1377 """
1378 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1378 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1379 except_methods=None):
1379 except_methods=None):
1380 self.token = token
1380 self.token = token
1381 self.header = header
1381 self.header = header
1382 self.except_methods = except_methods or []
1382 self.except_methods = except_methods or []
1383
1383
1384 def __call__(self, func):
1384 def __call__(self, func):
1385 return get_cython_compat_decorator(self.__wrapper, func)
1385 return get_cython_compat_decorator(self.__wrapper, func)
1386
1386
1387 def _get_csrf(self, _request):
1387 def _get_csrf(self, _request):
1388 return _request.POST.get(self.token, _request.headers.get(self.header))
1388 return _request.POST.get(self.token, _request.headers.get(self.header))
1389
1389
1390 def check_csrf(self, _request, cur_token):
1390 def check_csrf(self, _request, cur_token):
1391 supplied_token = self._get_csrf(_request)
1391 supplied_token = self._get_csrf(_request)
1392 return supplied_token and supplied_token == cur_token
1392 return supplied_token and supplied_token == cur_token
1393
1393
1394 def _get_request(self):
1394 def _get_request(self):
1395 return get_request(self)
1395 return get_request(self)
1396
1396
1397 def __wrapper(self, func, *fargs, **fkwargs):
1397 def __wrapper(self, func, *fargs, **fkwargs):
1398 request = self._get_request()
1398 request = self._get_request()
1399
1399
1400 if request.method in self.except_methods:
1400 if request.method in self.except_methods:
1401 return func(*fargs, **fkwargs)
1401 return func(*fargs, **fkwargs)
1402
1402
1403 cur_token = get_csrf_token(request.session, save_if_missing=False)
1403 cur_token = get_csrf_token(request.session, save_if_missing=False)
1404 if self.check_csrf(request, cur_token):
1404 if self.check_csrf(request, cur_token):
1405 if request.POST.get(self.token):
1405 if request.POST.get(self.token):
1406 del request.POST[self.token]
1406 del request.POST[self.token]
1407 return func(*fargs, **fkwargs)
1407 return func(*fargs, **fkwargs)
1408 else:
1408 else:
1409 reason = 'token-missing'
1409 reason = 'token-missing'
1410 supplied_token = self._get_csrf(request)
1410 supplied_token = self._get_csrf(request)
1411 if supplied_token and cur_token != supplied_token:
1411 if supplied_token and cur_token != supplied_token:
1412 reason = 'token-mismatch [%s:%s]' % (
1412 reason = 'token-mismatch [%s:%s]' % (
1413 cur_token or ''[:6], supplied_token or ''[:6])
1413 cur_token or ''[:6], supplied_token or ''[:6])
1414
1414
1415 csrf_message = \
1415 csrf_message = \
1416 ("Cross-site request forgery detected, request denied. See "
1416 ("Cross-site request forgery detected, request denied. See "
1417 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1417 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1418 "more information.")
1418 "more information.")
1419 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1419 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1420 'REMOTE_ADDR:%s, HEADERS:%s' % (
1420 'REMOTE_ADDR:%s, HEADERS:%s' % (
1421 request, reason, request.remote_addr, request.headers))
1421 request, reason, request.remote_addr, request.headers))
1422
1422
1423 raise HTTPForbidden(explanation=csrf_message)
1423 raise HTTPForbidden(explanation=csrf_message)
1424
1424
1425
1425
1426 class LoginRequired(object):
1426 class LoginRequired(object):
1427 """
1427 """
1428 Must be logged in to execute this function else
1428 Must be logged in to execute this function else
1429 redirect to login page
1429 redirect to login page
1430
1430
1431 :param api_access: if enabled this checks only for valid auth token
1431 :param api_access: if enabled this checks only for valid auth token
1432 and grants access based on valid token
1432 and grants access based on valid token
1433 """
1433 """
1434 def __init__(self, auth_token_access=None):
1434 def __init__(self, auth_token_access=None):
1435 self.auth_token_access = auth_token_access
1435 self.auth_token_access = auth_token_access
1436
1436
1437 def __call__(self, func):
1437 def __call__(self, func):
1438 return get_cython_compat_decorator(self.__wrapper, func)
1438 return get_cython_compat_decorator(self.__wrapper, func)
1439
1439
1440 def _get_request(self):
1440 def _get_request(self):
1441 return get_request(self)
1441 return get_request(self)
1442
1442
1443 def __wrapper(self, func, *fargs, **fkwargs):
1443 def __wrapper(self, func, *fargs, **fkwargs):
1444 from rhodecode.lib import helpers as h
1444 from rhodecode.lib import helpers as h
1445 cls = fargs[0]
1445 cls = fargs[0]
1446 user = cls._rhodecode_user
1446 user = cls._rhodecode_user
1447 request = self._get_request()
1447 request = self._get_request()
1448 _ = request.translate
1448 _ = request.translate
1449
1449
1450 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1450 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1451 log.debug('Starting login restriction checks for user: %s' % (user,))
1451 log.debug('Starting login restriction checks for user: %s' % (user,))
1452 # check if our IP is allowed
1452 # check if our IP is allowed
1453 ip_access_valid = True
1453 ip_access_valid = True
1454 if not user.ip_allowed:
1454 if not user.ip_allowed:
1455 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1455 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1456 category='warning')
1456 category='warning')
1457 ip_access_valid = False
1457 ip_access_valid = False
1458
1458
1459 # check if we used an APIKEY and it's a valid one
1459 # check if we used an APIKEY and it's a valid one
1460 # defined white-list of controllers which API access will be enabled
1460 # defined white-list of controllers which API access will be enabled
1461 _auth_token = request.GET.get(
1461 _auth_token = request.GET.get(
1462 'auth_token', '') or request.GET.get('api_key', '')
1462 'auth_token', '') or request.GET.get('api_key', '')
1463 auth_token_access_valid = allowed_auth_token_access(
1463 auth_token_access_valid = allowed_auth_token_access(
1464 loc, auth_token=_auth_token)
1464 loc, auth_token=_auth_token)
1465
1465
1466 # explicit controller is enabled or API is in our whitelist
1466 # explicit controller is enabled or API is in our whitelist
1467 if self.auth_token_access or auth_token_access_valid:
1467 if self.auth_token_access or auth_token_access_valid:
1468 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1468 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1469 db_user = user.get_instance()
1469 db_user = user.get_instance()
1470
1470
1471 if db_user:
1471 if db_user:
1472 if self.auth_token_access:
1472 if self.auth_token_access:
1473 roles = self.auth_token_access
1473 roles = self.auth_token_access
1474 else:
1474 else:
1475 roles = [UserApiKeys.ROLE_HTTP]
1475 roles = [UserApiKeys.ROLE_HTTP]
1476 token_match = db_user.authenticate_by_token(
1476 token_match = db_user.authenticate_by_token(
1477 _auth_token, roles=roles)
1477 _auth_token, roles=roles)
1478 else:
1478 else:
1479 log.debug('Unable to fetch db instance for auth user: %s', user)
1479 log.debug('Unable to fetch db instance for auth user: %s', user)
1480 token_match = False
1480 token_match = False
1481
1481
1482 if _auth_token and token_match:
1482 if _auth_token and token_match:
1483 auth_token_access_valid = True
1483 auth_token_access_valid = True
1484 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1484 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1485 else:
1485 else:
1486 auth_token_access_valid = False
1486 auth_token_access_valid = False
1487 if not _auth_token:
1487 if not _auth_token:
1488 log.debug("AUTH TOKEN *NOT* present in request")
1488 log.debug("AUTH TOKEN *NOT* present in request")
1489 else:
1489 else:
1490 log.warning(
1490 log.warning(
1491 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1491 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1492
1492
1493 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1493 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1494 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1494 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1495 else 'AUTH_TOKEN_AUTH'
1495 else 'AUTH_TOKEN_AUTH'
1496
1496
1497 if ip_access_valid and (
1497 if ip_access_valid and (
1498 user.is_authenticated or auth_token_access_valid):
1498 user.is_authenticated or auth_token_access_valid):
1499 log.info(
1499 log.info(
1500 'user %s authenticating with:%s IS authenticated on func %s'
1500 'user %s authenticating with:%s IS authenticated on func %s'
1501 % (user, reason, loc))
1501 % (user, reason, loc))
1502
1502
1503 # update user data to check last activity
1503 # update user data to check last activity
1504 user.update_lastactivity()
1504 user.update_lastactivity()
1505 Session().commit()
1505 Session().commit()
1506 return func(*fargs, **fkwargs)
1506 return func(*fargs, **fkwargs)
1507 else:
1507 else:
1508 log.warning(
1508 log.warning(
1509 'user %s authenticating with:%s NOT authenticated on '
1509 'user %s authenticating with:%s NOT authenticated on '
1510 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1510 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1511 % (user, reason, loc, ip_access_valid,
1511 % (user, reason, loc, ip_access_valid,
1512 auth_token_access_valid))
1512 auth_token_access_valid))
1513 # we preserve the get PARAM
1513 # we preserve the get PARAM
1514 came_from = get_came_from(request)
1514 came_from = get_came_from(request)
1515
1515
1516 log.debug('redirecting to login page with %s' % (came_from,))
1516 log.debug('redirecting to login page with %s' % (came_from,))
1517 raise HTTPFound(
1517 raise HTTPFound(
1518 h.route_path('login', _query={'came_from': came_from}))
1518 h.route_path('login', _query={'came_from': came_from}))
1519
1519
1520
1520
1521 class NotAnonymous(object):
1521 class NotAnonymous(object):
1522 """
1522 """
1523 Must be logged in to execute this function else
1523 Must be logged in to execute this function else
1524 redirect to login page
1524 redirect to login page
1525 """
1525 """
1526
1526
1527 def __call__(self, func):
1527 def __call__(self, func):
1528 return get_cython_compat_decorator(self.__wrapper, func)
1528 return get_cython_compat_decorator(self.__wrapper, func)
1529
1529
1530 def _get_request(self):
1530 def _get_request(self):
1531 return get_request(self)
1531 return get_request(self)
1532
1532
1533 def __wrapper(self, func, *fargs, **fkwargs):
1533 def __wrapper(self, func, *fargs, **fkwargs):
1534 import rhodecode.lib.helpers as h
1534 import rhodecode.lib.helpers as h
1535 cls = fargs[0]
1535 cls = fargs[0]
1536 self.user = cls._rhodecode_user
1536 self.user = cls._rhodecode_user
1537 request = self._get_request()
1537 request = self._get_request()
1538 _ = request.translate
1538 _ = request.translate
1539 log.debug('Checking if user is not anonymous @%s' % cls)
1539 log.debug('Checking if user is not anonymous @%s' % cls)
1540
1540
1541 anonymous = self.user.username == User.DEFAULT_USER
1541 anonymous = self.user.username == User.DEFAULT_USER
1542
1542
1543 if anonymous:
1543 if anonymous:
1544 came_from = get_came_from(request)
1544 came_from = get_came_from(request)
1545 h.flash(_('You need to be a registered user to '
1545 h.flash(_('You need to be a registered user to '
1546 'perform this action'),
1546 'perform this action'),
1547 category='warning')
1547 category='warning')
1548 raise HTTPFound(
1548 raise HTTPFound(
1549 h.route_path('login', _query={'came_from': came_from}))
1549 h.route_path('login', _query={'came_from': came_from}))
1550 else:
1550 else:
1551 return func(*fargs, **fkwargs)
1551 return func(*fargs, **fkwargs)
1552
1552
1553
1553
1554 class PermsDecorator(object):
1554 class PermsDecorator(object):
1555 """
1555 """
1556 Base class for controller decorators, we extract the current user from
1556 Base class for controller decorators, we extract the current user from
1557 the class itself, which has it stored in base controllers
1557 the class itself, which has it stored in base controllers
1558 """
1558 """
1559
1559
1560 def __init__(self, *required_perms):
1560 def __init__(self, *required_perms):
1561 self.required_perms = set(required_perms)
1561 self.required_perms = set(required_perms)
1562
1562
1563 def __call__(self, func):
1563 def __call__(self, func):
1564 return get_cython_compat_decorator(self.__wrapper, func)
1564 return get_cython_compat_decorator(self.__wrapper, func)
1565
1565
1566 def _get_request(self):
1566 def _get_request(self):
1567 return get_request(self)
1567 return get_request(self)
1568
1568
1569 def __wrapper(self, func, *fargs, **fkwargs):
1569 def __wrapper(self, func, *fargs, **fkwargs):
1570 import rhodecode.lib.helpers as h
1570 import rhodecode.lib.helpers as h
1571 cls = fargs[0]
1571 cls = fargs[0]
1572 _user = cls._rhodecode_user
1572 _user = cls._rhodecode_user
1573 request = self._get_request()
1573 request = self._get_request()
1574 _ = request.translate
1574 _ = request.translate
1575
1575
1576 log.debug('checking %s permissions %s for %s %s',
1576 log.debug('checking %s permissions %s for %s %s',
1577 self.__class__.__name__, self.required_perms, cls, _user)
1577 self.__class__.__name__, self.required_perms, cls, _user)
1578
1578
1579 if self.check_permissions(_user):
1579 if self.check_permissions(_user):
1580 log.debug('Permission granted for %s %s', cls, _user)
1580 log.debug('Permission granted for %s %s', cls, _user)
1581 return func(*fargs, **fkwargs)
1581 return func(*fargs, **fkwargs)
1582
1582
1583 else:
1583 else:
1584 log.debug('Permission denied for %s %s', cls, _user)
1584 log.debug('Permission denied for %s %s', cls, _user)
1585 anonymous = _user.username == User.DEFAULT_USER
1585 anonymous = _user.username == User.DEFAULT_USER
1586
1586
1587 if anonymous:
1587 if anonymous:
1588 came_from = get_came_from(self._get_request())
1588 came_from = get_came_from(self._get_request())
1589 h.flash(_('You need to be signed in to view this page'),
1589 h.flash(_('You need to be signed in to view this page'),
1590 category='warning')
1590 category='warning')
1591 raise HTTPFound(
1591 raise HTTPFound(
1592 h.route_path('login', _query={'came_from': came_from}))
1592 h.route_path('login', _query={'came_from': came_from}))
1593
1593
1594 else:
1594 else:
1595 # redirect with 404 to prevent resource discovery
1595 # redirect with 404 to prevent resource discovery
1596 raise HTTPNotFound()
1596 raise HTTPNotFound()
1597
1597
1598 def check_permissions(self, user):
1598 def check_permissions(self, user):
1599 """Dummy function for overriding"""
1599 """Dummy function for overriding"""
1600 raise NotImplementedError(
1600 raise NotImplementedError(
1601 'You have to write this function in child class')
1601 'You have to write this function in child class')
1602
1602
1603
1603
1604 class HasPermissionAllDecorator(PermsDecorator):
1604 class HasPermissionAllDecorator(PermsDecorator):
1605 """
1605 """
1606 Checks for access permission for all given predicates. All of them
1606 Checks for access permission for all given predicates. All of them
1607 have to be meet in order to fulfill the request
1607 have to be meet in order to fulfill the request
1608 """
1608 """
1609
1609
1610 def check_permissions(self, user):
1610 def check_permissions(self, user):
1611 perms = user.permissions_with_scope({})
1611 perms = user.permissions_with_scope({})
1612 if self.required_perms.issubset(perms['global']):
1612 if self.required_perms.issubset(perms['global']):
1613 return True
1613 return True
1614 return False
1614 return False
1615
1615
1616
1616
1617 class HasPermissionAnyDecorator(PermsDecorator):
1617 class HasPermissionAnyDecorator(PermsDecorator):
1618 """
1618 """
1619 Checks for access permission for any of given predicates. In order to
1619 Checks for access permission for any of given predicates. In order to
1620 fulfill the request any of predicates must be meet
1620 fulfill the request any of predicates must be meet
1621 """
1621 """
1622
1622
1623 def check_permissions(self, user):
1623 def check_permissions(self, user):
1624 perms = user.permissions_with_scope({})
1624 perms = user.permissions_with_scope({})
1625 if self.required_perms.intersection(perms['global']):
1625 if self.required_perms.intersection(perms['global']):
1626 return True
1626 return True
1627 return False
1627 return False
1628
1628
1629
1629
1630 class HasRepoPermissionAllDecorator(PermsDecorator):
1630 class HasRepoPermissionAllDecorator(PermsDecorator):
1631 """
1631 """
1632 Checks for access permission for all given predicates for specific
1632 Checks for access permission for all given predicates for specific
1633 repository. All of them have to be meet in order to fulfill the request
1633 repository. All of them have to be meet in order to fulfill the request
1634 """
1634 """
1635 def _get_repo_name(self):
1635 def _get_repo_name(self):
1636 _request = self._get_request()
1636 _request = self._get_request()
1637 return get_repo_slug(_request)
1637 return get_repo_slug(_request)
1638
1638
1639 def check_permissions(self, user):
1639 def check_permissions(self, user):
1640 perms = user.permissions
1640 perms = user.permissions
1641 repo_name = self._get_repo_name()
1641 repo_name = self._get_repo_name()
1642
1642
1643 try:
1643 try:
1644 user_perms = {perms['repositories'][repo_name]}
1644 user_perms = {perms['repositories'][repo_name]}
1645 except KeyError:
1645 except KeyError:
1646 log.debug('cannot locate repo with name: `%s` in permissions defs',
1646 log.debug('cannot locate repo with name: `%s` in permissions defs',
1647 repo_name)
1647 repo_name)
1648 return False
1648 return False
1649
1649
1650 log.debug('checking `%s` permissions for repo `%s`',
1650 log.debug('checking `%s` permissions for repo `%s`',
1651 user_perms, repo_name)
1651 user_perms, repo_name)
1652 if self.required_perms.issubset(user_perms):
1652 if self.required_perms.issubset(user_perms):
1653 return True
1653 return True
1654 return False
1654 return False
1655
1655
1656
1656
1657 class HasRepoPermissionAnyDecorator(PermsDecorator):
1657 class HasRepoPermissionAnyDecorator(PermsDecorator):
1658 """
1658 """
1659 Checks for access permission for any of given predicates for specific
1659 Checks for access permission for any of given predicates for specific
1660 repository. In order to fulfill the request any of predicates must be meet
1660 repository. In order to fulfill the request any of predicates must be meet
1661 """
1661 """
1662 def _get_repo_name(self):
1662 def _get_repo_name(self):
1663 _request = self._get_request()
1663 _request = self._get_request()
1664 return get_repo_slug(_request)
1664 return get_repo_slug(_request)
1665
1665
1666 def check_permissions(self, user):
1666 def check_permissions(self, user):
1667 perms = user.permissions
1667 perms = user.permissions
1668 repo_name = self._get_repo_name()
1668 repo_name = self._get_repo_name()
1669
1669
1670 try:
1670 try:
1671 user_perms = {perms['repositories'][repo_name]}
1671 user_perms = {perms['repositories'][repo_name]}
1672 except KeyError:
1672 except KeyError:
1673 log.debug(
1673 log.debug(
1674 'cannot locate repo with name: `%s` in permissions defs',
1674 'cannot locate repo with name: `%s` in permissions defs',
1675 repo_name)
1675 repo_name)
1676 return False
1676 return False
1677
1677
1678 log.debug('checking `%s` permissions for repo `%s`',
1678 log.debug('checking `%s` permissions for repo `%s`',
1679 user_perms, repo_name)
1679 user_perms, repo_name)
1680 if self.required_perms.intersection(user_perms):
1680 if self.required_perms.intersection(user_perms):
1681 return True
1681 return True
1682 return False
1682 return False
1683
1683
1684
1684
1685 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1685 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1686 """
1686 """
1687 Checks for access permission for all given predicates for specific
1687 Checks for access permission for all given predicates for specific
1688 repository group. All of them have to be meet in order to
1688 repository group. All of them have to be meet in order to
1689 fulfill the request
1689 fulfill the request
1690 """
1690 """
1691 def _get_repo_group_name(self):
1691 def _get_repo_group_name(self):
1692 _request = self._get_request()
1692 _request = self._get_request()
1693 return get_repo_group_slug(_request)
1693 return get_repo_group_slug(_request)
1694
1694
1695 def check_permissions(self, user):
1695 def check_permissions(self, user):
1696 perms = user.permissions
1696 perms = user.permissions
1697 group_name = self._get_repo_group_name()
1697 group_name = self._get_repo_group_name()
1698 try:
1698 try:
1699 user_perms = {perms['repositories_groups'][group_name]}
1699 user_perms = {perms['repositories_groups'][group_name]}
1700 except KeyError:
1700 except KeyError:
1701 log.debug(
1701 log.debug(
1702 'cannot locate repo group with name: `%s` in permissions defs',
1702 'cannot locate repo group with name: `%s` in permissions defs',
1703 group_name)
1703 group_name)
1704 return False
1704 return False
1705
1705
1706 log.debug('checking `%s` permissions for repo group `%s`',
1706 log.debug('checking `%s` permissions for repo group `%s`',
1707 user_perms, group_name)
1707 user_perms, group_name)
1708 if self.required_perms.issubset(user_perms):
1708 if self.required_perms.issubset(user_perms):
1709 return True
1709 return True
1710 return False
1710 return False
1711
1711
1712
1712
1713 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1713 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1714 """
1714 """
1715 Checks for access permission for any of given predicates for specific
1715 Checks for access permission for any of given predicates for specific
1716 repository group. In order to fulfill the request any
1716 repository group. In order to fulfill the request any
1717 of predicates must be met
1717 of predicates must be met
1718 """
1718 """
1719 def _get_repo_group_name(self):
1719 def _get_repo_group_name(self):
1720 _request = self._get_request()
1720 _request = self._get_request()
1721 return get_repo_group_slug(_request)
1721 return get_repo_group_slug(_request)
1722
1722
1723 def check_permissions(self, user):
1723 def check_permissions(self, user):
1724 perms = user.permissions
1724 perms = user.permissions
1725 group_name = self._get_repo_group_name()
1725 group_name = self._get_repo_group_name()
1726
1726
1727 try:
1727 try:
1728 user_perms = {perms['repositories_groups'][group_name]}
1728 user_perms = {perms['repositories_groups'][group_name]}
1729 except KeyError:
1729 except KeyError:
1730 log.debug(
1730 log.debug(
1731 'cannot locate repo group with name: `%s` in permissions defs',
1731 'cannot locate repo group with name: `%s` in permissions defs',
1732 group_name)
1732 group_name)
1733 return False
1733 return False
1734
1734
1735 log.debug('checking `%s` permissions for repo group `%s`',
1735 log.debug('checking `%s` permissions for repo group `%s`',
1736 user_perms, group_name)
1736 user_perms, group_name)
1737 if self.required_perms.intersection(user_perms):
1737 if self.required_perms.intersection(user_perms):
1738 return True
1738 return True
1739 return False
1739 return False
1740
1740
1741
1741
1742 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1742 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1743 """
1743 """
1744 Checks for access permission for all given predicates for specific
1744 Checks for access permission for all given predicates for specific
1745 user group. All of them have to be meet in order to fulfill the request
1745 user group. All of them have to be meet in order to fulfill the request
1746 """
1746 """
1747 def _get_user_group_name(self):
1747 def _get_user_group_name(self):
1748 _request = self._get_request()
1748 _request = self._get_request()
1749 return get_user_group_slug(_request)
1749 return get_user_group_slug(_request)
1750
1750
1751 def check_permissions(self, user):
1751 def check_permissions(self, user):
1752 perms = user.permissions
1752 perms = user.permissions
1753 group_name = self._get_user_group_name()
1753 group_name = self._get_user_group_name()
1754 try:
1754 try:
1755 user_perms = {perms['user_groups'][group_name]}
1755 user_perms = {perms['user_groups'][group_name]}
1756 except KeyError:
1756 except KeyError:
1757 return False
1757 return False
1758
1758
1759 if self.required_perms.issubset(user_perms):
1759 if self.required_perms.issubset(user_perms):
1760 return True
1760 return True
1761 return False
1761 return False
1762
1762
1763
1763
1764 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1764 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1765 """
1765 """
1766 Checks for access permission for any of given predicates for specific
1766 Checks for access permission for any of given predicates for specific
1767 user group. In order to fulfill the request any of predicates must be meet
1767 user group. In order to fulfill the request any of predicates must be meet
1768 """
1768 """
1769 def _get_user_group_name(self):
1769 def _get_user_group_name(self):
1770 _request = self._get_request()
1770 _request = self._get_request()
1771 return get_user_group_slug(_request)
1771 return get_user_group_slug(_request)
1772
1772
1773 def check_permissions(self, user):
1773 def check_permissions(self, user):
1774 perms = user.permissions
1774 perms = user.permissions
1775 group_name = self._get_user_group_name()
1775 group_name = self._get_user_group_name()
1776 try:
1776 try:
1777 user_perms = {perms['user_groups'][group_name]}
1777 user_perms = {perms['user_groups'][group_name]}
1778 except KeyError:
1778 except KeyError:
1779 return False
1779 return False
1780
1780
1781 if self.required_perms.intersection(user_perms):
1781 if self.required_perms.intersection(user_perms):
1782 return True
1782 return True
1783 return False
1783 return False
1784
1784
1785
1785
1786 # CHECK FUNCTIONS
1786 # CHECK FUNCTIONS
1787 class PermsFunction(object):
1787 class PermsFunction(object):
1788 """Base function for other check functions"""
1788 """Base function for other check functions"""
1789
1789
1790 def __init__(self, *perms):
1790 def __init__(self, *perms):
1791 self.required_perms = set(perms)
1791 self.required_perms = set(perms)
1792 self.repo_name = None
1792 self.repo_name = None
1793 self.repo_group_name = None
1793 self.repo_group_name = None
1794 self.user_group_name = None
1794 self.user_group_name = None
1795
1795
1796 def __bool__(self):
1796 def __bool__(self):
1797 frame = inspect.currentframe()
1797 frame = inspect.currentframe()
1798 stack_trace = traceback.format_stack(frame)
1798 stack_trace = traceback.format_stack(frame)
1799 log.error('Checking bool value on a class instance of perm '
1799 log.error('Checking bool value on a class instance of perm '
1800 'function is not allowed: %s' % ''.join(stack_trace))
1800 'function is not allowed: %s' % ''.join(stack_trace))
1801 # rather than throwing errors, here we always return False so if by
1801 # rather than throwing errors, here we always return False so if by
1802 # accident someone checks truth for just an instance it will always end
1802 # accident someone checks truth for just an instance it will always end
1803 # up in returning False
1803 # up in returning False
1804 return False
1804 return False
1805 __nonzero__ = __bool__
1805 __nonzero__ = __bool__
1806
1806
1807 def __call__(self, check_location='', user=None):
1807 def __call__(self, check_location='', user=None):
1808 if not user:
1808 if not user:
1809 log.debug('Using user attribute from global request')
1809 log.debug('Using user attribute from global request')
1810 # TODO: remove this someday,put as user as attribute here
1810 # TODO: remove this someday,put as user as attribute here
1811 request = self._get_request()
1811 request = self._get_request()
1812 user = request.user
1812 user = request.user
1813
1813
1814 # init auth user if not already given
1814 # init auth user if not already given
1815 if not isinstance(user, AuthUser):
1815 if not isinstance(user, AuthUser):
1816 log.debug('Wrapping user %s into AuthUser', user)
1816 log.debug('Wrapping user %s into AuthUser', user)
1817 user = AuthUser(user.user_id)
1817 user = AuthUser(user.user_id)
1818
1818
1819 cls_name = self.__class__.__name__
1819 cls_name = self.__class__.__name__
1820 check_scope = self._get_check_scope(cls_name)
1820 check_scope = self._get_check_scope(cls_name)
1821 check_location = check_location or 'unspecified location'
1821 check_location = check_location or 'unspecified location'
1822
1822
1823 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1823 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1824 self.required_perms, user, check_scope, check_location)
1824 self.required_perms, user, check_scope, check_location)
1825 if not user:
1825 if not user:
1826 log.warning('Empty user given for permission check')
1826 log.warning('Empty user given for permission check')
1827 return False
1827 return False
1828
1828
1829 if self.check_permissions(user):
1829 if self.check_permissions(user):
1830 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1830 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1831 check_scope, user, check_location)
1831 check_scope, user, check_location)
1832 return True
1832 return True
1833
1833
1834 else:
1834 else:
1835 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1835 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1836 check_scope, user, check_location)
1836 check_scope, user, check_location)
1837 return False
1837 return False
1838
1838
1839 def _get_request(self):
1839 def _get_request(self):
1840 return get_request(self)
1840 return get_request(self)
1841
1841
1842 def _get_check_scope(self, cls_name):
1842 def _get_check_scope(self, cls_name):
1843 return {
1843 return {
1844 'HasPermissionAll': 'GLOBAL',
1844 'HasPermissionAll': 'GLOBAL',
1845 'HasPermissionAny': 'GLOBAL',
1845 'HasPermissionAny': 'GLOBAL',
1846 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1846 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1847 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1847 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1848 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1848 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1849 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1849 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1850 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1850 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1851 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1851 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1852 }.get(cls_name, '?:%s' % cls_name)
1852 }.get(cls_name, '?:%s' % cls_name)
1853
1853
1854 def check_permissions(self, user):
1854 def check_permissions(self, user):
1855 """Dummy function for overriding"""
1855 """Dummy function for overriding"""
1856 raise Exception('You have to write this function in child class')
1856 raise Exception('You have to write this function in child class')
1857
1857
1858
1858
1859 class HasPermissionAll(PermsFunction):
1859 class HasPermissionAll(PermsFunction):
1860 def check_permissions(self, user):
1860 def check_permissions(self, user):
1861 perms = user.permissions_with_scope({})
1861 perms = user.permissions_with_scope({})
1862 if self.required_perms.issubset(perms.get('global')):
1862 if self.required_perms.issubset(perms.get('global')):
1863 return True
1863 return True
1864 return False
1864 return False
1865
1865
1866
1866
1867 class HasPermissionAny(PermsFunction):
1867 class HasPermissionAny(PermsFunction):
1868 def check_permissions(self, user):
1868 def check_permissions(self, user):
1869 perms = user.permissions_with_scope({})
1869 perms = user.permissions_with_scope({})
1870 if self.required_perms.intersection(perms.get('global')):
1870 if self.required_perms.intersection(perms.get('global')):
1871 return True
1871 return True
1872 return False
1872 return False
1873
1873
1874
1874
1875 class HasRepoPermissionAll(PermsFunction):
1875 class HasRepoPermissionAll(PermsFunction):
1876 def __call__(self, repo_name=None, check_location='', user=None):
1876 def __call__(self, repo_name=None, check_location='', user=None):
1877 self.repo_name = repo_name
1877 self.repo_name = repo_name
1878 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1878 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1879
1879
1880 def _get_repo_name(self):
1880 def _get_repo_name(self):
1881 if not self.repo_name:
1881 if not self.repo_name:
1882 _request = self._get_request()
1882 _request = self._get_request()
1883 self.repo_name = get_repo_slug(_request)
1883 self.repo_name = get_repo_slug(_request)
1884 return self.repo_name
1884 return self.repo_name
1885
1885
1886 def check_permissions(self, user):
1886 def check_permissions(self, user):
1887 self.repo_name = self._get_repo_name()
1887 self.repo_name = self._get_repo_name()
1888 perms = user.permissions
1888 perms = user.permissions
1889 try:
1889 try:
1890 user_perms = {perms['repositories'][self.repo_name]}
1890 user_perms = {perms['repositories'][self.repo_name]}
1891 except KeyError:
1891 except KeyError:
1892 return False
1892 return False
1893 if self.required_perms.issubset(user_perms):
1893 if self.required_perms.issubset(user_perms):
1894 return True
1894 return True
1895 return False
1895 return False
1896
1896
1897
1897
1898 class HasRepoPermissionAny(PermsFunction):
1898 class HasRepoPermissionAny(PermsFunction):
1899 def __call__(self, repo_name=None, check_location='', user=None):
1899 def __call__(self, repo_name=None, check_location='', user=None):
1900 self.repo_name = repo_name
1900 self.repo_name = repo_name
1901 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1901 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1902
1902
1903 def _get_repo_name(self):
1903 def _get_repo_name(self):
1904 if not self.repo_name:
1904 if not self.repo_name:
1905 _request = self._get_request()
1905 _request = self._get_request()
1906 self.repo_name = get_repo_slug(_request)
1906 self.repo_name = get_repo_slug(_request)
1907 return self.repo_name
1907 return self.repo_name
1908
1908
1909 def check_permissions(self, user):
1909 def check_permissions(self, user):
1910 self.repo_name = self._get_repo_name()
1910 self.repo_name = self._get_repo_name()
1911 perms = user.permissions
1911 perms = user.permissions
1912 try:
1912 try:
1913 user_perms = {perms['repositories'][self.repo_name]}
1913 user_perms = {perms['repositories'][self.repo_name]}
1914 except KeyError:
1914 except KeyError:
1915 return False
1915 return False
1916 if self.required_perms.intersection(user_perms):
1916 if self.required_perms.intersection(user_perms):
1917 return True
1917 return True
1918 return False
1918 return False
1919
1919
1920
1920
1921 class HasRepoGroupPermissionAny(PermsFunction):
1921 class HasRepoGroupPermissionAny(PermsFunction):
1922 def __call__(self, group_name=None, check_location='', user=None):
1922 def __call__(self, group_name=None, check_location='', user=None):
1923 self.repo_group_name = group_name
1923 self.repo_group_name = group_name
1924 return super(HasRepoGroupPermissionAny, self).__call__(
1924 return super(HasRepoGroupPermissionAny, self).__call__(
1925 check_location, user)
1925 check_location, user)
1926
1926
1927 def check_permissions(self, user):
1927 def check_permissions(self, user):
1928 perms = user.permissions
1928 perms = user.permissions
1929 try:
1929 try:
1930 user_perms = {perms['repositories_groups'][self.repo_group_name]}
1930 user_perms = {perms['repositories_groups'][self.repo_group_name]}
1931 except KeyError:
1931 except KeyError:
1932 return False
1932 return False
1933 if self.required_perms.intersection(user_perms):
1933 if self.required_perms.intersection(user_perms):
1934 return True
1934 return True
1935 return False
1935 return False
1936
1936
1937
1937
1938 class HasRepoGroupPermissionAll(PermsFunction):
1938 class HasRepoGroupPermissionAll(PermsFunction):
1939 def __call__(self, group_name=None, check_location='', user=None):
1939 def __call__(self, group_name=None, check_location='', user=None):
1940 self.repo_group_name = group_name
1940 self.repo_group_name = group_name
1941 return super(HasRepoGroupPermissionAll, self).__call__(
1941 return super(HasRepoGroupPermissionAll, self).__call__(
1942 check_location, user)
1942 check_location, user)
1943
1943
1944 def check_permissions(self, user):
1944 def check_permissions(self, user):
1945 perms = user.permissions
1945 perms = user.permissions
1946 try:
1946 try:
1947 user_perms = {perms['repositories_groups'][self.repo_group_name]}
1947 user_perms = {perms['repositories_groups'][self.repo_group_name]}
1948 except KeyError:
1948 except KeyError:
1949 return False
1949 return False
1950 if self.required_perms.issubset(user_perms):
1950 if self.required_perms.issubset(user_perms):
1951 return True
1951 return True
1952 return False
1952 return False
1953
1953
1954
1954
1955 class HasUserGroupPermissionAny(PermsFunction):
1955 class HasUserGroupPermissionAny(PermsFunction):
1956 def __call__(self, user_group_name=None, check_location='', user=None):
1956 def __call__(self, user_group_name=None, check_location='', user=None):
1957 self.user_group_name = user_group_name
1957 self.user_group_name = user_group_name
1958 return super(HasUserGroupPermissionAny, self).__call__(
1958 return super(HasUserGroupPermissionAny, self).__call__(
1959 check_location, user)
1959 check_location, user)
1960
1960
1961 def check_permissions(self, user):
1961 def check_permissions(self, user):
1962 perms = user.permissions
1962 perms = user.permissions
1963 try:
1963 try:
1964 user_perms = {perms['user_groups'][self.user_group_name]}
1964 user_perms = {perms['user_groups'][self.user_group_name]}
1965 except KeyError:
1965 except KeyError:
1966 return False
1966 return False
1967 if self.required_perms.intersection(user_perms):
1967 if self.required_perms.intersection(user_perms):
1968 return True
1968 return True
1969 return False
1969 return False
1970
1970
1971
1971
1972 class HasUserGroupPermissionAll(PermsFunction):
1972 class HasUserGroupPermissionAll(PermsFunction):
1973 def __call__(self, user_group_name=None, check_location='', user=None):
1973 def __call__(self, user_group_name=None, check_location='', user=None):
1974 self.user_group_name = user_group_name
1974 self.user_group_name = user_group_name
1975 return super(HasUserGroupPermissionAll, self).__call__(
1975 return super(HasUserGroupPermissionAll, self).__call__(
1976 check_location, user)
1976 check_location, user)
1977
1977
1978 def check_permissions(self, user):
1978 def check_permissions(self, user):
1979 perms = user.permissions
1979 perms = user.permissions
1980 try:
1980 try:
1981 user_perms = {perms['user_groups'][self.user_group_name]}
1981 user_perms = {perms['user_groups'][self.user_group_name]}
1982 except KeyError:
1982 except KeyError:
1983 return False
1983 return False
1984 if self.required_perms.issubset(user_perms):
1984 if self.required_perms.issubset(user_perms):
1985 return True
1985 return True
1986 return False
1986 return False
1987
1987
1988
1988
1989 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1989 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1990 class HasPermissionAnyMiddleware(object):
1990 class HasPermissionAnyMiddleware(object):
1991 def __init__(self, *perms):
1991 def __init__(self, *perms):
1992 self.required_perms = set(perms)
1992 self.required_perms = set(perms)
1993
1993
1994 def __call__(self, user, repo_name):
1994 def __call__(self, user, repo_name):
1995 # repo_name MUST be unicode, since we handle keys in permission
1995 # repo_name MUST be unicode, since we handle keys in permission
1996 # dict by unicode
1996 # dict by unicode
1997 repo_name = safe_unicode(repo_name)
1997 repo_name = safe_unicode(repo_name)
1998 user = AuthUser(user.user_id)
1998 user = AuthUser(user.user_id)
1999 log.debug(
1999 log.debug(
2000 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2000 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2001 self.required_perms, user, repo_name)
2001 self.required_perms, user, repo_name)
2002
2002
2003 if self.check_permissions(user, repo_name):
2003 if self.check_permissions(user, repo_name):
2004 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2004 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2005 repo_name, user, 'PermissionMiddleware')
2005 repo_name, user, 'PermissionMiddleware')
2006 return True
2006 return True
2007
2007
2008 else:
2008 else:
2009 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2009 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2010 repo_name, user, 'PermissionMiddleware')
2010 repo_name, user, 'PermissionMiddleware')
2011 return False
2011 return False
2012
2012
2013 def check_permissions(self, user, repo_name):
2013 def check_permissions(self, user, repo_name):
2014 perms = user.permissions_with_scope({'repo_name': repo_name})
2014 perms = user.permissions_with_scope({'repo_name': repo_name})
2015
2015
2016 try:
2016 try:
2017 user_perms = {perms['repositories'][repo_name]}
2017 user_perms = {perms['repositories'][repo_name]}
2018 except Exception:
2018 except Exception:
2019 log.exception('Error while accessing user permissions')
2019 log.exception('Error while accessing user permissions')
2020 return False
2020 return False
2021
2021
2022 if self.required_perms.intersection(user_perms):
2022 if self.required_perms.intersection(user_perms):
2023 return True
2023 return True
2024 return False
2024 return False
2025
2025
2026
2026
2027 # SPECIAL VERSION TO HANDLE API AUTH
2027 # SPECIAL VERSION TO HANDLE API AUTH
2028 class _BaseApiPerm(object):
2028 class _BaseApiPerm(object):
2029 def __init__(self, *perms):
2029 def __init__(self, *perms):
2030 self.required_perms = set(perms)
2030 self.required_perms = set(perms)
2031
2031
2032 def __call__(self, check_location=None, user=None, repo_name=None,
2032 def __call__(self, check_location=None, user=None, repo_name=None,
2033 group_name=None, user_group_name=None):
2033 group_name=None, user_group_name=None):
2034 cls_name = self.__class__.__name__
2034 cls_name = self.__class__.__name__
2035 check_scope = 'global:%s' % (self.required_perms,)
2035 check_scope = 'global:%s' % (self.required_perms,)
2036 if repo_name:
2036 if repo_name:
2037 check_scope += ', repo_name:%s' % (repo_name,)
2037 check_scope += ', repo_name:%s' % (repo_name,)
2038
2038
2039 if group_name:
2039 if group_name:
2040 check_scope += ', repo_group_name:%s' % (group_name,)
2040 check_scope += ', repo_group_name:%s' % (group_name,)
2041
2041
2042 if user_group_name:
2042 if user_group_name:
2043 check_scope += ', user_group_name:%s' % (user_group_name,)
2043 check_scope += ', user_group_name:%s' % (user_group_name,)
2044
2044
2045 log.debug(
2045 log.debug(
2046 'checking cls:%s %s %s @ %s'
2046 'checking cls:%s %s %s @ %s'
2047 % (cls_name, self.required_perms, check_scope, check_location))
2047 % (cls_name, self.required_perms, check_scope, check_location))
2048 if not user:
2048 if not user:
2049 log.debug('Empty User passed into arguments')
2049 log.debug('Empty User passed into arguments')
2050 return False
2050 return False
2051
2051
2052 # process user
2052 # process user
2053 if not isinstance(user, AuthUser):
2053 if not isinstance(user, AuthUser):
2054 user = AuthUser(user.user_id)
2054 user = AuthUser(user.user_id)
2055 if not check_location:
2055 if not check_location:
2056 check_location = 'unspecified'
2056 check_location = 'unspecified'
2057 if self.check_permissions(user.permissions, repo_name, group_name,
2057 if self.check_permissions(user.permissions, repo_name, group_name,
2058 user_group_name):
2058 user_group_name):
2059 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2059 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2060 check_scope, user, check_location)
2060 check_scope, user, check_location)
2061 return True
2061 return True
2062
2062
2063 else:
2063 else:
2064 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2064 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2065 check_scope, user, check_location)
2065 check_scope, user, check_location)
2066 return False
2066 return False
2067
2067
2068 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2068 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2069 user_group_name=None):
2069 user_group_name=None):
2070 """
2070 """
2071 implement in child class should return True if permissions are ok,
2071 implement in child class should return True if permissions are ok,
2072 False otherwise
2072 False otherwise
2073
2073
2074 :param perm_defs: dict with permission definitions
2074 :param perm_defs: dict with permission definitions
2075 :param repo_name: repo name
2075 :param repo_name: repo name
2076 """
2076 """
2077 raise NotImplementedError()
2077 raise NotImplementedError()
2078
2078
2079
2079
2080 class HasPermissionAllApi(_BaseApiPerm):
2080 class HasPermissionAllApi(_BaseApiPerm):
2081 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2081 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2082 user_group_name=None):
2082 user_group_name=None):
2083 if self.required_perms.issubset(perm_defs.get('global')):
2083 if self.required_perms.issubset(perm_defs.get('global')):
2084 return True
2084 return True
2085 return False
2085 return False
2086
2086
2087
2087
2088 class HasPermissionAnyApi(_BaseApiPerm):
2088 class HasPermissionAnyApi(_BaseApiPerm):
2089 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2089 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2090 user_group_name=None):
2090 user_group_name=None):
2091 if self.required_perms.intersection(perm_defs.get('global')):
2091 if self.required_perms.intersection(perm_defs.get('global')):
2092 return True
2092 return True
2093 return False
2093 return False
2094
2094
2095
2095
2096 class HasRepoPermissionAllApi(_BaseApiPerm):
2096 class HasRepoPermissionAllApi(_BaseApiPerm):
2097 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2097 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2098 user_group_name=None):
2098 user_group_name=None):
2099 try:
2099 try:
2100 _user_perms = {perm_defs['repositories'][repo_name]}
2100 _user_perms = {perm_defs['repositories'][repo_name]}
2101 except KeyError:
2101 except KeyError:
2102 log.warning(traceback.format_exc())
2102 log.warning(traceback.format_exc())
2103 return False
2103 return False
2104 if self.required_perms.issubset(_user_perms):
2104 if self.required_perms.issubset(_user_perms):
2105 return True
2105 return True
2106 return False
2106 return False
2107
2107
2108
2108
2109 class HasRepoPermissionAnyApi(_BaseApiPerm):
2109 class HasRepoPermissionAnyApi(_BaseApiPerm):
2110 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2110 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2111 user_group_name=None):
2111 user_group_name=None):
2112 try:
2112 try:
2113 _user_perms = {perm_defs['repositories'][repo_name]}
2113 _user_perms = {perm_defs['repositories'][repo_name]}
2114 except KeyError:
2114 except KeyError:
2115 log.warning(traceback.format_exc())
2115 log.warning(traceback.format_exc())
2116 return False
2116 return False
2117 if self.required_perms.intersection(_user_perms):
2117 if self.required_perms.intersection(_user_perms):
2118 return True
2118 return True
2119 return False
2119 return False
2120
2120
2121
2121
2122 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2122 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2123 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2123 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2124 user_group_name=None):
2124 user_group_name=None):
2125 try:
2125 try:
2126 _user_perms = {perm_defs['repositories_groups'][group_name]}
2126 _user_perms = {perm_defs['repositories_groups'][group_name]}
2127 except KeyError:
2127 except KeyError:
2128 log.warning(traceback.format_exc())
2128 log.warning(traceback.format_exc())
2129 return False
2129 return False
2130 if self.required_perms.intersection(_user_perms):
2130 if self.required_perms.intersection(_user_perms):
2131 return True
2131 return True
2132 return False
2132 return False
2133
2133
2134
2134
2135 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2135 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2136 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2136 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2137 user_group_name=None):
2137 user_group_name=None):
2138 try:
2138 try:
2139 _user_perms = {perm_defs['repositories_groups'][group_name]}
2139 _user_perms = {perm_defs['repositories_groups'][group_name]}
2140 except KeyError:
2140 except KeyError:
2141 log.warning(traceback.format_exc())
2141 log.warning(traceback.format_exc())
2142 return False
2142 return False
2143 if self.required_perms.issubset(_user_perms):
2143 if self.required_perms.issubset(_user_perms):
2144 return True
2144 return True
2145 return False
2145 return False
2146
2146
2147
2147
2148 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2148 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2149 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2149 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2150 user_group_name=None):
2150 user_group_name=None):
2151 try:
2151 try:
2152 _user_perms = {perm_defs['user_groups'][user_group_name]}
2152 _user_perms = {perm_defs['user_groups'][user_group_name]}
2153 except KeyError:
2153 except KeyError:
2154 log.warning(traceback.format_exc())
2154 log.warning(traceback.format_exc())
2155 return False
2155 return False
2156 if self.required_perms.intersection(_user_perms):
2156 if self.required_perms.intersection(_user_perms):
2157 return True
2157 return True
2158 return False
2158 return False
2159
2159
2160
2160
2161 def check_ip_access(source_ip, allowed_ips=None):
2161 def check_ip_access(source_ip, allowed_ips=None):
2162 """
2162 """
2163 Checks if source_ip is a subnet of any of allowed_ips.
2163 Checks if source_ip is a subnet of any of allowed_ips.
2164
2164
2165 :param source_ip:
2165 :param source_ip:
2166 :param allowed_ips: list of allowed ips together with mask
2166 :param allowed_ips: list of allowed ips together with mask
2167 """
2167 """
2168 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2168 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2169 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2169 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2170 if isinstance(allowed_ips, (tuple, list, set)):
2170 if isinstance(allowed_ips, (tuple, list, set)):
2171 for ip in allowed_ips:
2171 for ip in allowed_ips:
2172 ip = safe_unicode(ip)
2172 ip = safe_unicode(ip)
2173 try:
2173 try:
2174 network_address = ipaddress.ip_network(ip, strict=False)
2174 network_address = ipaddress.ip_network(ip, strict=False)
2175 if source_ip_address in network_address:
2175 if source_ip_address in network_address:
2176 log.debug('IP %s is network %s' %
2176 log.debug('IP %s is network %s' %
2177 (source_ip_address, network_address))
2177 (source_ip_address, network_address))
2178 return True
2178 return True
2179 # for any case we cannot determine the IP, don't crash just
2179 # for any case we cannot determine the IP, don't crash just
2180 # skip it and log as error, we want to say forbidden still when
2180 # skip it and log as error, we want to say forbidden still when
2181 # sending bad IP
2181 # sending bad IP
2182 except Exception:
2182 except Exception:
2183 log.error(traceback.format_exc())
2183 log.error(traceback.format_exc())
2184 continue
2184 continue
2185 return False
2185 return False
2186
2186
2187
2187
2188 def get_cython_compat_decorator(wrapper, func):
2188 def get_cython_compat_decorator(wrapper, func):
2189 """
2189 """
2190 Creates a cython compatible decorator. The previously used
2190 Creates a cython compatible decorator. The previously used
2191 decorator.decorator() function seems to be incompatible with cython.
2191 decorator.decorator() function seems to be incompatible with cython.
2192
2192
2193 :param wrapper: __wrapper method of the decorator class
2193 :param wrapper: __wrapper method of the decorator class
2194 :param func: decorated function
2194 :param func: decorated function
2195 """
2195 """
2196 @wraps(func)
2196 @wraps(func)
2197 def local_wrapper(*args, **kwds):
2197 def local_wrapper(*args, **kwds):
2198 return wrapper(func, *args, **kwds)
2198 return wrapper(func, *args, **kwds)
2199 local_wrapper.__wrapped__ = func
2199 local_wrapper.__wrapped__ = func
2200 return local_wrapper
2200 return local_wrapper
2201
2201
2202
2202
@@ -1,546 +1,547 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.authentication.base import VCS_TYPE
38 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.lib import auth, utils2
39 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils2 import (
44 from rhodecode.lib.utils2 import (
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 from rhodecode.model.db import Repository, User, ChangesetComment
46 from rhodecode.model.db import Repository, User, ChangesetComment
47 from rhodecode.model.notification import NotificationModel
47 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 def _filter_proxy(ip):
53 def _filter_proxy(ip):
54 """
54 """
55 Passed in IP addresses in HEADERS can be in a special format of multiple
55 Passed in IP addresses in HEADERS can be in a special format of multiple
56 ips. Those comma separated IPs are passed from various proxies in the
56 ips. Those comma separated IPs are passed from various proxies in the
57 chain of request processing. The left-most being the original client.
57 chain of request processing. The left-most being the original client.
58 We only care about the first IP which came from the org. client.
58 We only care about the first IP which came from the org. client.
59
59
60 :param ip: ip string from headers
60 :param ip: ip string from headers
61 """
61 """
62 if ',' in ip:
62 if ',' in ip:
63 _ips = ip.split(',')
63 _ips = ip.split(',')
64 _first_ip = _ips[0].strip()
64 _first_ip = _ips[0].strip()
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 return _first_ip
66 return _first_ip
67 return ip
67 return ip
68
68
69
69
70 def _filter_port(ip):
70 def _filter_port(ip):
71 """
71 """
72 Removes a port from ip, there are 4 main cases to handle here.
72 Removes a port from ip, there are 4 main cases to handle here.
73 - ipv4 eg. 127.0.0.1
73 - ipv4 eg. 127.0.0.1
74 - ipv6 eg. ::1
74 - ipv6 eg. ::1
75 - ipv4+port eg. 127.0.0.1:8080
75 - ipv4+port eg. 127.0.0.1:8080
76 - ipv6+port eg. [::1]:8080
76 - ipv6+port eg. [::1]:8080
77
77
78 :param ip:
78 :param ip:
79 """
79 """
80 def is_ipv6(ip_addr):
80 def is_ipv6(ip_addr):
81 if hasattr(socket, 'inet_pton'):
81 if hasattr(socket, 'inet_pton'):
82 try:
82 try:
83 socket.inet_pton(socket.AF_INET6, ip_addr)
83 socket.inet_pton(socket.AF_INET6, ip_addr)
84 except socket.error:
84 except socket.error:
85 return False
85 return False
86 else:
86 else:
87 # fallback to ipaddress
87 # fallback to ipaddress
88 try:
88 try:
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 except Exception:
90 except Exception:
91 return False
91 return False
92 return True
92 return True
93
93
94 if ':' not in ip: # must be ipv4 pure ip
94 if ':' not in ip: # must be ipv4 pure ip
95 return ip
95 return ip
96
96
97 if '[' in ip and ']' in ip: # ipv6 with port
97 if '[' in ip and ']' in ip: # ipv6 with port
98 return ip.split(']')[0][1:].lower()
98 return ip.split(']')[0][1:].lower()
99
99
100 # must be ipv6 or ipv4 with port
100 # must be ipv6 or ipv4 with port
101 if is_ipv6(ip):
101 if is_ipv6(ip):
102 return ip
102 return ip
103 else:
103 else:
104 ip, _port = ip.split(':')[:2] # means ipv4+port
104 ip, _port = ip.split(':')[:2] # means ipv4+port
105 return ip
105 return ip
106
106
107
107
108 def get_ip_addr(environ):
108 def get_ip_addr(environ):
109 proxy_key = 'HTTP_X_REAL_IP'
109 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 def_key = 'REMOTE_ADDR'
111 def_key = 'REMOTE_ADDR'
112 _filters = lambda x: _filter_port(_filter_proxy(x))
112 _filters = lambda x: _filter_port(_filter_proxy(x))
113
113
114 ip = environ.get(proxy_key)
114 ip = environ.get(proxy_key)
115 if ip:
115 if ip:
116 return _filters(ip)
116 return _filters(ip)
117
117
118 ip = environ.get(proxy_key2)
118 ip = environ.get(proxy_key2)
119 if ip:
119 if ip:
120 return _filters(ip)
120 return _filters(ip)
121
121
122 ip = environ.get(def_key, '0.0.0.0')
122 ip = environ.get(def_key, '0.0.0.0')
123 return _filters(ip)
123 return _filters(ip)
124
124
125
125
126 def get_server_ip_addr(environ, log_errors=True):
126 def get_server_ip_addr(environ, log_errors=True):
127 hostname = environ.get('SERVER_NAME')
127 hostname = environ.get('SERVER_NAME')
128 try:
128 try:
129 return socket.gethostbyname(hostname)
129 return socket.gethostbyname(hostname)
130 except Exception as e:
130 except Exception as e:
131 if log_errors:
131 if log_errors:
132 # in some cases this lookup is not possible, and we don't want to
132 # in some cases this lookup is not possible, and we don't want to
133 # make it an exception in logs
133 # make it an exception in logs
134 log.exception('Could not retrieve server ip address: %s', e)
134 log.exception('Could not retrieve server ip address: %s', e)
135 return hostname
135 return hostname
136
136
137
137
138 def get_server_port(environ):
138 def get_server_port(environ):
139 return environ.get('SERVER_PORT')
139 return environ.get('SERVER_PORT')
140
140
141
141
142 def get_access_path(environ):
142 def get_access_path(environ):
143 path = environ.get('PATH_INFO')
143 path = environ.get('PATH_INFO')
144 org_req = environ.get('pylons.original_request')
144 org_req = environ.get('pylons.original_request')
145 if org_req:
145 if org_req:
146 path = org_req.environ.get('PATH_INFO')
146 path = org_req.environ.get('PATH_INFO')
147 return path
147 return path
148
148
149
149
150 def get_user_agent(environ):
150 def get_user_agent(environ):
151 return environ.get('HTTP_USER_AGENT')
151 return environ.get('HTTP_USER_AGENT')
152
152
153
153
154 def vcs_operation_context(
154 def vcs_operation_context(
155 environ, repo_name, username, action, scm, check_locking=True,
155 environ, repo_name, username, action, scm, check_locking=True,
156 is_shadow_repo=False):
156 is_shadow_repo=False):
157 """
157 """
158 Generate the context for a vcs operation, e.g. push or pull.
158 Generate the context for a vcs operation, e.g. push or pull.
159
159
160 This context is passed over the layers so that hooks triggered by the
160 This context is passed over the layers so that hooks triggered by the
161 vcs operation know details like the user, the user's IP address etc.
161 vcs operation know details like the user, the user's IP address etc.
162
162
163 :param check_locking: Allows to switch of the computation of the locking
163 :param check_locking: Allows to switch of the computation of the locking
164 data. This serves mainly the need of the simplevcs middleware to be
164 data. This serves mainly the need of the simplevcs middleware to be
165 able to disable this for certain operations.
165 able to disable this for certain operations.
166
166
167 """
167 """
168 # Tri-state value: False: unlock, None: nothing, True: lock
168 # Tri-state value: False: unlock, None: nothing, True: lock
169 make_lock = None
169 make_lock = None
170 locked_by = [None, None, None]
170 locked_by = [None, None, None]
171 is_anonymous = username == User.DEFAULT_USER
171 is_anonymous = username == User.DEFAULT_USER
172 user = User.get_by_username(username)
172 user = User.get_by_username(username)
173 if not is_anonymous and check_locking:
173 if not is_anonymous and check_locking:
174 log.debug('Checking locking on repository "%s"', repo_name)
174 log.debug('Checking locking on repository "%s"', repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
176 make_lock, __, locked_by = repo.get_locking_state(
176 make_lock, __, locked_by = repo.get_locking_state(
177 action, user.user_id)
177 action, user.user_id)
178 user_id = user.user_id
178 user_id = user.user_id
179 settings_model = VcsSettingsModel(repo=repo_name)
179 settings_model = VcsSettingsModel(repo=repo_name)
180 ui_settings = settings_model.get_ui_settings()
180 ui_settings = settings_model.get_ui_settings()
181
181
182 extras = {
182 extras = {
183 'ip': get_ip_addr(environ),
183 'ip': get_ip_addr(environ),
184 'username': username,
184 'username': username,
185 'user_id': user_id,
185 'user_id': user_id,
186 'action': action,
186 'action': action,
187 'repository': repo_name,
187 'repository': repo_name,
188 'scm': scm,
188 'scm': scm,
189 'config': rhodecode.CONFIG['__file__'],
189 'config': rhodecode.CONFIG['__file__'],
190 'make_lock': make_lock,
190 'make_lock': make_lock,
191 'locked_by': locked_by,
191 'locked_by': locked_by,
192 'server_url': utils2.get_server_url(environ),
192 'server_url': utils2.get_server_url(environ),
193 'user_agent': get_user_agent(environ),
193 'user_agent': get_user_agent(environ),
194 'hooks': get_enabled_hook_classes(ui_settings),
194 'hooks': get_enabled_hook_classes(ui_settings),
195 'is_shadow_repo': is_shadow_repo,
195 'is_shadow_repo': is_shadow_repo,
196 }
196 }
197 return extras
197 return extras
198
198
199
199
200 class BasicAuth(AuthBasicAuthenticator):
200 class BasicAuth(AuthBasicAuthenticator):
201
201
202 def __init__(self, realm, authfunc, registry, auth_http_code=None,
202 def __init__(self, realm, authfunc, registry, auth_http_code=None,
203 initial_call_detection=False, acl_repo_name=None):
203 initial_call_detection=False, acl_repo_name=None):
204 self.realm = realm
204 self.realm = realm
205 self.initial_call = initial_call_detection
205 self.initial_call = initial_call_detection
206 self.authfunc = authfunc
206 self.authfunc = authfunc
207 self.registry = registry
207 self.registry = registry
208 self.acl_repo_name = acl_repo_name
208 self.acl_repo_name = acl_repo_name
209 self._rc_auth_http_code = auth_http_code
209 self._rc_auth_http_code = auth_http_code
210
210
211 def _get_response_from_code(self, http_code):
211 def _get_response_from_code(self, http_code):
212 try:
212 try:
213 return get_exception(safe_int(http_code))
213 return get_exception(safe_int(http_code))
214 except Exception:
214 except Exception:
215 log.exception('Failed to fetch response for code %s' % http_code)
215 log.exception('Failed to fetch response for code %s' % http_code)
216 return HTTPForbidden
216 return HTTPForbidden
217
217
218 def get_rc_realm(self):
218 def get_rc_realm(self):
219 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
219 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
220
220
221 def build_authentication(self):
221 def build_authentication(self):
222 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
222 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
223 if self._rc_auth_http_code and not self.initial_call:
223 if self._rc_auth_http_code and not self.initial_call:
224 # return alternative HTTP code if alternative http return code
224 # return alternative HTTP code if alternative http return code
225 # is specified in RhodeCode config, but ONLY if it's not the
225 # is specified in RhodeCode config, but ONLY if it's not the
226 # FIRST call
226 # FIRST call
227 custom_response_klass = self._get_response_from_code(
227 custom_response_klass = self._get_response_from_code(
228 self._rc_auth_http_code)
228 self._rc_auth_http_code)
229 return custom_response_klass(headers=head)
229 return custom_response_klass(headers=head)
230 return HTTPUnauthorized(headers=head)
230 return HTTPUnauthorized(headers=head)
231
231
232 def authenticate(self, environ):
232 def authenticate(self, environ):
233 authorization = AUTHORIZATION(environ)
233 authorization = AUTHORIZATION(environ)
234 if not authorization:
234 if not authorization:
235 return self.build_authentication()
235 return self.build_authentication()
236 (authmeth, auth) = authorization.split(' ', 1)
236 (authmeth, auth) = authorization.split(' ', 1)
237 if 'basic' != authmeth.lower():
237 if 'basic' != authmeth.lower():
238 return self.build_authentication()
238 return self.build_authentication()
239 auth = auth.strip().decode('base64')
239 auth = auth.strip().decode('base64')
240 _parts = auth.split(':', 1)
240 _parts = auth.split(':', 1)
241 if len(_parts) == 2:
241 if len(_parts) == 2:
242 username, password = _parts
242 username, password = _parts
243 auth_data = self.authfunc(
243 auth_data = self.authfunc(
244 username, password, environ, VCS_TYPE,
244 username, password, environ, VCS_TYPE,
245 registry=self.registry, acl_repo_name=self.acl_repo_name)
245 registry=self.registry, acl_repo_name=self.acl_repo_name)
246 if auth_data:
246 if auth_data:
247 return {'username': username, 'auth_data': auth_data}
247 return {'username': username, 'auth_data': auth_data}
248 if username and password:
248 if username and password:
249 # we mark that we actually executed authentication once, at
249 # we mark that we actually executed authentication once, at
250 # that point we can use the alternative auth code
250 # that point we can use the alternative auth code
251 self.initial_call = False
251 self.initial_call = False
252
252
253 return self.build_authentication()
253 return self.build_authentication()
254
254
255 __call__ = authenticate
255 __call__ = authenticate
256
256
257
257
258 def calculate_version_hash(config):
258 def calculate_version_hash(config):
259 return sha1(
259 return sha1(
260 config.get('beaker.session.secret', '') +
260 config.get('beaker.session.secret', '') +
261 rhodecode.__version__)[:8]
261 rhodecode.__version__)[:8]
262
262
263
263
264 def get_current_lang(request):
264 def get_current_lang(request):
265 # NOTE(marcink): remove after pyramid move
265 # NOTE(marcink): remove after pyramid move
266 try:
266 try:
267 return translation.get_lang()[0]
267 return translation.get_lang()[0]
268 except:
268 except:
269 pass
269 pass
270
270
271 return getattr(request, '_LOCALE_', request.locale_name)
271 return getattr(request, '_LOCALE_', request.locale_name)
272
272
273
273
274 def attach_context_attributes(context, request, user_id):
274 def attach_context_attributes(context, request, user_id):
275 """
275 """
276 Attach variables into template context called `c`.
276 Attach variables into template context called `c`.
277 """
277 """
278 config = request.registry.settings
278 config = request.registry.settings
279
279
280
280
281 rc_config = SettingsModel().get_all_settings(cache=True)
281 rc_config = SettingsModel().get_all_settings(cache=True)
282
282
283 context.rhodecode_version = rhodecode.__version__
283 context.rhodecode_version = rhodecode.__version__
284 context.rhodecode_edition = config.get('rhodecode.edition')
284 context.rhodecode_edition = config.get('rhodecode.edition')
285 # unique secret + version does not leak the version but keep consistency
285 # unique secret + version does not leak the version but keep consistency
286 context.rhodecode_version_hash = calculate_version_hash(config)
286 context.rhodecode_version_hash = calculate_version_hash(config)
287
287
288 # Default language set for the incoming request
288 # Default language set for the incoming request
289 context.language = get_current_lang(request)
289 context.language = get_current_lang(request)
290
290
291 # Visual options
291 # Visual options
292 context.visual = AttributeDict({})
292 context.visual = AttributeDict({})
293
293
294 # DB stored Visual Items
294 # DB stored Visual Items
295 context.visual.show_public_icon = str2bool(
295 context.visual.show_public_icon = str2bool(
296 rc_config.get('rhodecode_show_public_icon'))
296 rc_config.get('rhodecode_show_public_icon'))
297 context.visual.show_private_icon = str2bool(
297 context.visual.show_private_icon = str2bool(
298 rc_config.get('rhodecode_show_private_icon'))
298 rc_config.get('rhodecode_show_private_icon'))
299 context.visual.stylify_metatags = str2bool(
299 context.visual.stylify_metatags = str2bool(
300 rc_config.get('rhodecode_stylify_metatags'))
300 rc_config.get('rhodecode_stylify_metatags'))
301 context.visual.dashboard_items = safe_int(
301 context.visual.dashboard_items = safe_int(
302 rc_config.get('rhodecode_dashboard_items', 100))
302 rc_config.get('rhodecode_dashboard_items', 100))
303 context.visual.admin_grid_items = safe_int(
303 context.visual.admin_grid_items = safe_int(
304 rc_config.get('rhodecode_admin_grid_items', 100))
304 rc_config.get('rhodecode_admin_grid_items', 100))
305 context.visual.repository_fields = str2bool(
305 context.visual.repository_fields = str2bool(
306 rc_config.get('rhodecode_repository_fields'))
306 rc_config.get('rhodecode_repository_fields'))
307 context.visual.show_version = str2bool(
307 context.visual.show_version = str2bool(
308 rc_config.get('rhodecode_show_version'))
308 rc_config.get('rhodecode_show_version'))
309 context.visual.use_gravatar = str2bool(
309 context.visual.use_gravatar = str2bool(
310 rc_config.get('rhodecode_use_gravatar'))
310 rc_config.get('rhodecode_use_gravatar'))
311 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
311 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
312 context.visual.default_renderer = rc_config.get(
312 context.visual.default_renderer = rc_config.get(
313 'rhodecode_markup_renderer', 'rst')
313 'rhodecode_markup_renderer', 'rst')
314 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
314 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
315 context.visual.rhodecode_support_url = \
315 context.visual.rhodecode_support_url = \
316 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
316 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
317
317
318 context.visual.affected_files_cut_off = 60
318 context.visual.affected_files_cut_off = 60
319
319
320 context.pre_code = rc_config.get('rhodecode_pre_code')
320 context.pre_code = rc_config.get('rhodecode_pre_code')
321 context.post_code = rc_config.get('rhodecode_post_code')
321 context.post_code = rc_config.get('rhodecode_post_code')
322 context.rhodecode_name = rc_config.get('rhodecode_title')
322 context.rhodecode_name = rc_config.get('rhodecode_title')
323 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
323 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
324 # if we have specified default_encoding in the request, it has more
324 # if we have specified default_encoding in the request, it has more
325 # priority
325 # priority
326 if request.GET.get('default_encoding'):
326 if request.GET.get('default_encoding'):
327 context.default_encodings.insert(0, request.GET.get('default_encoding'))
327 context.default_encodings.insert(0, request.GET.get('default_encoding'))
328 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
328 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
329 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
329 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
330
330
331 # INI stored
331 # INI stored
332 context.labs_active = str2bool(
332 context.labs_active = str2bool(
333 config.get('labs_settings_active', 'false'))
333 config.get('labs_settings_active', 'false'))
334 context.ssh_enabled = str2bool(
334 context.ssh_enabled = str2bool(
335 config.get('ssh.generate_authorized_keyfile', 'false'))
335 config.get('ssh.generate_authorized_keyfile', 'false'))
336
336
337 context.visual.allow_repo_location_change = str2bool(
337 context.visual.allow_repo_location_change = str2bool(
338 config.get('allow_repo_location_change', True))
338 config.get('allow_repo_location_change', True))
339 context.visual.allow_custom_hooks_settings = str2bool(
339 context.visual.allow_custom_hooks_settings = str2bool(
340 config.get('allow_custom_hooks_settings', True))
340 config.get('allow_custom_hooks_settings', True))
341 context.debug_style = str2bool(config.get('debug_style', False))
341 context.debug_style = str2bool(config.get('debug_style', False))
342
342
343 context.rhodecode_instanceid = config.get('instance_id')
343 context.rhodecode_instanceid = config.get('instance_id')
344
344
345 context.visual.cut_off_limit_diff = safe_int(
345 context.visual.cut_off_limit_diff = safe_int(
346 config.get('cut_off_limit_diff'))
346 config.get('cut_off_limit_diff'))
347 context.visual.cut_off_limit_file = safe_int(
347 context.visual.cut_off_limit_file = safe_int(
348 config.get('cut_off_limit_file'))
348 config.get('cut_off_limit_file'))
349
349
350 # AppEnlight
350 # AppEnlight
351 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
351 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
352 context.appenlight_api_public_key = config.get(
352 context.appenlight_api_public_key = config.get(
353 'appenlight.api_public_key', '')
353 'appenlight.api_public_key', '')
354 context.appenlight_server_url = config.get('appenlight.server_url', '')
354 context.appenlight_server_url = config.get('appenlight.server_url', '')
355
355
356 # JS template context
356 # JS template context
357 context.template_context = {
357 context.template_context = {
358 'repo_name': None,
358 'repo_name': None,
359 'repo_type': None,
359 'repo_type': None,
360 'repo_landing_commit': None,
360 'repo_landing_commit': None,
361 'rhodecode_user': {
361 'rhodecode_user': {
362 'username': None,
362 'username': None,
363 'email': None,
363 'email': None,
364 'notification_status': False
364 'notification_status': False
365 },
365 },
366 'visual': {
366 'visual': {
367 'default_renderer': None
367 'default_renderer': None
368 },
368 },
369 'commit_data': {
369 'commit_data': {
370 'commit_id': None
370 'commit_id': None
371 },
371 },
372 'pull_request_data': {'pull_request_id': None},
372 'pull_request_data': {'pull_request_id': None},
373 'timeago': {
373 'timeago': {
374 'refresh_time': 120 * 1000,
374 'refresh_time': 120 * 1000,
375 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
375 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
376 },
376 },
377 'pyramid_dispatch': {
377 'pyramid_dispatch': {
378
378
379 },
379 },
380 'extra': {'plugins': {}}
380 'extra': {'plugins': {}}
381 }
381 }
382 # END CONFIG VARS
382 # END CONFIG VARS
383
383
384 diffmode = 'sideside'
384 diffmode = 'sideside'
385 if request.GET.get('diffmode'):
385 if request.GET.get('diffmode'):
386 if request.GET['diffmode'] == 'unified':
386 if request.GET['diffmode'] == 'unified':
387 diffmode = 'unified'
387 diffmode = 'unified'
388 elif request.session.get('diffmode'):
388 elif request.session.get('diffmode'):
389 diffmode = request.session['diffmode']
389 diffmode = request.session['diffmode']
390
390
391 context.diffmode = diffmode
391 context.diffmode = diffmode
392
392
393 if request.session.get('diffmode') != diffmode:
393 if request.session.get('diffmode') != diffmode:
394 request.session['diffmode'] = diffmode
394 request.session['diffmode'] = diffmode
395
395
396 context.csrf_token = auth.get_csrf_token(session=request.session)
396 context.csrf_token = auth.get_csrf_token(session=request.session)
397 context.backends = rhodecode.BACKENDS.keys()
397 context.backends = rhodecode.BACKENDS.keys()
398 context.backends.sort()
398 context.backends.sort()
399 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
399 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
400
400
401 # web case
401 # web case
402 if hasattr(request, 'user'):
402 if hasattr(request, 'user'):
403 context.auth_user = request.user
403 context.auth_user = request.user
404 context.rhodecode_user = request.user
404 context.rhodecode_user = request.user
405
405
406 # api case
406 # api case
407 if hasattr(request, 'rpc_user'):
407 if hasattr(request, 'rpc_user'):
408 context.auth_user = request.rpc_user
408 context.auth_user = request.rpc_user
409 context.rhodecode_user = request.rpc_user
409 context.rhodecode_user = request.rpc_user
410
410
411 # attach the whole call context to the request
411 # attach the whole call context to the request
412 request.call_context = context
412 request.call_context = context
413
413
414
414
415 def get_auth_user(request):
415 def get_auth_user(request):
416 environ = request.environ
416 environ = request.environ
417 session = request.session
417 session = request.session
418
418
419 ip_addr = get_ip_addr(environ)
419 ip_addr = get_ip_addr(environ)
420 # make sure that we update permissions each time we call controller
420 # make sure that we update permissions each time we call controller
421 _auth_token = (request.GET.get('auth_token', '') or
421 _auth_token = (request.GET.get('auth_token', '') or
422 request.GET.get('api_key', ''))
422 request.GET.get('api_key', ''))
423
423
424 if _auth_token:
424 if _auth_token:
425 # when using API_KEY we assume user exists, and
425 # when using API_KEY we assume user exists, and
426 # doesn't need auth based on cookies.
426 # doesn't need auth based on cookies.
427 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
427 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
428 authenticated = False
428 authenticated = False
429 else:
429 else:
430 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
430 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
431 try:
431 try:
432 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
432 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
433 ip_addr=ip_addr)
433 ip_addr=ip_addr)
434 except UserCreationError as e:
434 except UserCreationError as e:
435 h.flash(e, 'error')
435 h.flash(e, 'error')
436 # container auth or other auth functions that create users
436 # container auth or other auth functions that create users
437 # on the fly can throw this exception signaling that there's
437 # on the fly can throw this exception signaling that there's
438 # issue with user creation, explanation should be provided
438 # issue with user creation, explanation should be provided
439 # in Exception itself. We then create a simple blank
439 # in Exception itself. We then create a simple blank
440 # AuthUser
440 # AuthUser
441 auth_user = AuthUser(ip_addr=ip_addr)
441 auth_user = AuthUser(ip_addr=ip_addr)
442
442
443 # in case someone changes a password for user it triggers session
443 # in case someone changes a password for user it triggers session
444 # flush and forces a re-login
444 # flush and forces a re-login
445 if password_changed(auth_user, session):
445 if password_changed(auth_user, session):
446 session.invalidate()
446 session.invalidate()
447 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
447 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
448 auth_user = AuthUser(ip_addr=ip_addr)
448 auth_user = AuthUser(ip_addr=ip_addr)
449
449
450 authenticated = cookie_store.get('is_authenticated')
450 authenticated = cookie_store.get('is_authenticated')
451
451
452 if not auth_user.is_authenticated and auth_user.is_user_object:
452 if not auth_user.is_authenticated and auth_user.is_user_object:
453 # user is not authenticated and not empty
453 # user is not authenticated and not empty
454 auth_user.set_authenticated(authenticated)
454 auth_user.set_authenticated(authenticated)
455
455
456 return auth_user
456 return auth_user
457
457
458
458
459 def h_filter(s):
459 def h_filter(s):
460 """
460 """
461 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
461 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
462 we wrap this with additional functionality that converts None to empty
462 we wrap this with additional functionality that converts None to empty
463 strings
463 strings
464 """
464 """
465 if s is None:
465 if s is None:
466 return markupsafe.Markup()
466 return markupsafe.Markup()
467 return markupsafe.escape(s)
467 return markupsafe.escape(s)
468
468
469
469
470 def add_events_routes(config):
470 def add_events_routes(config):
471 """
471 """
472 Adds routing that can be used in events. Because some events are triggered
472 Adds routing that can be used in events. Because some events are triggered
473 outside of pyramid context, we need to bootstrap request with some
473 outside of pyramid context, we need to bootstrap request with some
474 routing registered
474 routing registered
475 """
475 """
476
476
477 from rhodecode.apps._base import ADMIN_PREFIX
477 from rhodecode.apps._base import ADMIN_PREFIX
478
478
479 config.add_route(name='home', pattern='/')
479 config.add_route(name='home', pattern='/')
480
480
481 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
481 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
482 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
482 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
483 config.add_route(name='repo_summary', pattern='/{repo_name}')
483 config.add_route(name='repo_summary', pattern='/{repo_name}')
484 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
484 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
485 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
485 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
486
486
487 config.add_route(name='pullrequest_show',
487 config.add_route(name='pullrequest_show',
488 pattern='/{repo_name}/pull-request/{pull_request_id}')
488 pattern='/{repo_name}/pull-request/{pull_request_id}')
489 config.add_route(name='pull_requests_global',
489 config.add_route(name='pull_requests_global',
490 pattern='/pull-request/{pull_request_id}')
490 pattern='/pull-request/{pull_request_id}')
491 config.add_route(name='repo_commit',
491 config.add_route(name='repo_commit',
492 pattern='/{repo_name}/changeset/{commit_id}')
492 pattern='/{repo_name}/changeset/{commit_id}')
493
493
494 config.add_route(name='repo_files',
494 config.add_route(name='repo_files',
495 pattern='/{repo_name}/files/{commit_id}/{f_path}')
495 pattern='/{repo_name}/files/{commit_id}/{f_path}')
496
496
497
497
498 def bootstrap_config(request):
498 def bootstrap_config(request):
499 import pyramid.testing
499 import pyramid.testing
500 registry = pyramid.testing.Registry('RcTestRegistry')
500 registry = pyramid.testing.Registry('RcTestRegistry')
501
501
502 config = pyramid.testing.setUp(registry=registry, request=request)
502 config = pyramid.testing.setUp(registry=registry, request=request)
503
503
504 # allow pyramid lookup in testing
504 # allow pyramid lookup in testing
505 config.include('pyramid_mako')
505 config.include('pyramid_mako')
506 config.include('pyramid_beaker')
506 config.include('pyramid_beaker')
507 config.include('rhodecode.lib.caches')
507 config.include('rhodecode.lib.caches')
508 config.include('rhodecode.lib.rc_cache')
508
509
509 add_events_routes(config)
510 add_events_routes(config)
510
511
511 return config
512 return config
512
513
513
514
514 def bootstrap_request(**kwargs):
515 def bootstrap_request(**kwargs):
515 import pyramid.testing
516 import pyramid.testing
516
517
517 class TestRequest(pyramid.testing.DummyRequest):
518 class TestRequest(pyramid.testing.DummyRequest):
518 application_url = kwargs.pop('application_url', 'http://example.com')
519 application_url = kwargs.pop('application_url', 'http://example.com')
519 host = kwargs.pop('host', 'example.com:80')
520 host = kwargs.pop('host', 'example.com:80')
520 domain = kwargs.pop('domain', 'example.com')
521 domain = kwargs.pop('domain', 'example.com')
521
522
522 def translate(self, msg):
523 def translate(self, msg):
523 return msg
524 return msg
524
525
525 def plularize(self, singular, plural, n):
526 def plularize(self, singular, plural, n):
526 return singular
527 return singular
527
528
528 def get_partial_renderer(self, tmpl_name):
529 def get_partial_renderer(self, tmpl_name):
529
530
530 from rhodecode.lib.partial_renderer import get_partial_renderer
531 from rhodecode.lib.partial_renderer import get_partial_renderer
531 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
532 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
532
533
533 _call_context = {}
534 _call_context = {}
534 @property
535 @property
535 def call_context(self):
536 def call_context(self):
536 return self._call_context
537 return self._call_context
537
538
538 class TestDummySession(pyramid.testing.DummySession):
539 class TestDummySession(pyramid.testing.DummySession):
539 def save(*arg, **kw):
540 def save(*arg, **kw):
540 pass
541 pass
541
542
542 request = TestRequest(**kwargs)
543 request = TestRequest(**kwargs)
543 request.session = TestDummySession()
544 request.session = TestDummySession()
544
545
545 return request
546 return request
546
547
@@ -1,295 +1,295 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2015-2018 RhodeCode GmbH
3 # Copyright (C) 2015-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import functools
20 import functools
21
21
22 import beaker
22 import beaker
23 import logging
23 import logging
24 import threading
24 import threading
25
25
26 from beaker.cache import _cache_decorate, cache_regions, region_invalidate
26 from beaker.cache import _cache_decorate, cache_regions, region_invalidate
27 from sqlalchemy.exc import IntegrityError
27 from sqlalchemy.exc import IntegrityError
28
28
29 from rhodecode.lib.utils import safe_str, sha1
29 from rhodecode.lib.utils import safe_str, sha1
30 from rhodecode.model.db import Session, CacheKey
30 from rhodecode.model.db import Session, CacheKey
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34 FILE_TREE = 'cache_file_tree'
34 FILE_TREE = 'cache_file_tree'
35 FILE_TREE_META = 'cache_file_tree_metadata'
35 FILE_TREE_META = 'cache_file_tree_metadata'
36 FILE_SEARCH_TREE_META = 'cache_file_search_metadata'
36 FILE_SEARCH_TREE_META = 'cache_file_search_metadata'
37 SUMMARY_STATS = 'cache_summary_stats'
37 SUMMARY_STATS = 'cache_summary_stats'
38
38
39 # This list of caches gets purged when invalidation happens
39 # This list of caches gets purged when invalidation happens
40 USED_REPO_CACHES = (FILE_TREE, FILE_SEARCH_TREE_META)
40 USED_REPO_CACHES = (FILE_TREE, FILE_SEARCH_TREE_META)
41
41
42 DEFAULT_CACHE_MANAGER_CONFIG = {
42 DEFAULT_CACHE_MANAGER_CONFIG = {
43 'type': 'memorylru_base',
43 'type': 'memorylru_base',
44 'max_items': 10240,
44 'max_items': 10240,
45 'key_length': 256,
45 'key_length': 256,
46 'enabled': True
46 'enabled': True
47 }
47 }
48
48
49
49
50 def get_default_cache_settings(settings):
50 def get_default_cache_settings(settings):
51 cache_settings = {}
51 cache_settings = {}
52 for key in settings.keys():
52 for key in settings.keys():
53 for prefix in ['beaker.cache.', 'cache.']:
53 for prefix in ['beaker.cache.', 'cache.']:
54 if key.startswith(prefix):
54 if key.startswith(prefix):
55 name = key.split(prefix)[1].strip()
55 name = key.split(prefix)[1].strip()
56 cache_settings[name] = settings[key].strip()
56 cache_settings[name] = settings[key].strip()
57 return cache_settings
57 return cache_settings
58
58
59
59
60 # set cache regions for beaker so celery can utilise it
60 # set cache regions for beaker so celery can utilise it
61 def configure_caches(settings, default_region_settings=None):
61 def configure_caches(settings, default_region_settings=None):
62 cache_settings = {'regions': None}
62 cache_settings = {'regions': None}
63 # main cache settings used as default ...
63 # main cache settings used as default ...
64 cache_settings.update(get_default_cache_settings(settings))
64 cache_settings.update(get_default_cache_settings(settings))
65 default_region_settings = default_region_settings or \
65 default_region_settings = default_region_settings or \
66 {'type': DEFAULT_CACHE_MANAGER_CONFIG['type']}
66 {'type': DEFAULT_CACHE_MANAGER_CONFIG['type']}
67 if cache_settings['regions']:
67 if cache_settings['regions']:
68 for region in cache_settings['regions'].split(','):
68 for region in cache_settings['regions'].split(','):
69 region = region.strip()
69 region = region.strip()
70 region_settings = default_region_settings.copy()
70 region_settings = default_region_settings.copy()
71 for key, value in cache_settings.items():
71 for key, value in cache_settings.items():
72 if key.startswith(region):
72 if key.startswith(region):
73 region_settings[key.split(region + '.')[-1]] = value
73 region_settings[key.split(region + '.')[-1]] = value
74 log.debug('Configuring cache region `%s` with settings %s',
74 log.debug('Configuring cache region `%s` with settings %s',
75 region, region_settings)
75 region, region_settings)
76 configure_cache_region(
76 configure_cache_region(
77 region, region_settings, cache_settings)
77 region, region_settings, cache_settings)
78
78
79
79
80 def configure_cache_region(
80 def configure_cache_region(
81 region_name, region_settings, default_cache_kw, default_expire=60):
81 region_name, region_settings, default_cache_kw, default_expire=60):
82 default_type = default_cache_kw.get('type', 'memory')
82 default_type = default_cache_kw.get('type', 'memory')
83 default_lock_dir = default_cache_kw.get('lock_dir')
83 default_lock_dir = default_cache_kw.get('lock_dir')
84 default_data_dir = default_cache_kw.get('data_dir')
84 default_data_dir = default_cache_kw.get('data_dir')
85
85
86 region_settings['lock_dir'] = region_settings.get('lock_dir', default_lock_dir)
86 region_settings['lock_dir'] = region_settings.get('lock_dir', default_lock_dir)
87 region_settings['data_dir'] = region_settings.get('data_dir', default_data_dir)
87 region_settings['data_dir'] = region_settings.get('data_dir', default_data_dir)
88 region_settings['type'] = region_settings.get('type', default_type)
88 region_settings['type'] = region_settings.get('type', default_type)
89 region_settings['expire'] = int(region_settings.get('expire', default_expire))
89 region_settings['expire'] = int(region_settings.get('expire', default_expire))
90
90
91 beaker.cache.cache_regions[region_name] = region_settings
91 beaker.cache.cache_regions[region_name] = region_settings
92
92
93
93
94 def get_cache_manager(region_name, cache_name, custom_ttl=None):
94 def get_cache_manager(region_name, cache_name, custom_ttl=None):
95 """
95 """
96 Creates a Beaker cache manager. Such instance can be used like that::
96 Creates a Beaker cache manager. Such instance can be used like that::
97
97
98 _namespace = caches.get_repo_namespace_key(caches.XXX, repo_name)
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 _cache_key = caches.compute_key_from_params(repo_name, commit.raw_id)
100 _cache_key = caches.compute_key_from_params(repo_name, commit.raw_id)
101 def heavy_compute():
101 def heavy_compute():
102 ...
102 ...
103 result = cache_manager.get(_cache_key, createfunc=heavy_compute)
103 result = cache_manager.get(_cache_key, createfunc=heavy_compute)
104
104
105 :param region_name: region from ini file
105 :param region_name: region from ini file
106 :param cache_name: custom cache name, usually prefix+repo_name. eg
106 :param cache_name: custom cache name, usually prefix+repo_name. eg
107 file_switcher_repo1
107 file_switcher_repo1
108 :param custom_ttl: override .ini file timeout on this cache
108 :param custom_ttl: override .ini file timeout on this cache
109 :return: instance of cache manager
109 :return: instance of cache manager
110 """
110 """
111
111
112 cache_config = cache_regions.get(region_name, DEFAULT_CACHE_MANAGER_CONFIG)
112 cache_config = cache_regions.get(region_name, DEFAULT_CACHE_MANAGER_CONFIG)
113 if custom_ttl:
113 if custom_ttl:
114 log.debug('Updating region %s with custom ttl: %s',
114 log.debug('Updating region %s with custom ttl: %s',
115 region_name, custom_ttl)
115 region_name, custom_ttl)
116 cache_config.update({'expire': custom_ttl})
116 cache_config.update({'expire': custom_ttl})
117
117
118 return beaker.cache.Cache._get_cache(cache_name, cache_config)
118 return beaker.cache.Cache._get_cache(cache_name, cache_config)
119
119
120
120
121 def clear_cache_manager(cache_manager):
121 def clear_cache_manager(cache_manager):
122 """
122 """
123 namespace = 'foobar'
123 namespace = 'foobar'
124 cache_manager = get_cache_manager('repo_cache_long', namespace)
124 cache_manager = get_cache_manager('some_namespace_name', namespace)
125 clear_cache_manager(cache_manager)
125 clear_cache_manager(cache_manager)
126 """
126 """
127
127
128 log.debug('Clearing all values for cache manager %s', cache_manager)
128 log.debug('Clearing all values for cache manager %s', cache_manager)
129 cache_manager.clear()
129 cache_manager.clear()
130
130
131
131
132 def clear_repo_caches(repo_name):
132 def clear_repo_caches(repo_name):
133 # invalidate cache manager for this repo
133 # invalidate cache manager for this repo
134 for prefix in USED_REPO_CACHES:
134 for prefix in USED_REPO_CACHES:
135 namespace = get_repo_namespace_key(prefix, repo_name)
135 namespace = get_repo_namespace_key(prefix, repo_name)
136 cache_manager = get_cache_manager('repo_cache_long', namespace)
136 cache_manager = get_cache_manager('repo_cache_long', namespace)
137 clear_cache_manager(cache_manager)
137 clear_cache_manager(cache_manager)
138
138
139
139
140 def compute_key_from_params(*args):
140 def compute_key_from_params(*args):
141 """
141 """
142 Helper to compute key from given params to be used in cache manager
142 Helper to compute key from given params to be used in cache manager
143 """
143 """
144 return sha1("_".join(map(safe_str, args)))
144 return sha1("_".join(map(safe_str, args)))
145
145
146
146
147 def get_repo_namespace_key(prefix, repo_name):
147 def get_repo_namespace_key(prefix, repo_name):
148 return '{0}_{1}'.format(prefix, compute_key_from_params(repo_name))
148 return '{0}_{1}'.format(prefix, compute_key_from_params(repo_name))
149
149
150
150
151 def conditional_cache(region, cache_namespace, condition, func):
151 def conditional_cache(region, cache_namespace, condition, func):
152 """
152 """
153 Conditional caching function use like::
153 Conditional caching function use like::
154 def _c(arg):
154 def _c(arg):
155 # heavy computation function
155 # heavy computation function
156 return data
156 return data
157
157
158 # depending on the condition the compute is wrapped in cache or not
158 # depending on the condition the compute is wrapped in cache or not
159 compute = conditional_cache('short_term', 'cache_namespace_id',
159 compute = conditional_cache('short_term', 'cache_namespace_id',
160 condition=True, func=func)
160 condition=True, func=func)
161 return compute(arg)
161 return compute(arg)
162
162
163 :param region: name of cache region
163 :param region: name of cache region
164 :param cache_namespace: cache namespace
164 :param cache_namespace: cache namespace
165 :param condition: condition for cache to be triggered, and
165 :param condition: condition for cache to be triggered, and
166 return data cached
166 return data cached
167 :param func: wrapped heavy function to compute
167 :param func: wrapped heavy function to compute
168
168
169 """
169 """
170 wrapped = func
170 wrapped = func
171 if condition:
171 if condition:
172 log.debug('conditional_cache: True, wrapping call of '
172 log.debug('conditional_cache: True, wrapping call of '
173 'func: %s into %s region cache', region, func)
173 'func: %s into %s region cache', region, func)
174
174
175 def _cache_wrap(region_name, cache_namespace):
175 def _cache_wrap(region_name, cache_namespace):
176 """Return a caching wrapper"""
176 """Return a caching wrapper"""
177
177
178 def decorate(func):
178 def decorate(func):
179 @functools.wraps(func)
179 @functools.wraps(func)
180 def cached(*args, **kwargs):
180 def cached(*args, **kwargs):
181 if kwargs:
181 if kwargs:
182 raise AttributeError(
182 raise AttributeError(
183 'Usage of kwargs is not allowed. '
183 'Usage of kwargs is not allowed. '
184 'Use only positional arguments in wrapped function')
184 'Use only positional arguments in wrapped function')
185 manager = get_cache_manager(region_name, cache_namespace)
185 manager = get_cache_manager(region_name, cache_namespace)
186 cache_key = compute_key_from_params(*args)
186 cache_key = compute_key_from_params(*args)
187
187
188 def go():
188 def go():
189 return func(*args, **kwargs)
189 return func(*args, **kwargs)
190
190
191 # save org function name
191 # save org function name
192 go.__name__ = '_cached_%s' % (func.__name__,)
192 go.__name__ = '_cached_%s' % (func.__name__,)
193
193
194 return manager.get(cache_key, createfunc=go)
194 return manager.get(cache_key, createfunc=go)
195 return cached
195 return cached
196
196
197 return decorate
197 return decorate
198
198
199 cached_region = _cache_wrap(region, cache_namespace)
199 cached_region = _cache_wrap(region, cache_namespace)
200 wrapped = cached_region(func)
200 wrapped = cached_region(func)
201
201
202 return wrapped
202 return wrapped
203
203
204
204
205 class ActiveRegionCache(object):
205 class ActiveRegionCache(object):
206 def __init__(self, context):
206 def __init__(self, context):
207 self.context = context
207 self.context = context
208
208
209 def invalidate(self, *args, **kwargs):
209 def invalidate(self, *args, **kwargs):
210 return False
210 return False
211
211
212 def compute(self):
212 def compute(self):
213 log.debug('Context cache: getting obj %s from cache', self.context)
213 log.debug('Context cache: getting obj %s from cache', self.context)
214 return self.context.compute_func(self.context.cache_key)
214 return self.context.compute_func(self.context.cache_key)
215
215
216
216
217 class FreshRegionCache(ActiveRegionCache):
217 class FreshRegionCache(ActiveRegionCache):
218 def invalidate(self):
218 def invalidate(self):
219 log.debug('Context cache: invalidating cache for %s', self.context)
219 log.debug('Context cache: invalidating cache for %s', self.context)
220 region_invalidate(
220 region_invalidate(
221 self.context.compute_func, None, self.context.cache_key)
221 self.context.compute_func, None, self.context.cache_key)
222 return True
222 return True
223
223
224
224
225 class InvalidationContext(object):
225 class InvalidationContext(object):
226 def __repr__(self):
226 def __repr__(self):
227 return '<InvalidationContext:{}[{}]>'.format(
227 return '<InvalidationContext:{}[{}]>'.format(
228 safe_str(self.repo_name), safe_str(self.cache_type))
228 safe_str(self.repo_name), safe_str(self.cache_type))
229
229
230 def __init__(self, compute_func, repo_name, cache_type,
230 def __init__(self, compute_func, repo_name, cache_type,
231 raise_exception=False, thread_scoped=False):
231 raise_exception=False, thread_scoped=False):
232 self.compute_func = compute_func
232 self.compute_func = compute_func
233 self.repo_name = repo_name
233 self.repo_name = repo_name
234 self.cache_type = cache_type
234 self.cache_type = cache_type
235 self.cache_key = compute_key_from_params(
235 self.cache_key = compute_key_from_params(
236 repo_name, cache_type)
236 repo_name, cache_type)
237 self.raise_exception = raise_exception
237 self.raise_exception = raise_exception
238
238
239 # Append the thread id to the cache key if this invalidation context
239 # Append the thread id to the cache key if this invalidation context
240 # should be scoped to the current thread.
240 # should be scoped to the current thread.
241 if thread_scoped:
241 if thread_scoped:
242 thread_id = threading.current_thread().ident
242 thread_id = threading.current_thread().ident
243 self.cache_key = '{cache_key}_{thread_id}'.format(
243 self.cache_key = '{cache_key}_{thread_id}'.format(
244 cache_key=self.cache_key, thread_id=thread_id)
244 cache_key=self.cache_key, thread_id=thread_id)
245
245
246 def get_cache_obj(self):
246 def get_cache_obj(self):
247 cache_key = CacheKey.get_cache_key(
247 cache_key = CacheKey.get_cache_key(
248 self.repo_name, self.cache_type)
248 self.repo_name, self.cache_type)
249 cache_obj = CacheKey.get_active_cache(cache_key)
249 cache_obj = CacheKey.get_active_cache(cache_key)
250 if not cache_obj:
250 if not cache_obj:
251 cache_obj = CacheKey(cache_key, self.repo_name)
251 cache_obj = CacheKey(cache_key, self.repo_name)
252 return cache_obj
252 return cache_obj
253
253
254 def __enter__(self):
254 def __enter__(self):
255 """
255 """
256 Test if current object is valid, and return CacheRegion function
256 Test if current object is valid, and return CacheRegion function
257 that does invalidation and calculation
257 that does invalidation and calculation
258 """
258 """
259
259
260 self.cache_obj = self.get_cache_obj()
260 self.cache_obj = self.get_cache_obj()
261 if self.cache_obj.cache_active:
261 if self.cache_obj.cache_active:
262 # means our cache obj is existing and marked as it's
262 # means our cache obj is existing and marked as it's
263 # cache is not outdated, we return BaseInvalidator
263 # cache is not outdated, we return BaseInvalidator
264 self.skip_cache_active_change = True
264 self.skip_cache_active_change = True
265 return ActiveRegionCache(self)
265 return ActiveRegionCache(self)
266
266
267 # the key is either not existing or set to False, we return
267 # the key is either not existing or set to False, we return
268 # the real invalidator which re-computes value. We additionally set
268 # the real invalidator which re-computes value. We additionally set
269 # the flag to actually update the Database objects
269 # the flag to actually update the Database objects
270 self.skip_cache_active_change = False
270 self.skip_cache_active_change = False
271 return FreshRegionCache(self)
271 return FreshRegionCache(self)
272
272
273 def __exit__(self, exc_type, exc_val, exc_tb):
273 def __exit__(self, exc_type, exc_val, exc_tb):
274
274
275 if self.skip_cache_active_change:
275 if self.skip_cache_active_change:
276 return
276 return
277
277
278 try:
278 try:
279 self.cache_obj.cache_active = True
279 self.cache_obj.cache_active = True
280 Session().add(self.cache_obj)
280 Session().add(self.cache_obj)
281 Session().commit()
281 Session().commit()
282 except IntegrityError:
282 except IntegrityError:
283 # if we catch integrity error, it means we inserted this object
283 # if we catch integrity error, it means we inserted this object
284 # assumption is that's really an edge race-condition case and
284 # assumption is that's really an edge race-condition case and
285 # it's safe is to skip it
285 # it's safe is to skip it
286 Session().rollback()
286 Session().rollback()
287 except Exception:
287 except Exception:
288 log.exception('Failed to commit on cache key update')
288 log.exception('Failed to commit on cache key update')
289 Session().rollback()
289 Session().rollback()
290 if self.raise_exception:
290 if self.raise_exception:
291 raise
291 raise
292
292
293
293
294 def includeme(config):
294 def includeme(config):
295 configure_caches(config.registry.settings)
295 configure_caches(config.registry.settings)
@@ -1,674 +1,662 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2018 RhodeCode GmbH
3 # Copyright (C) 2014-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import importlib
29 import importlib
30 from functools import wraps
30 from functools import wraps
31 from StringIO import StringIO
31 from StringIO import StringIO
32 from lxml import etree
32 from lxml import etree
33
33
34 import time
34 import time
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from pyramid.httpexceptions import (
37 from pyramid.httpexceptions import (
38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
39 from zope.cachedescriptors.property import Lazy as LazyProperty
39 from zope.cachedescriptors.property import Lazy as LazyProperty
40
40
41 import rhodecode
41 import rhodecode
42 from rhodecode.authentication.base import (
42 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
43 authenticate, get_perms_cache_manager, VCS_TYPE, loadplugin)
43 from rhodecode.lib import caches, rc_cache
44 from rhodecode.lib import caches
45 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
44 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
46 from rhodecode.lib.base import (
45 from rhodecode.lib.base import (
47 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
46 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
48 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
47 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
49 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
48 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
50 from rhodecode.lib.middleware import appenlight
49 from rhodecode.lib.middleware import appenlight
51 from rhodecode.lib.middleware.utils import scm_app_http
50 from rhodecode.lib.middleware.utils import scm_app_http
52 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
53 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool, safe_unicode
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool, safe_unicode
54 from rhodecode.lib.vcs.conf import settings as vcs_settings
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
55 from rhodecode.lib.vcs.backends import base
54 from rhodecode.lib.vcs.backends import base
56
55
57 from rhodecode.model import meta
56 from rhodecode.model import meta
58 from rhodecode.model.db import User, Repository, PullRequest
57 from rhodecode.model.db import User, Repository, PullRequest
59 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.pull_request import PullRequestModel
59 from rhodecode.model.pull_request import PullRequestModel
61 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
62
61
63 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
64
63
65
64
66 def extract_svn_txn_id(acl_repo_name, data):
65 def extract_svn_txn_id(acl_repo_name, data):
67 """
66 """
68 Helper method for extraction of svn txn_id from submited XML data during
67 Helper method for extraction of svn txn_id from submited XML data during
69 POST operations
68 POST operations
70 """
69 """
71 try:
70 try:
72 root = etree.fromstring(data)
71 root = etree.fromstring(data)
73 pat = re.compile(r'/txn/(?P<txn_id>.*)')
72 pat = re.compile(r'/txn/(?P<txn_id>.*)')
74 for el in root:
73 for el in root:
75 if el.tag == '{DAV:}source':
74 if el.tag == '{DAV:}source':
76 for sub_el in el:
75 for sub_el in el:
77 if sub_el.tag == '{DAV:}href':
76 if sub_el.tag == '{DAV:}href':
78 match = pat.search(sub_el.text)
77 match = pat.search(sub_el.text)
79 if match:
78 if match:
80 svn_tx_id = match.groupdict()['txn_id']
79 svn_tx_id = match.groupdict()['txn_id']
81 txn_id = caches.compute_key_from_params(
80 txn_id = caches.compute_key_from_params(
82 acl_repo_name, svn_tx_id)
81 acl_repo_name, svn_tx_id)
83 return txn_id
82 return txn_id
84 except Exception:
83 except Exception:
85 log.exception('Failed to extract txn_id')
84 log.exception('Failed to extract txn_id')
86
85
87
86
88 def initialize_generator(factory):
87 def initialize_generator(factory):
89 """
88 """
90 Initializes the returned generator by draining its first element.
89 Initializes the returned generator by draining its first element.
91
90
92 This can be used to give a generator an initializer, which is the code
91 This can be used to give a generator an initializer, which is the code
93 up to the first yield statement. This decorator enforces that the first
92 up to the first yield statement. This decorator enforces that the first
94 produced element has the value ``"__init__"`` to make its special
93 produced element has the value ``"__init__"`` to make its special
95 purpose very explicit in the using code.
94 purpose very explicit in the using code.
96 """
95 """
97
96
98 @wraps(factory)
97 @wraps(factory)
99 def wrapper(*args, **kwargs):
98 def wrapper(*args, **kwargs):
100 gen = factory(*args, **kwargs)
99 gen = factory(*args, **kwargs)
101 try:
100 try:
102 init = gen.next()
101 init = gen.next()
103 except StopIteration:
102 except StopIteration:
104 raise ValueError('Generator must yield at least one element.')
103 raise ValueError('Generator must yield at least one element.')
105 if init != "__init__":
104 if init != "__init__":
106 raise ValueError('First yielded element must be "__init__".')
105 raise ValueError('First yielded element must be "__init__".')
107 return gen
106 return gen
108 return wrapper
107 return wrapper
109
108
110
109
111 class SimpleVCS(object):
110 class SimpleVCS(object):
112 """Common functionality for SCM HTTP handlers."""
111 """Common functionality for SCM HTTP handlers."""
113
112
114 SCM = 'unknown'
113 SCM = 'unknown'
115
114
116 acl_repo_name = None
115 acl_repo_name = None
117 url_repo_name = None
116 url_repo_name = None
118 vcs_repo_name = None
117 vcs_repo_name = None
119 rc_extras = {}
118 rc_extras = {}
120
119
121 # We have to handle requests to shadow repositories different than requests
120 # We have to handle requests to shadow repositories different than requests
122 # to normal repositories. Therefore we have to distinguish them. To do this
121 # to normal repositories. Therefore we have to distinguish them. To do this
123 # we use this regex which will match only on URLs pointing to shadow
122 # we use this regex which will match only on URLs pointing to shadow
124 # repositories.
123 # repositories.
125 shadow_repo_re = re.compile(
124 shadow_repo_re = re.compile(
126 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
125 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
127 '(?P<target>{slug_pat})/' # target repo
126 '(?P<target>{slug_pat})/' # target repo
128 'pull-request/(?P<pr_id>\d+)/' # pull request
127 'pull-request/(?P<pr_id>\d+)/' # pull request
129 'repository$' # shadow repo
128 'repository$' # shadow repo
130 .format(slug_pat=SLUG_RE.pattern))
129 .format(slug_pat=SLUG_RE.pattern))
131
130
132 def __init__(self, config, registry):
131 def __init__(self, config, registry):
133 self.registry = registry
132 self.registry = registry
134 self.config = config
133 self.config = config
135 # re-populated by specialized middleware
134 # re-populated by specialized middleware
136 self.repo_vcs_config = base.Config()
135 self.repo_vcs_config = base.Config()
137 self.rhodecode_settings = SettingsModel().get_all_settings(cache=True)
136 self.rhodecode_settings = SettingsModel().get_all_settings(cache=True)
138
137
139 registry.rhodecode_settings = self.rhodecode_settings
138 registry.rhodecode_settings = self.rhodecode_settings
140 # authenticate this VCS request using authfunc
139 # authenticate this VCS request using authfunc
141 auth_ret_code_detection = \
140 auth_ret_code_detection = \
142 str2bool(self.config.get('auth_ret_code_detection', False))
141 str2bool(self.config.get('auth_ret_code_detection', False))
143 self.authenticate = BasicAuth(
142 self.authenticate = BasicAuth(
144 '', authenticate, registry, config.get('auth_ret_code'),
143 '', authenticate, registry, config.get('auth_ret_code'),
145 auth_ret_code_detection)
144 auth_ret_code_detection)
146 self.ip_addr = '0.0.0.0'
145 self.ip_addr = '0.0.0.0'
147
146
148 @LazyProperty
147 @LazyProperty
149 def global_vcs_config(self):
148 def global_vcs_config(self):
150 try:
149 try:
151 return VcsSettingsModel().get_ui_settings_as_config_obj()
150 return VcsSettingsModel().get_ui_settings_as_config_obj()
152 except Exception:
151 except Exception:
153 return base.Config()
152 return base.Config()
154
153
155 @property
154 @property
156 def base_path(self):
155 def base_path(self):
157 settings_path = self.repo_vcs_config.get(
156 settings_path = self.repo_vcs_config.get(
158 *VcsSettingsModel.PATH_SETTING)
157 *VcsSettingsModel.PATH_SETTING)
159
158
160 if not settings_path:
159 if not settings_path:
161 settings_path = self.global_vcs_config.get(
160 settings_path = self.global_vcs_config.get(
162 *VcsSettingsModel.PATH_SETTING)
161 *VcsSettingsModel.PATH_SETTING)
163
162
164 if not settings_path:
163 if not settings_path:
165 # try, maybe we passed in explicitly as config option
164 # try, maybe we passed in explicitly as config option
166 settings_path = self.config.get('base_path')
165 settings_path = self.config.get('base_path')
167
166
168 if not settings_path:
167 if not settings_path:
169 raise ValueError('FATAL: base_path is empty')
168 raise ValueError('FATAL: base_path is empty')
170 return settings_path
169 return settings_path
171
170
172 def set_repo_names(self, environ):
171 def set_repo_names(self, environ):
173 """
172 """
174 This will populate the attributes acl_repo_name, url_repo_name,
173 This will populate the attributes acl_repo_name, url_repo_name,
175 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
174 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
176 shadow) repositories all names are equal. In case of requests to a
175 shadow) repositories all names are equal. In case of requests to a
177 shadow repository the acl-name points to the target repo of the pull
176 shadow repository the acl-name points to the target repo of the pull
178 request and the vcs-name points to the shadow repo file system path.
177 request and the vcs-name points to the shadow repo file system path.
179 The url-name is always the URL used by the vcs client program.
178 The url-name is always the URL used by the vcs client program.
180
179
181 Example in case of a shadow repo:
180 Example in case of a shadow repo:
182 acl_repo_name = RepoGroup/MyRepo
181 acl_repo_name = RepoGroup/MyRepo
183 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
182 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
184 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
183 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
185 """
184 """
186 # First we set the repo name from URL for all attributes. This is the
185 # First we set the repo name from URL for all attributes. This is the
187 # default if handling normal (non shadow) repo requests.
186 # default if handling normal (non shadow) repo requests.
188 self.url_repo_name = self._get_repository_name(environ)
187 self.url_repo_name = self._get_repository_name(environ)
189 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
188 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
190 self.is_shadow_repo = False
189 self.is_shadow_repo = False
191
190
192 # Check if this is a request to a shadow repository.
191 # Check if this is a request to a shadow repository.
193 match = self.shadow_repo_re.match(self.url_repo_name)
192 match = self.shadow_repo_re.match(self.url_repo_name)
194 if match:
193 if match:
195 match_dict = match.groupdict()
194 match_dict = match.groupdict()
196
195
197 # Build acl repo name from regex match.
196 # Build acl repo name from regex match.
198 acl_repo_name = safe_unicode('{groups}{target}'.format(
197 acl_repo_name = safe_unicode('{groups}{target}'.format(
199 groups=match_dict['groups'] or '',
198 groups=match_dict['groups'] or '',
200 target=match_dict['target']))
199 target=match_dict['target']))
201
200
202 # Retrieve pull request instance by ID from regex match.
201 # Retrieve pull request instance by ID from regex match.
203 pull_request = PullRequest.get(match_dict['pr_id'])
202 pull_request = PullRequest.get(match_dict['pr_id'])
204
203
205 # Only proceed if we got a pull request and if acl repo name from
204 # Only proceed if we got a pull request and if acl repo name from
206 # URL equals the target repo name of the pull request.
205 # URL equals the target repo name of the pull request.
207 if pull_request and \
206 if pull_request and \
208 (acl_repo_name == pull_request.target_repo.repo_name):
207 (acl_repo_name == pull_request.target_repo.repo_name):
209 repo_id = pull_request.target_repo.repo_id
208 repo_id = pull_request.target_repo.repo_id
210 # Get file system path to shadow repository.
209 # Get file system path to shadow repository.
211 workspace_id = PullRequestModel()._workspace_id(pull_request)
210 workspace_id = PullRequestModel()._workspace_id(pull_request)
212 target_vcs = pull_request.target_repo.scm_instance()
211 target_vcs = pull_request.target_repo.scm_instance()
213 vcs_repo_name = target_vcs._get_shadow_repository_path(
212 vcs_repo_name = target_vcs._get_shadow_repository_path(
214 repo_id, workspace_id)
213 repo_id, workspace_id)
215
214
216 # Store names for later usage.
215 # Store names for later usage.
217 self.vcs_repo_name = vcs_repo_name
216 self.vcs_repo_name = vcs_repo_name
218 self.acl_repo_name = acl_repo_name
217 self.acl_repo_name = acl_repo_name
219 self.is_shadow_repo = True
218 self.is_shadow_repo = True
220
219
221 log.debug('Setting all VCS repository names: %s', {
220 log.debug('Setting all VCS repository names: %s', {
222 'acl_repo_name': self.acl_repo_name,
221 'acl_repo_name': self.acl_repo_name,
223 'url_repo_name': self.url_repo_name,
222 'url_repo_name': self.url_repo_name,
224 'vcs_repo_name': self.vcs_repo_name,
223 'vcs_repo_name': self.vcs_repo_name,
225 })
224 })
226
225
227 @property
226 @property
228 def scm_app(self):
227 def scm_app(self):
229 custom_implementation = self.config['vcs.scm_app_implementation']
228 custom_implementation = self.config['vcs.scm_app_implementation']
230 if custom_implementation == 'http':
229 if custom_implementation == 'http':
231 log.info('Using HTTP implementation of scm app.')
230 log.info('Using HTTP implementation of scm app.')
232 scm_app_impl = scm_app_http
231 scm_app_impl = scm_app_http
233 else:
232 else:
234 log.info('Using custom implementation of scm_app: "{}"'.format(
233 log.info('Using custom implementation of scm_app: "{}"'.format(
235 custom_implementation))
234 custom_implementation))
236 scm_app_impl = importlib.import_module(custom_implementation)
235 scm_app_impl = importlib.import_module(custom_implementation)
237 return scm_app_impl
236 return scm_app_impl
238
237
239 def _get_by_id(self, repo_name):
238 def _get_by_id(self, repo_name):
240 """
239 """
241 Gets a special pattern _<ID> from clone url and tries to replace it
240 Gets a special pattern _<ID> from clone url and tries to replace it
242 with a repository_name for support of _<ID> non changeable urls
241 with a repository_name for support of _<ID> non changeable urls
243 """
242 """
244
243
245 data = repo_name.split('/')
244 data = repo_name.split('/')
246 if len(data) >= 2:
245 if len(data) >= 2:
247 from rhodecode.model.repo import RepoModel
246 from rhodecode.model.repo import RepoModel
248 by_id_match = RepoModel().get_repo_by_id(repo_name)
247 by_id_match = RepoModel().get_repo_by_id(repo_name)
249 if by_id_match:
248 if by_id_match:
250 data[1] = by_id_match.repo_name
249 data[1] = by_id_match.repo_name
251
250
252 return safe_str('/'.join(data))
251 return safe_str('/'.join(data))
253
252
254 def _invalidate_cache(self, repo_name):
253 def _invalidate_cache(self, repo_name):
255 """
254 """
256 Set's cache for this repository for invalidation on next access
255 Set's cache for this repository for invalidation on next access
257
256
258 :param repo_name: full repo name, also a cache key
257 :param repo_name: full repo name, also a cache key
259 """
258 """
260 ScmModel().mark_for_invalidation(repo_name)
259 ScmModel().mark_for_invalidation(repo_name)
261
260
262 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
261 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
263 db_repo = Repository.get_by_repo_name(repo_name)
262 db_repo = Repository.get_by_repo_name(repo_name)
264 if not db_repo:
263 if not db_repo:
265 log.debug('Repository `%s` not found inside the database.',
264 log.debug('Repository `%s` not found inside the database.',
266 repo_name)
265 repo_name)
267 return False
266 return False
268
267
269 if db_repo.repo_type != scm_type:
268 if db_repo.repo_type != scm_type:
270 log.warning(
269 log.warning(
271 'Repository `%s` have incorrect scm_type, expected %s got %s',
270 'Repository `%s` have incorrect scm_type, expected %s got %s',
272 repo_name, db_repo.repo_type, scm_type)
271 repo_name, db_repo.repo_type, scm_type)
273 return False
272 return False
274
273
275 config = db_repo._config
274 config = db_repo._config
276 config.set('extensions', 'largefiles', '')
275 config.set('extensions', 'largefiles', '')
277 return is_valid_repo(
276 return is_valid_repo(
278 repo_name, base_path,
277 repo_name, base_path,
279 explicit_scm=scm_type, expect_scm=scm_type, config=config)
278 explicit_scm=scm_type, expect_scm=scm_type, config=config)
280
279
281 def valid_and_active_user(self, user):
280 def valid_and_active_user(self, user):
282 """
281 """
283 Checks if that user is not empty, and if it's actually object it checks
282 Checks if that user is not empty, and if it's actually object it checks
284 if he's active.
283 if he's active.
285
284
286 :param user: user object or None
285 :param user: user object or None
287 :return: boolean
286 :return: boolean
288 """
287 """
289 if user is None:
288 if user is None:
290 return False
289 return False
291
290
292 elif user.active:
291 elif user.active:
293 return True
292 return True
294
293
295 return False
294 return False
296
295
297 @property
296 @property
298 def is_shadow_repo_dir(self):
297 def is_shadow_repo_dir(self):
299 return os.path.isdir(self.vcs_repo_name)
298 return os.path.isdir(self.vcs_repo_name)
300
299
301 def _check_permission(self, action, user, repo_name, ip_addr=None,
300 def _check_permission(self, action, user, repo_name, ip_addr=None,
302 plugin_id='', plugin_cache_active=False, cache_ttl=0):
301 plugin_id='', plugin_cache_active=False, cache_ttl=0):
303 """
302 """
304 Checks permissions using action (push/pull) user and repository
303 Checks permissions using action (push/pull) user and repository
305 name. If plugin_cache and ttl is set it will use the plugin which
304 name. If plugin_cache and ttl is set it will use the plugin which
306 authenticated the user to store the cached permissions result for N
305 authenticated the user to store the cached permissions result for N
307 amount of seconds as in cache_ttl
306 amount of seconds as in cache_ttl
308
307
309 :param action: push or pull action
308 :param action: push or pull action
310 :param user: user instance
309 :param user: user instance
311 :param repo_name: repository name
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 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
313 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
318 plugin_id, plugin_cache_active, cache_ttl)
314 plugin_id, plugin_cache_active, cache_ttl)
319
315
320 # for environ based password can be empty, but then the validation is
316 user_id = user.user_id
321 # on the server that fills in the env data needed for authentication
317 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
322 _perm_calc_hash = caches.compute_key_from_params(
318 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
323 plugin_id, action, user.user_id, repo_name, ip_addr)
324
319
325 # _authenticate is a wrapper for .auth() method of plugin.
320 @region.cache_on_arguments(namespace=cache_namespace_uid,
326 # it checks if .auth() sends proper data.
321 expiration_time=cache_ttl,
327 # For RhodeCodeExternalAuthPlugin it also maps users to
322 should_cache_fn=lambda v: plugin_cache_active)
328 # Database and maps the attributes returned from .auth()
323 def compute_perm_vcs(
329 # to RhodeCode database. If this function returns data
324 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
330 # then auth is correct.
331 start = time.time()
332 log.debug('Running plugin `%s` permissions check', plugin_id)
333
325
334 def perm_func():
335 """
336 This function is used internally in Cache of Beaker to calculate
337 Results
338 """
339 log.debug('auth: calculating permission access now...')
326 log.debug('auth: calculating permission access now...')
340 # check IP
327 # check IP
341 inherit = user.inherit_default_permissions
328 inherit = user.inherit_default_permissions
342 ip_allowed = AuthUser.check_ip_allowed(
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 if ip_allowed:
331 if ip_allowed:
345 log.info('Access for IP:%s allowed', ip_addr)
332 log.info('Access for IP:%s allowed', ip_addr)
346 else:
333 else:
347 return False
334 return False
348
335
349 if action == 'push':
336 if action == 'push':
350 perms = ('repository.write', 'repository.admin')
337 perms = ('repository.write', 'repository.admin')
351 if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
338 if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
352 return False
339 return False
353
340
354 else:
341 else:
355 # any other action need at least read permission
342 # any other action need at least read permission
356 perms = (
343 perms = (
357 'repository.read', 'repository.write', 'repository.admin')
344 'repository.read', 'repository.write', 'repository.admin')
358 if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
345 if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
359 return False
346 return False
360
347
361 return True
348 return True
362
349
363 if plugin_cache_active:
350 start = time.time()
364 log.debug('Trying to fetch cached perms by %s', _perm_calc_hash[:6])
351 log.debug('Running plugin `%s` permissions check', plugin_id)
365 perm_result = cache_manager.get(
352
366 _perm_calc_hash, createfunc=perm_func)
353 # for environ based auth, password can be empty, but then the validation is
367 else:
354 # on the server that fills in the env data needed for authentication
368 perm_result = perm_func()
355 perm_result = compute_perm_vcs(
356 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
369
357
370 auth_time = time.time() - start
358 auth_time = time.time() - start
371 log.debug('Permissions for plugin `%s` completed in %.3fs, '
359 log.debug('Permissions for plugin `%s` completed in %.3fs, '
372 'expiration time of fetched cache %.1fs.',
360 'expiration time of fetched cache %.1fs.',
373 plugin_id, auth_time, cache_ttl)
361 plugin_id, auth_time, cache_ttl)
374
362
375 return perm_result
363 return perm_result
376
364
377 def _check_ssl(self, environ, start_response):
365 def _check_ssl(self, environ, start_response):
378 """
366 """
379 Checks the SSL check flag and returns False if SSL is not present
367 Checks the SSL check flag and returns False if SSL is not present
380 and required True otherwise
368 and required True otherwise
381 """
369 """
382 org_proto = environ['wsgi._org_proto']
370 org_proto = environ['wsgi._org_proto']
383 # check if we have SSL required ! if not it's a bad request !
371 # check if we have SSL required ! if not it's a bad request !
384 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
372 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
385 if require_ssl and org_proto == 'http':
373 if require_ssl and org_proto == 'http':
386 log.debug(
374 log.debug(
387 'Bad request: detected protocol is `%s` and '
375 'Bad request: detected protocol is `%s` and '
388 'SSL/HTTPS is required.', org_proto)
376 'SSL/HTTPS is required.', org_proto)
389 return False
377 return False
390 return True
378 return True
391
379
392 def _get_default_cache_ttl(self):
380 def _get_default_cache_ttl(self):
393 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
381 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
394 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
382 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
395 plugin_settings = plugin.get_settings()
383 plugin_settings = plugin.get_settings()
396 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
384 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
397 plugin_settings) or (False, 0)
385 plugin_settings) or (False, 0)
398 return plugin_cache_active, cache_ttl
386 return plugin_cache_active, cache_ttl
399
387
400 def __call__(self, environ, start_response):
388 def __call__(self, environ, start_response):
401 try:
389 try:
402 return self._handle_request(environ, start_response)
390 return self._handle_request(environ, start_response)
403 except Exception:
391 except Exception:
404 log.exception("Exception while handling request")
392 log.exception("Exception while handling request")
405 appenlight.track_exception(environ)
393 appenlight.track_exception(environ)
406 return HTTPInternalServerError()(environ, start_response)
394 return HTTPInternalServerError()(environ, start_response)
407 finally:
395 finally:
408 meta.Session.remove()
396 meta.Session.remove()
409
397
410 def _handle_request(self, environ, start_response):
398 def _handle_request(self, environ, start_response):
411
399
412 if not self._check_ssl(environ, start_response):
400 if not self._check_ssl(environ, start_response):
413 reason = ('SSL required, while RhodeCode was unable '
401 reason = ('SSL required, while RhodeCode was unable '
414 'to detect this as SSL request')
402 'to detect this as SSL request')
415 log.debug('User not allowed to proceed, %s', reason)
403 log.debug('User not allowed to proceed, %s', reason)
416 return HTTPNotAcceptable(reason)(environ, start_response)
404 return HTTPNotAcceptable(reason)(environ, start_response)
417
405
418 if not self.url_repo_name:
406 if not self.url_repo_name:
419 log.warning('Repository name is empty: %s', self.url_repo_name)
407 log.warning('Repository name is empty: %s', self.url_repo_name)
420 # failed to get repo name, we fail now
408 # failed to get repo name, we fail now
421 return HTTPNotFound()(environ, start_response)
409 return HTTPNotFound()(environ, start_response)
422 log.debug('Extracted repo name is %s', self.url_repo_name)
410 log.debug('Extracted repo name is %s', self.url_repo_name)
423
411
424 ip_addr = get_ip_addr(environ)
412 ip_addr = get_ip_addr(environ)
425 user_agent = get_user_agent(environ)
413 user_agent = get_user_agent(environ)
426 username = None
414 username = None
427
415
428 # skip passing error to error controller
416 # skip passing error to error controller
429 environ['pylons.status_code_redirect'] = True
417 environ['pylons.status_code_redirect'] = True
430
418
431 # ======================================================================
419 # ======================================================================
432 # GET ACTION PULL or PUSH
420 # GET ACTION PULL or PUSH
433 # ======================================================================
421 # ======================================================================
434 action = self._get_action(environ)
422 action = self._get_action(environ)
435
423
436 # ======================================================================
424 # ======================================================================
437 # Check if this is a request to a shadow repository of a pull request.
425 # Check if this is a request to a shadow repository of a pull request.
438 # In this case only pull action is allowed.
426 # In this case only pull action is allowed.
439 # ======================================================================
427 # ======================================================================
440 if self.is_shadow_repo and action != 'pull':
428 if self.is_shadow_repo and action != 'pull':
441 reason = 'Only pull action is allowed for shadow repositories.'
429 reason = 'Only pull action is allowed for shadow repositories.'
442 log.debug('User not allowed to proceed, %s', reason)
430 log.debug('User not allowed to proceed, %s', reason)
443 return HTTPNotAcceptable(reason)(environ, start_response)
431 return HTTPNotAcceptable(reason)(environ, start_response)
444
432
445 # Check if the shadow repo actually exists, in case someone refers
433 # Check if the shadow repo actually exists, in case someone refers
446 # to it, and it has been deleted because of successful merge.
434 # to it, and it has been deleted because of successful merge.
447 if self.is_shadow_repo and not self.is_shadow_repo_dir:
435 if self.is_shadow_repo and not self.is_shadow_repo_dir:
448 log.debug(
436 log.debug(
449 'Shadow repo detected, and shadow repo dir `%s` is missing',
437 'Shadow repo detected, and shadow repo dir `%s` is missing',
450 self.is_shadow_repo_dir)
438 self.is_shadow_repo_dir)
451 return HTTPNotFound()(environ, start_response)
439 return HTTPNotFound()(environ, start_response)
452
440
453 # ======================================================================
441 # ======================================================================
454 # CHECK ANONYMOUS PERMISSION
442 # CHECK ANONYMOUS PERMISSION
455 # ======================================================================
443 # ======================================================================
456 if action in ['pull', 'push']:
444 if action in ['pull', 'push']:
457 anonymous_user = User.get_default_user()
445 anonymous_user = User.get_default_user()
458 username = anonymous_user.username
446 username = anonymous_user.username
459 if anonymous_user.active:
447 if anonymous_user.active:
460 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
448 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
461 # ONLY check permissions if the user is activated
449 # ONLY check permissions if the user is activated
462 anonymous_perm = self._check_permission(
450 anonymous_perm = self._check_permission(
463 action, anonymous_user, self.acl_repo_name, ip_addr,
451 action, anonymous_user, self.acl_repo_name, ip_addr,
464 plugin_id='anonymous_access',
452 plugin_id='anonymous_access',
465 plugin_cache_active=plugin_cache_active,
453 plugin_cache_active=plugin_cache_active,
466 cache_ttl=cache_ttl,
454 cache_ttl=cache_ttl,
467 )
455 )
468 else:
456 else:
469 anonymous_perm = False
457 anonymous_perm = False
470
458
471 if not anonymous_user.active or not anonymous_perm:
459 if not anonymous_user.active or not anonymous_perm:
472 if not anonymous_user.active:
460 if not anonymous_user.active:
473 log.debug('Anonymous access is disabled, running '
461 log.debug('Anonymous access is disabled, running '
474 'authentication')
462 'authentication')
475
463
476 if not anonymous_perm:
464 if not anonymous_perm:
477 log.debug('Not enough credentials to access this '
465 log.debug('Not enough credentials to access this '
478 'repository as anonymous user')
466 'repository as anonymous user')
479
467
480 username = None
468 username = None
481 # ==============================================================
469 # ==============================================================
482 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
470 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
483 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
471 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
484 # ==============================================================
472 # ==============================================================
485
473
486 # try to auth based on environ, container auth methods
474 # try to auth based on environ, container auth methods
487 log.debug('Running PRE-AUTH for container based authentication')
475 log.debug('Running PRE-AUTH for container based authentication')
488 pre_auth = authenticate(
476 pre_auth = authenticate(
489 '', '', environ, VCS_TYPE, registry=self.registry,
477 '', '', environ, VCS_TYPE, registry=self.registry,
490 acl_repo_name=self.acl_repo_name)
478 acl_repo_name=self.acl_repo_name)
491 if pre_auth and pre_auth.get('username'):
479 if pre_auth and pre_auth.get('username'):
492 username = pre_auth['username']
480 username = pre_auth['username']
493 log.debug('PRE-AUTH got %s as username', username)
481 log.debug('PRE-AUTH got %s as username', username)
494 if pre_auth:
482 if pre_auth:
495 log.debug('PRE-AUTH successful from %s',
483 log.debug('PRE-AUTH successful from %s',
496 pre_auth.get('auth_data', {}).get('_plugin'))
484 pre_auth.get('auth_data', {}).get('_plugin'))
497
485
498 # If not authenticated by the container, running basic auth
486 # If not authenticated by the container, running basic auth
499 # before inject the calling repo_name for special scope checks
487 # before inject the calling repo_name for special scope checks
500 self.authenticate.acl_repo_name = self.acl_repo_name
488 self.authenticate.acl_repo_name = self.acl_repo_name
501
489
502 plugin_cache_active, cache_ttl = False, 0
490 plugin_cache_active, cache_ttl = False, 0
503 plugin = None
491 plugin = None
504 if not username:
492 if not username:
505 self.authenticate.realm = self.authenticate.get_rc_realm()
493 self.authenticate.realm = self.authenticate.get_rc_realm()
506
494
507 try:
495 try:
508 auth_result = self.authenticate(environ)
496 auth_result = self.authenticate(environ)
509 except (UserCreationError, NotAllowedToCreateUserError) as e:
497 except (UserCreationError, NotAllowedToCreateUserError) as e:
510 log.error(e)
498 log.error(e)
511 reason = safe_str(e)
499 reason = safe_str(e)
512 return HTTPNotAcceptable(reason)(environ, start_response)
500 return HTTPNotAcceptable(reason)(environ, start_response)
513
501
514 if isinstance(auth_result, dict):
502 if isinstance(auth_result, dict):
515 AUTH_TYPE.update(environ, 'basic')
503 AUTH_TYPE.update(environ, 'basic')
516 REMOTE_USER.update(environ, auth_result['username'])
504 REMOTE_USER.update(environ, auth_result['username'])
517 username = auth_result['username']
505 username = auth_result['username']
518 plugin = auth_result.get('auth_data', {}).get('_plugin')
506 plugin = auth_result.get('auth_data', {}).get('_plugin')
519 log.info(
507 log.info(
520 'MAIN-AUTH successful for user `%s` from %s plugin',
508 'MAIN-AUTH successful for user `%s` from %s plugin',
521 username, plugin)
509 username, plugin)
522
510
523 plugin_cache_active, cache_ttl = auth_result.get(
511 plugin_cache_active, cache_ttl = auth_result.get(
524 'auth_data', {}).get('_ttl_cache') or (False, 0)
512 'auth_data', {}).get('_ttl_cache') or (False, 0)
525 else:
513 else:
526 return auth_result.wsgi_application(
514 return auth_result.wsgi_application(
527 environ, start_response)
515 environ, start_response)
528
516
529
517
530 # ==============================================================
518 # ==============================================================
531 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
519 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
532 # ==============================================================
520 # ==============================================================
533 user = User.get_by_username(username)
521 user = User.get_by_username(username)
534 if not self.valid_and_active_user(user):
522 if not self.valid_and_active_user(user):
535 return HTTPForbidden()(environ, start_response)
523 return HTTPForbidden()(environ, start_response)
536 username = user.username
524 username = user.username
537 user.update_lastactivity()
525 user.update_lastactivity()
538 meta.Session().commit()
526 meta.Session().commit()
539
527
540 # check user attributes for password change flag
528 # check user attributes for password change flag
541 user_obj = user
529 user_obj = user
542 if user_obj and user_obj.username != User.DEFAULT_USER and \
530 if user_obj and user_obj.username != User.DEFAULT_USER and \
543 user_obj.user_data.get('force_password_change'):
531 user_obj.user_data.get('force_password_change'):
544 reason = 'password change required'
532 reason = 'password change required'
545 log.debug('User not allowed to authenticate, %s', reason)
533 log.debug('User not allowed to authenticate, %s', reason)
546 return HTTPNotAcceptable(reason)(environ, start_response)
534 return HTTPNotAcceptable(reason)(environ, start_response)
547
535
548 # check permissions for this repository
536 # check permissions for this repository
549 perm = self._check_permission(
537 perm = self._check_permission(
550 action, user, self.acl_repo_name, ip_addr,
538 action, user, self.acl_repo_name, ip_addr,
551 plugin, plugin_cache_active, cache_ttl)
539 plugin, plugin_cache_active, cache_ttl)
552 if not perm:
540 if not perm:
553 return HTTPForbidden()(environ, start_response)
541 return HTTPForbidden()(environ, start_response)
554
542
555 # extras are injected into UI object and later available
543 # extras are injected into UI object and later available
556 # in hooks executed by RhodeCode
544 # in hooks executed by RhodeCode
557 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
545 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
558 extras = vcs_operation_context(
546 extras = vcs_operation_context(
559 environ, repo_name=self.acl_repo_name, username=username,
547 environ, repo_name=self.acl_repo_name, username=username,
560 action=action, scm=self.SCM, check_locking=check_locking,
548 action=action, scm=self.SCM, check_locking=check_locking,
561 is_shadow_repo=self.is_shadow_repo
549 is_shadow_repo=self.is_shadow_repo
562 )
550 )
563
551
564 # ======================================================================
552 # ======================================================================
565 # REQUEST HANDLING
553 # REQUEST HANDLING
566 # ======================================================================
554 # ======================================================================
567 repo_path = os.path.join(
555 repo_path = os.path.join(
568 safe_str(self.base_path), safe_str(self.vcs_repo_name))
556 safe_str(self.base_path), safe_str(self.vcs_repo_name))
569 log.debug('Repository path is %s', repo_path)
557 log.debug('Repository path is %s', repo_path)
570
558
571 fix_PATH()
559 fix_PATH()
572
560
573 log.info(
561 log.info(
574 '%s action on %s repo "%s" by "%s" from %s %s',
562 '%s action on %s repo "%s" by "%s" from %s %s',
575 action, self.SCM, safe_str(self.url_repo_name),
563 action, self.SCM, safe_str(self.url_repo_name),
576 safe_str(username), ip_addr, user_agent)
564 safe_str(username), ip_addr, user_agent)
577
565
578 return self._generate_vcs_response(
566 return self._generate_vcs_response(
579 environ, start_response, repo_path, extras, action)
567 environ, start_response, repo_path, extras, action)
580
568
581 @initialize_generator
569 @initialize_generator
582 def _generate_vcs_response(
570 def _generate_vcs_response(
583 self, environ, start_response, repo_path, extras, action):
571 self, environ, start_response, repo_path, extras, action):
584 """
572 """
585 Returns a generator for the response content.
573 Returns a generator for the response content.
586
574
587 This method is implemented as a generator, so that it can trigger
575 This method is implemented as a generator, so that it can trigger
588 the cache validation after all content sent back to the client. It
576 the cache validation after all content sent back to the client. It
589 also handles the locking exceptions which will be triggered when
577 also handles the locking exceptions which will be triggered when
590 the first chunk is produced by the underlying WSGI application.
578 the first chunk is produced by the underlying WSGI application.
591 """
579 """
592 txn_id = ''
580 txn_id = ''
593 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
581 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
594 # case for SVN, we want to re-use the callback daemon port
582 # case for SVN, we want to re-use the callback daemon port
595 # so we use the txn_id, for this we peek the body, and still save
583 # so we use the txn_id, for this we peek the body, and still save
596 # it as wsgi.input
584 # it as wsgi.input
597 data = environ['wsgi.input'].read()
585 data = environ['wsgi.input'].read()
598 environ['wsgi.input'] = StringIO(data)
586 environ['wsgi.input'] = StringIO(data)
599 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
587 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
600
588
601 callback_daemon, extras = self._prepare_callback_daemon(
589 callback_daemon, extras = self._prepare_callback_daemon(
602 extras, environ, action, txn_id=txn_id)
590 extras, environ, action, txn_id=txn_id)
603 log.debug('HOOKS extras is %s', extras)
591 log.debug('HOOKS extras is %s', extras)
604
592
605 config = self._create_config(extras, self.acl_repo_name)
593 config = self._create_config(extras, self.acl_repo_name)
606 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
594 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
607 with callback_daemon:
595 with callback_daemon:
608 app.rc_extras = extras
596 app.rc_extras = extras
609
597
610 try:
598 try:
611 response = app(environ, start_response)
599 response = app(environ, start_response)
612 finally:
600 finally:
613 # This statement works together with the decorator
601 # This statement works together with the decorator
614 # "initialize_generator" above. The decorator ensures that
602 # "initialize_generator" above. The decorator ensures that
615 # we hit the first yield statement before the generator is
603 # we hit the first yield statement before the generator is
616 # returned back to the WSGI server. This is needed to
604 # returned back to the WSGI server. This is needed to
617 # ensure that the call to "app" above triggers the
605 # ensure that the call to "app" above triggers the
618 # needed callback to "start_response" before the
606 # needed callback to "start_response" before the
619 # generator is actually used.
607 # generator is actually used.
620 yield "__init__"
608 yield "__init__"
621
609
622 # iter content
610 # iter content
623 for chunk in response:
611 for chunk in response:
624 yield chunk
612 yield chunk
625
613
626 try:
614 try:
627 # invalidate cache on push
615 # invalidate cache on push
628 if action == 'push':
616 if action == 'push':
629 self._invalidate_cache(self.url_repo_name)
617 self._invalidate_cache(self.url_repo_name)
630 finally:
618 finally:
631 meta.Session.remove()
619 meta.Session.remove()
632
620
633 def _get_repository_name(self, environ):
621 def _get_repository_name(self, environ):
634 """Get repository name out of the environmnent
622 """Get repository name out of the environmnent
635
623
636 :param environ: WSGI environment
624 :param environ: WSGI environment
637 """
625 """
638 raise NotImplementedError()
626 raise NotImplementedError()
639
627
640 def _get_action(self, environ):
628 def _get_action(self, environ):
641 """Map request commands into a pull or push command.
629 """Map request commands into a pull or push command.
642
630
643 :param environ: WSGI environment
631 :param environ: WSGI environment
644 """
632 """
645 raise NotImplementedError()
633 raise NotImplementedError()
646
634
647 def _create_wsgi_app(self, repo_path, repo_name, config):
635 def _create_wsgi_app(self, repo_path, repo_name, config):
648 """Return the WSGI app that will finally handle the request."""
636 """Return the WSGI app that will finally handle the request."""
649 raise NotImplementedError()
637 raise NotImplementedError()
650
638
651 def _create_config(self, extras, repo_name):
639 def _create_config(self, extras, repo_name):
652 """Create a safe config representation."""
640 """Create a safe config representation."""
653 raise NotImplementedError()
641 raise NotImplementedError()
654
642
655 def _should_use_callback_daemon(self, extras, environ, action):
643 def _should_use_callback_daemon(self, extras, environ, action):
656 return True
644 return True
657
645
658 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
646 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
659 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
647 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
660 if not self._should_use_callback_daemon(extras, environ, action):
648 if not self._should_use_callback_daemon(extras, environ, action):
661 # disable callback daemon for actions that don't require it
649 # disable callback daemon for actions that don't require it
662 direct_calls = True
650 direct_calls = True
663
651
664 return prepare_callback_daemon(
652 return prepare_callback_daemon(
665 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
653 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
666 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
654 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
667
655
668
656
669 def _should_check_locking(query_string):
657 def _should_check_locking(query_string):
670 # this is kind of hacky, but due to how mercurial handles client-server
658 # this is kind of hacky, but due to how mercurial handles client-server
671 # server see all operation on commit; bookmarks, phases and
659 # server see all operation on commit; bookmarks, phases and
672 # obsolescence marker in different transaction, we don't want to check
660 # obsolescence marker in different transaction, we don't want to check
673 # locking on those
661 # locking on those
674 return query_string not in ['cmd=listkeys']
662 return query_string not in ['cmd=listkeys']
@@ -1,325 +1,327 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
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 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
51 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
51 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
52 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
52 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
53 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
53 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
54 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
54 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
55 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
55 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
56 pyroutes.register('admin_settings', '/_admin/settings', []);
56 pyroutes.register('admin_settings', '/_admin/settings', []);
57 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
57 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
58 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
58 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
59 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
59 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
60 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
60 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
61 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
61 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
62 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
62 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
63 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
63 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
64 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
64 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
65 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
65 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
66 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
66 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
67 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
67 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
68 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
68 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
69 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
69 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
70 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
70 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
71 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
71 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
72 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
72 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
73 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
73 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
74 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
74 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
75 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
75 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
76 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
76 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
77 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
77 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
78 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
78 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
79 pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
79 pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
80 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
80 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
81 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
81 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
82 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
82 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
83 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
83 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
84 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
84 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
85 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
85 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
86 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
86 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
87 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
87 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
88 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
88 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
89 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
89 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
90 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
90 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
91 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
91 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
92 pyroutes.register('users', '/_admin/users', []);
92 pyroutes.register('users', '/_admin/users', []);
93 pyroutes.register('users_data', '/_admin/users_data', []);
93 pyroutes.register('users_data', '/_admin/users_data', []);
94 pyroutes.register('users_create', '/_admin/users/create', []);
94 pyroutes.register('users_create', '/_admin/users/create', []);
95 pyroutes.register('users_new', '/_admin/users/new', []);
95 pyroutes.register('users_new', '/_admin/users/new', []);
96 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
96 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
97 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
97 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
98 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
98 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
99 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
99 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
100 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
100 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
101 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
101 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
102 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
102 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
103 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
103 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
104 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
104 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
105 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
105 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
106 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
106 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
107 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
107 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
108 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
108 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
109 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
109 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
110 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
110 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
111 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
111 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
112 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
112 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
113 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
113 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
114 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
114 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
115 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
115 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
116 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
116 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
117 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
117 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
118 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
118 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
119 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
119 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
120 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
120 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
121 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
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 pyroutes.register('user_groups', '/_admin/user_groups', []);
124 pyroutes.register('user_groups', '/_admin/user_groups', []);
123 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
125 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
124 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
126 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
125 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
127 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
126 pyroutes.register('repos', '/_admin/repos', []);
128 pyroutes.register('repos', '/_admin/repos', []);
127 pyroutes.register('repo_new', '/_admin/repos/new', []);
129 pyroutes.register('repo_new', '/_admin/repos/new', []);
128 pyroutes.register('repo_create', '/_admin/repos/create', []);
130 pyroutes.register('repo_create', '/_admin/repos/create', []);
129 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
131 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
130 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
132 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
131 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
133 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
132 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
134 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
133 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
135 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
134 pyroutes.register('channelstream_proxy', '/_channelstream', []);
136 pyroutes.register('channelstream_proxy', '/_channelstream', []);
135 pyroutes.register('login', '/_admin/login', []);
137 pyroutes.register('login', '/_admin/login', []);
136 pyroutes.register('logout', '/_admin/logout', []);
138 pyroutes.register('logout', '/_admin/logout', []);
137 pyroutes.register('register', '/_admin/register', []);
139 pyroutes.register('register', '/_admin/register', []);
138 pyroutes.register('reset_password', '/_admin/password_reset', []);
140 pyroutes.register('reset_password', '/_admin/password_reset', []);
139 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
141 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
140 pyroutes.register('home', '/', []);
142 pyroutes.register('home', '/', []);
141 pyroutes.register('user_autocomplete_data', '/_users', []);
143 pyroutes.register('user_autocomplete_data', '/_users', []);
142 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
144 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
143 pyroutes.register('repo_list_data', '/_repos', []);
145 pyroutes.register('repo_list_data', '/_repos', []);
144 pyroutes.register('goto_switcher_data', '/_goto_data', []);
146 pyroutes.register('goto_switcher_data', '/_goto_data', []);
145 pyroutes.register('markup_preview', '/_markup_preview', []);
147 pyroutes.register('markup_preview', '/_markup_preview', []);
146 pyroutes.register('journal', '/_admin/journal', []);
148 pyroutes.register('journal', '/_admin/journal', []);
147 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
149 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
148 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
150 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
149 pyroutes.register('journal_public', '/_admin/public_journal', []);
151 pyroutes.register('journal_public', '/_admin/public_journal', []);
150 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
152 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
151 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
153 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
152 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
154 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
153 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
155 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
154 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
156 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
155 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
157 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
156 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
158 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
157 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
159 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
158 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
160 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
159 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
161 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
160 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
162 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
161 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
163 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
162 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
164 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
163 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
165 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
164 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
166 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
165 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
167 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
166 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
167 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
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 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
172 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
171 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
173 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
172 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
174 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
173 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
175 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
174 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
177 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
176 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
178 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
177 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
179 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
178 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
181 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
180 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
183 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
182 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
183 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
195 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
194 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
196 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
195 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
197 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
196 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
198 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
197 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
200 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
199 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
202 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
201 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']);
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 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
204 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
203 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
205 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
204 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
206 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
205 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
207 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
206 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
208 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
207 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
209 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
208 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
210 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
209 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
211 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
210 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
212 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
211 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
213 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
212 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
214 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
213 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
215 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
214 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
216 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
215 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
217 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
216 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
218 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
217 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
219 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
218 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
220 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
219 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
221 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
220 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']);
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 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
223 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
222 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
224 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
223 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
225 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
224 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
226 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
225 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
227 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
226 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
228 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
227 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
229 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
228 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
230 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
229 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
231 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
230 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
232 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
231 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
233 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
232 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
234 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
233 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
235 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
234 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
236 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
235 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
237 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
236 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
238 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
237 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
239 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
238 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
240 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
239 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
241 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
240 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
242 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
241 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
243 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
242 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
244 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
243 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
245 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
244 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
246 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
245 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
247 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
246 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
248 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
247 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
249 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
248 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
250 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
249 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
251 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
250 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
252 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
251 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
253 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
252 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
254 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
253 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
255 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
254 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
256 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
255 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
257 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
256 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
258 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
257 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
259 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
258 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
260 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
259 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
261 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
260 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
262 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
261 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
263 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
262 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
264 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
263 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
265 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
264 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
266 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
265 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
267 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
266 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
268 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
267 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
269 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
268 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
270 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
269 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
271 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
270 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
272 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
271 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
273 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
272 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
274 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
273 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
275 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
274 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
276 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
275 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
277 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
276 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
278 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
277 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
279 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
278 pyroutes.register('search', '/_admin/search', []);
280 pyroutes.register('search', '/_admin/search', []);
279 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
281 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
280 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
282 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
281 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
283 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
282 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
284 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
283 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
285 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
284 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
286 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
285 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
287 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
286 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
288 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
287 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
289 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
288 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
290 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
289 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
291 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
290 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
292 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
291 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
293 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
292 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
294 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
293 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
295 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
294 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
296 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
295 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
297 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
296 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
298 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
297 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
299 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
298 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
300 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
299 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
301 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
300 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
302 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
301 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
303 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
302 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
304 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
303 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
305 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
304 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
306 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
305 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
307 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
306 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
308 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
307 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
309 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
308 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
310 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
309 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
311 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
310 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
312 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
311 pyroutes.register('gists_show', '/_admin/gists', []);
313 pyroutes.register('gists_show', '/_admin/gists', []);
312 pyroutes.register('gists_new', '/_admin/gists/new', []);
314 pyroutes.register('gists_new', '/_admin/gists/new', []);
313 pyroutes.register('gists_create', '/_admin/gists/create', []);
315 pyroutes.register('gists_create', '/_admin/gists/create', []);
314 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
316 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
315 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
317 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
316 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
318 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
317 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
319 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
318 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
320 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
319 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
321 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
320 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
322 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
321 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
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 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
324 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
323 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
325 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
324 pyroutes.register('apiv2', '/_admin/api', []);
326 pyroutes.register('apiv2', '/_admin/api', []);
325 }
327 }
@@ -1,57 +1,58 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s user settings') % c.user.username}
5 ${_('%s user settings') % c.user.username}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('Users'),h.route_path('users'))}
14 ${h.link_to(_('Users'),h.route_path('users'))}
15 &raquo;
15 &raquo;
16 % if c.user.active:
16 % if c.user.active:
17 ${c.user.username}
17 ${c.user.username}
18 % else:
18 % else:
19 <strike title="${_('This user is set as disabled')}">${c.user.username}</strike>
19 <strike title="${_('This user is set as disabled')}">${c.user.username}</strike>
20 % endif
20 % endif
21
21
22 </%def>
22 </%def>
23
23
24 <%def name="menu_bar_nav()">
24 <%def name="menu_bar_nav()">
25 ${self.menu_items(active='admin')}
25 ${self.menu_items(active='admin')}
26 </%def>
26 </%def>
27
27
28 <%def name="main()">
28 <%def name="main()">
29 <div class="box user_settings">
29 <div class="box user_settings">
30 <div class="title">
30 <div class="title">
31 ${self.breadcrumbs()}
31 ${self.breadcrumbs()}
32 </div>
32 </div>
33
33
34 ##main
34 ##main
35 <div class="sidebar-col-wrapper">
35 <div class="sidebar-col-wrapper">
36 <div class="sidebar">
36 <div class="sidebar">
37 <ul class="nav nav-pills nav-stacked">
37 <ul class="nav nav-pills nav-stacked">
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>
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 <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>
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 <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>
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 <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>
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 <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>
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 <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>
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 <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>
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 <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>
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 <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>
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 <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>
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 </ul>
49 </ul>
49 </div>
50 </div>
50
51
51 <div class="main-content-full-width">
52 <div class="main-content-full-width">
52 <%include file="/admin/users/user_edit_${c.active}.mako"/>
53 <%include file="/admin/users/user_edit_${c.active}.mako"/>
53 </div>
54 </div>
54 </div>
55 </div>
55 </div>
56 </div>
56
57
57 </%def>
58 </%def>
@@ -1,106 +1,109 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.lib import caches
25 from rhodecode.lib import rc_cache
26 from rhodecode.lib.memory_lru_debug import MemoryLRUNamespaceManagerBase
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', [
31 def test_cache_decorator_init_not_configured(self):
32 ('',),
32 with pytest.raises(EnvironmentError):
33 (u'',),
33 rc_cache.get_or_create_region('dontexist')
34 (u'ac',),
34
35 ('ac', ),
35 @pytest.mark.parametrize('region_name', [
36 (u'Δ™Δ‡c',),
36 'cache_perms', u'cache_perms',
37 ('Δ…ac',),
38 ])
37 ])
39 def test_cache_manager_init(self, repo_name):
38 def test_cache_decorator_init(self, region_name):
40 cache_manager_instance = caches.get_cache_manager(
39 namespace = region_name
41 repo_name, 'my_cache')
40 cache_region = rc_cache.get_or_create_region(
42 assert cache_manager_instance
41 region_name, region_namespace=namespace)
43
42 assert cache_region
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)
55
43
56 @pytest.mark.parametrize('example_input', [
44 @pytest.mark.parametrize('example_input', [
57 ('',),
45 ('',),
58 (u'/ac',),
46 (u'/ac',),
59 (u'/ac', 1, 2, object()),
47 (u'/ac', 1, 2, object()),
60 (u'/Δ™Δ‡c', 1, 2, object()),
48 (u'/Δ™Δ‡c', 1, 2, object()),
61 ('/Δ…ac',),
49 ('/Δ…ac',),
62 (u'/ac', ),
50 (u'/ac', ),
63 ])
51 ])
64 def test_cache_manager_create_key(self, example_input):
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 assert key
54 assert key
67
55
68 def test_store_and_invalidate_value_from_manager(self):
56 @pytest.mark.parametrize('example_namespace', [
69 cache_manger_instance = caches.get_cache_manager(
57 'namespace', None
70 'repo_cache_long', 'my_cache_store')
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 return time.time()
84 return time.time()
74
85
75 added_keys = []
86 for x in range(10):
76 for i in xrange(3):
87 compute(x)
77 _cache_key = caches.compute_key_from_params('foo_bar', 'p%s' % i)
88
78 added_keys.append(_cache_key)
89 assert len(set(cache_region.backend.list_keys())) == 10
79 for x in xrange(10):
80 cache_manger_instance.get(_cache_key, createfunc=compute)
81
90
82 for key in added_keys:
91 def test_store_and_get_value_from_region(self):
83 assert cache_manger_instance[key]
92 cache_region = rc_cache.get_or_create_region('cache_perms')
84
93 # make sure we empty the cache now
85 caches.clear_cache_manager(cache_manger_instance)
94 for key in cache_region.backend.list_keys():
86
95 cache_region.delete(key)
87 for key in added_keys:
96 assert cache_region.backend.list_keys() == []
88 assert key not in cache_manger_instance
89 assert len(cache_manger_instance.namespace.keys()) == 0
90
97
91 def test_store_and_get_value_from_manager(self):
98 @cache_region.cache_on_arguments(expiration_time=5)
92 cache_manger_instance = caches.get_cache_manager(
99 def compute(key):
93 'repo_cache_long', 'my_cache_store')
100 return time.time()
94
95 _cache_key = caches.compute_key_from_params('foo_bar', 'multicall')
96
101
97 def compute():
102 result = set()
98 return time.time()
103 for x in range(10):
104 ret = compute('x')
105 result.add(ret)
99
106
100 result = set()
107 # once computed we have only one value (the same from cache)
101 for x in xrange(10):
108 # after executing it 10x
102 ret = cache_manger_instance.get(_cache_key, createfunc=compute)
109 assert len(result) == 1
103 result.add(ret)
104
105 # once computed we have only one value after executing it 10x
106 assert len(result) == 1
@@ -1,687 +1,683 b''
1
1
2
2
3 ################################################################################
3 ################################################################################
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10
10
11 ################################################################################
11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 ## EMAIL CONFIGURATION ##
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
19 #email_prefix = [RhodeCode]
20
20
21 ## email FROM address all mails will be sent
21 ## email FROM address all mails will be sent
22 #app_email_from = rhodecode-noreply@localhost
22 #app_email_from = rhodecode-noreply@localhost
23
23
24 ## Uncomment and replace with the address which should receive any error report
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
26 #email_to = admin@localhost
27
27
28 ## in case of Application errors, sent an error email form
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
29 #error_email_from = rhodecode_error@localhost
30
30
31 ## additional error message to be send in case of server crash
31 ## additional error message to be send in case of server crash
32 #error_message =
32 #error_message =
33
33
34
34
35 #smtp_server = mail.server.com
35 #smtp_server = mail.server.com
36 #smtp_username =
36 #smtp_username =
37 #smtp_password =
37 #smtp_password =
38 #smtp_port =
38 #smtp_port =
39 #smtp_use_tls = false
39 #smtp_use_tls = false
40 #smtp_use_ssl = true
40 #smtp_use_ssl = true
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 #smtp_auth =
42 #smtp_auth =
43
43
44 [server:main]
44 [server:main]
45 ## COMMON ##
45 ## COMMON ##
46 host = 0.0.0.0
46 host = 0.0.0.0
47 port = 5000
47 port = 5000
48
48
49 ##########################
49 ##########################
50 ## GUNICORN WSGI SERVER ##
50 ## GUNICORN WSGI SERVER ##
51 ##########################
51 ##########################
52 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
52 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
53
53
54 use = egg:gunicorn#main
54 use = egg:gunicorn#main
55 ## Sets the number of process workers. You must set `instance_id = *`
55 ## Sets the number of process workers. You must set `instance_id = *`
56 ## when this option is set to more than one worker, recommended
56 ## when this option is set to more than one worker, recommended
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 ## The `instance_id = *` must be set in the [app:main] section below
58 ## The `instance_id = *` must be set in the [app:main] section below
59 #workers = 2
59 #workers = 2
60 ## number of threads for each of the worker, must be set to 1 for gevent
60 ## number of threads for each of the worker, must be set to 1 for gevent
61 ## generally recommened to be at 1
61 ## generally recommened to be at 1
62 #threads = 1
62 #threads = 1
63 ## process name
63 ## process name
64 #proc_name = rhodecode
64 #proc_name = rhodecode
65 ## type of worker class, one of sync, gevent
65 ## type of worker class, one of sync, gevent
66 ## recommended for bigger setup is using of of other than sync one
66 ## recommended for bigger setup is using of of other than sync one
67 #worker_class = sync
67 #worker_class = sync
68 ## The maximum number of simultaneous clients. Valid only for Gevent
68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 #worker_connections = 10
69 #worker_connections = 10
70 ## max number of requests that worker will handle before being gracefully
70 ## max number of requests that worker will handle before being gracefully
71 ## restarted, could prevent memory leaks
71 ## restarted, could prevent memory leaks
72 #max_requests = 1000
72 #max_requests = 1000
73 #max_requests_jitter = 30
73 #max_requests_jitter = 30
74 ## amount of time a worker can spend with handling a request before it
74 ## amount of time a worker can spend with handling a request before it
75 ## gets killed and restarted. Set to 6hrs
75 ## gets killed and restarted. Set to 6hrs
76 #timeout = 21600
76 #timeout = 21600
77
77
78 ## prefix middleware for RhodeCode.
78 ## prefix middleware for RhodeCode.
79 ## recommended when using proxy setup.
79 ## recommended when using proxy setup.
80 ## allows to set RhodeCode under a prefix in server.
80 ## allows to set RhodeCode under a prefix in server.
81 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
81 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
82 ## And set your prefix like: `prefix = /custom_prefix`
82 ## And set your prefix like: `prefix = /custom_prefix`
83 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
83 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
84 ## to make your cookies only work on prefix url
84 ## to make your cookies only work on prefix url
85 [filter:proxy-prefix]
85 [filter:proxy-prefix]
86 use = egg:PasteDeploy#prefix
86 use = egg:PasteDeploy#prefix
87 prefix = /
87 prefix = /
88
88
89 [app:main]
89 [app:main]
90 is_test = True
90 is_test = True
91 use = egg:rhodecode-enterprise-ce
91 use = egg:rhodecode-enterprise-ce
92
92
93 ## enable proxy prefix middleware, defined above
93 ## enable proxy prefix middleware, defined above
94 #filter-with = proxy-prefix
94 #filter-with = proxy-prefix
95
95
96
96
97 ## RHODECODE PLUGINS ##
97 ## RHODECODE PLUGINS ##
98 rhodecode.includes = rhodecode.api
98 rhodecode.includes = rhodecode.api
99
99
100 # api prefix url
100 # api prefix url
101 rhodecode.api.url = /_admin/api
101 rhodecode.api.url = /_admin/api
102
102
103
103
104 ## END RHODECODE PLUGINS ##
104 ## END RHODECODE PLUGINS ##
105
105
106 ## encryption key used to encrypt social plugin tokens,
106 ## encryption key used to encrypt social plugin tokens,
107 ## remote_urls with credentials etc, if not set it defaults to
107 ## remote_urls with credentials etc, if not set it defaults to
108 ## `beaker.session.secret`
108 ## `beaker.session.secret`
109 #rhodecode.encrypted_values.secret =
109 #rhodecode.encrypted_values.secret =
110
110
111 ## decryption strict mode (enabled by default). It controls if decryption raises
111 ## decryption strict mode (enabled by default). It controls if decryption raises
112 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
112 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
113 #rhodecode.encrypted_values.strict = false
113 #rhodecode.encrypted_values.strict = false
114
114
115 ## return gzipped responses from Rhodecode (static files/application)
115 ## return gzipped responses from Rhodecode (static files/application)
116 gzip_responses = false
116 gzip_responses = false
117
117
118 ## autogenerate javascript routes file on startup
118 ## autogenerate javascript routes file on startup
119 generate_js_files = false
119 generate_js_files = false
120
120
121 ## Optional Languages
121 ## Optional Languages
122 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
122 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
123 lang = en
123 lang = en
124
124
125 ## perform a full repository scan on each server start, this should be
125 ## perform a full repository scan on each server start, this should be
126 ## set to false after first startup, to allow faster server restarts.
126 ## set to false after first startup, to allow faster server restarts.
127 startup.import_repos = true
127 startup.import_repos = true
128
128
129 ## Uncomment and set this path to use archive download cache.
129 ## Uncomment and set this path to use archive download cache.
130 ## Once enabled, generated archives will be cached at this location
130 ## Once enabled, generated archives will be cached at this location
131 ## and served from the cache during subsequent requests for the same archive of
131 ## and served from the cache during subsequent requests for the same archive of
132 ## the repository.
132 ## the repository.
133 #archive_cache_dir = /tmp/tarballcache
133 #archive_cache_dir = /tmp/tarballcache
134
134
135 ## URL at which the application is running. This is used for bootstraping
135 ## URL at which the application is running. This is used for bootstraping
136 ## requests in context when no web request is available. Used in ishell, or
136 ## requests in context when no web request is available. Used in ishell, or
137 ## SSH calls. Set this for events to receive proper url for SSH calls.
137 ## SSH calls. Set this for events to receive proper url for SSH calls.
138 app.base_url = http://rhodecode.local
138 app.base_url = http://rhodecode.local
139
139
140 ## change this to unique ID for security
140 ## change this to unique ID for security
141 app_instance_uuid = rc-production
141 app_instance_uuid = rc-production
142
142
143 ## cut off limit for large diffs (size in bytes)
143 ## cut off limit for large diffs (size in bytes)
144 cut_off_limit_diff = 1024000
144 cut_off_limit_diff = 1024000
145 cut_off_limit_file = 256000
145 cut_off_limit_file = 256000
146
146
147 ## use cache version of scm repo everywhere
147 ## use cache version of scm repo everywhere
148 vcs_full_cache = false
148 vcs_full_cache = false
149
149
150 ## force https in RhodeCode, fixes https redirects, assumes it's always https
150 ## force https in RhodeCode, fixes https redirects, assumes it's always https
151 ## Normally this is controlled by proper http flags sent from http server
151 ## Normally this is controlled by proper http flags sent from http server
152 force_https = false
152 force_https = false
153
153
154 ## use Strict-Transport-Security headers
154 ## use Strict-Transport-Security headers
155 use_htsts = false
155 use_htsts = false
156
156
157 ## git rev filter option, --all is the default filter, if you need to
157 ## git rev filter option, --all is the default filter, if you need to
158 ## hide all refs in changelog switch this to --branches --tags
158 ## hide all refs in changelog switch this to --branches --tags
159 git_rev_filter = --all
159 git_rev_filter = --all
160
160
161 # Set to true if your repos are exposed using the dumb protocol
161 # Set to true if your repos are exposed using the dumb protocol
162 git_update_server_info = false
162 git_update_server_info = false
163
163
164 ## RSS/ATOM feed options
164 ## RSS/ATOM feed options
165 rss_cut_off_limit = 256000
165 rss_cut_off_limit = 256000
166 rss_items_per_page = 10
166 rss_items_per_page = 10
167 rss_include_diff = false
167 rss_include_diff = false
168
168
169 ## gist URL alias, used to create nicer urls for gist. This should be an
169 ## gist URL alias, used to create nicer urls for gist. This should be an
170 ## url that does rewrites to _admin/gists/{gistid}.
170 ## url that does rewrites to _admin/gists/{gistid}.
171 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
171 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
172 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
172 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
173 gist_alias_url =
173 gist_alias_url =
174
174
175 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
175 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
176 ## used for access.
176 ## used for access.
177 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
177 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
178 ## came from the the logged in user who own this authentication token.
178 ## came from the the logged in user who own this authentication token.
179 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
179 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
180 ## authentication token. Such view would be only accessible when used together
180 ## authentication token. Such view would be only accessible when used together
181 ## with this authentication token
181 ## with this authentication token
182 ##
182 ##
183 ## list of all views can be found under `/_admin/permissions/auth_token_access`
183 ## list of all views can be found under `/_admin/permissions/auth_token_access`
184 ## The list should be "," separated and on a single line.
184 ## The list should be "," separated and on a single line.
185 ##
185 ##
186 ## Most common views to enable:
186 ## Most common views to enable:
187 # RepoCommitsView:repo_commit_download
187 # RepoCommitsView:repo_commit_download
188 # RepoCommitsView:repo_commit_patch
188 # RepoCommitsView:repo_commit_patch
189 # RepoCommitsView:repo_commit_raw
189 # RepoCommitsView:repo_commit_raw
190 # RepoCommitsView:repo_commit_raw@TOKEN
190 # RepoCommitsView:repo_commit_raw@TOKEN
191 # RepoFilesView:repo_files_diff
191 # RepoFilesView:repo_files_diff
192 # RepoFilesView:repo_archivefile
192 # RepoFilesView:repo_archivefile
193 # RepoFilesView:repo_file_raw
193 # RepoFilesView:repo_file_raw
194 # GistView:*
194 # GistView:*
195 api_access_controllers_whitelist =
195 api_access_controllers_whitelist =
196
196
197 ## default encoding used to convert from and to unicode
197 ## default encoding used to convert from and to unicode
198 ## can be also a comma separated list of encoding in case of mixed encodings
198 ## can be also a comma separated list of encoding in case of mixed encodings
199 default_encoding = UTF-8
199 default_encoding = UTF-8
200
200
201 ## instance-id prefix
201 ## instance-id prefix
202 ## a prefix key for this instance used for cache invalidation when running
202 ## a prefix key for this instance used for cache invalidation when running
203 ## multiple instances of rhodecode, make sure it's globally unique for
203 ## multiple instances of rhodecode, make sure it's globally unique for
204 ## all running rhodecode instances. Leave empty if you don't use it
204 ## all running rhodecode instances. Leave empty if you don't use it
205 instance_id =
205 instance_id =
206
206
207 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
207 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
208 ## of an authentication plugin also if it is disabled by it's settings.
208 ## of an authentication plugin also if it is disabled by it's settings.
209 ## This could be useful if you are unable to log in to the system due to broken
209 ## This could be useful if you are unable to log in to the system due to broken
210 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
210 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
211 ## module to log in again and fix the settings.
211 ## module to log in again and fix the settings.
212 ##
212 ##
213 ## Available builtin plugin IDs (hash is part of the ID):
213 ## Available builtin plugin IDs (hash is part of the ID):
214 ## egg:rhodecode-enterprise-ce#rhodecode
214 ## egg:rhodecode-enterprise-ce#rhodecode
215 ## egg:rhodecode-enterprise-ce#pam
215 ## egg:rhodecode-enterprise-ce#pam
216 ## egg:rhodecode-enterprise-ce#ldap
216 ## egg:rhodecode-enterprise-ce#ldap
217 ## egg:rhodecode-enterprise-ce#jasig_cas
217 ## egg:rhodecode-enterprise-ce#jasig_cas
218 ## egg:rhodecode-enterprise-ce#headers
218 ## egg:rhodecode-enterprise-ce#headers
219 ## egg:rhodecode-enterprise-ce#crowd
219 ## egg:rhodecode-enterprise-ce#crowd
220 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
220 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
221
221
222 ## alternative return HTTP header for failed authentication. Default HTTP
222 ## alternative return HTTP header for failed authentication. Default HTTP
223 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
223 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
224 ## handling that causing a series of failed authentication calls.
224 ## handling that causing a series of failed authentication calls.
225 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
225 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
226 ## This will be served instead of default 401 on bad authnetication
226 ## This will be served instead of default 401 on bad authnetication
227 auth_ret_code =
227 auth_ret_code =
228
228
229 ## use special detection method when serving auth_ret_code, instead of serving
229 ## use special detection method when serving auth_ret_code, instead of serving
230 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
230 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
231 ## and then serve auth_ret_code to clients
231 ## and then serve auth_ret_code to clients
232 auth_ret_code_detection = false
232 auth_ret_code_detection = false
233
233
234 ## locking return code. When repository is locked return this HTTP code. 2XX
234 ## locking return code. When repository is locked return this HTTP code. 2XX
235 ## codes don't break the transactions while 4XX codes do
235 ## codes don't break the transactions while 4XX codes do
236 lock_ret_code = 423
236 lock_ret_code = 423
237
237
238 ## allows to change the repository location in settings page
238 ## allows to change the repository location in settings page
239 allow_repo_location_change = true
239 allow_repo_location_change = true
240
240
241 ## allows to setup custom hooks in settings page
241 ## allows to setup custom hooks in settings page
242 allow_custom_hooks_settings = true
242 allow_custom_hooks_settings = true
243
243
244 ## generated license token, goto license page in RhodeCode settings to obtain
244 ## generated license token, goto license page in RhodeCode settings to obtain
245 ## new token
245 ## new token
246 license_token = abra-cada-bra1-rce3
246 license_token = abra-cada-bra1-rce3
247
247
248 ## supervisor connection uri, for managing supervisor and logs.
248 ## supervisor connection uri, for managing supervisor and logs.
249 supervisor.uri =
249 supervisor.uri =
250 ## supervisord group name/id we only want this RC instance to handle
250 ## supervisord group name/id we only want this RC instance to handle
251 supervisor.group_id = dev
251 supervisor.group_id = dev
252
252
253 ## Display extended labs settings
253 ## Display extended labs settings
254 labs_settings_active = true
254 labs_settings_active = true
255
255
256 ####################################
256 ####################################
257 ### CELERY CONFIG ####
257 ### CELERY CONFIG ####
258 ####################################
258 ####################################
259 use_celery = false
259 use_celery = false
260 broker.host = localhost
260 broker.host = localhost
261 broker.vhost = rabbitmqhost
261 broker.vhost = rabbitmqhost
262 broker.port = 5672
262 broker.port = 5672
263 broker.user = rabbitmq
263 broker.user = rabbitmq
264 broker.password = qweqwe
264 broker.password = qweqwe
265
265
266 celery.imports = rhodecode.lib.celerylib.tasks
266 celery.imports = rhodecode.lib.celerylib.tasks
267
267
268 celery.result.backend = amqp
268 celery.result.backend = amqp
269 celery.result.dburi = amqp://
269 celery.result.dburi = amqp://
270 celery.result.serialier = json
270 celery.result.serialier = json
271
271
272 #celery.send.task.error.emails = true
272 #celery.send.task.error.emails = true
273 #celery.amqp.task.result.expires = 18000
273 #celery.amqp.task.result.expires = 18000
274
274
275 celeryd.concurrency = 2
275 celeryd.concurrency = 2
276 #celeryd.log.file = celeryd.log
276 #celeryd.log.file = celeryd.log
277 celeryd.log.level = debug
277 celeryd.log.level = debug
278 celeryd.max.tasks.per.child = 1
278 celeryd.max.tasks.per.child = 1
279
279
280 ## tasks will never be sent to the queue, but executed locally instead.
280 ## tasks will never be sent to the queue, but executed locally instead.
281 celery.always.eager = false
281 celery.always.eager = false
282
282
283 ####################################
283 ####################################
284 ### BEAKER CACHE ####
284 ### BEAKER CACHE ####
285 ####################################
285 ####################################
286 # default cache dir for templates. Putting this into a ramdisk
286 # default cache dir for templates. Putting this into a ramdisk
287 ## can boost performance, eg. %(here)s/data_ramdisk
287 ## can boost performance, eg. %(here)s/data_ramdisk
288 cache_dir = %(here)s/data
288 cache_dir = %(here)s/data
289
289
290 ## locking and default file storage for Beaker. Putting this into a ramdisk
290 ## locking and default file storage for Beaker. Putting this into a ramdisk
291 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
291 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
292 beaker.cache.data_dir = %(here)s/rc/data/cache/beaker_data
292 beaker.cache.data_dir = %(here)s/rc/data/cache/beaker_data
293 beaker.cache.lock_dir = %(here)s/rc/data/cache/beaker_lock
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
295 beaker.cache.regions = long_term, sql_cache_short, 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
300
296
301 beaker.cache.long_term.type = memory
297 beaker.cache.long_term.type = memory
302 beaker.cache.long_term.expire = 36000
298 beaker.cache.long_term.expire = 36000
303 beaker.cache.long_term.key_length = 256
299 beaker.cache.long_term.key_length = 256
304
300
305 beaker.cache.sql_cache_short.type = memory
301 beaker.cache.sql_cache_short.type = memory
306 beaker.cache.sql_cache_short.expire = 1
302 beaker.cache.sql_cache_short.expire = 1
307 beaker.cache.sql_cache_short.key_length = 256
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 beaker.cache.repo_cache_long.type = memorylru_base
305 beaker.cache.repo_cache_long.type = memorylru_base
320 beaker.cache.repo_cache_long.max_items = 4096
306 beaker.cache.repo_cache_long.max_items = 4096
321 beaker.cache.repo_cache_long.expire = 2592000
307 beaker.cache.repo_cache_long.expire = 2592000
322
308
323 ## default is memorylru_base cache, configure only if required
309 ## default is memorylru_base cache, configure only if required
324 ## using multi-node or multi-worker setup
310 ## using multi-node or multi-worker setup
325 #beaker.cache.repo_cache_long.type = ext:memcached
311 #beaker.cache.repo_cache_long.type = ext:memcached
326 #beaker.cache.repo_cache_long.url = localhost:11211
312 #beaker.cache.repo_cache_long.url = localhost:11211
327 #beaker.cache.repo_cache_long.expire = 1209600
313 #beaker.cache.repo_cache_long.expire = 1209600
328 #beaker.cache.repo_cache_long.key_length = 256
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 ### BEAKER SESSION ####
327 ### BEAKER SESSION ####
332 ####################################
328 ####################################
333
329
334 ## .session.type is type of storage options for the session, current allowed
330 ## .session.type is type of storage options for the session, current allowed
335 ## types are file, ext:memcached, ext:database, and memory (default).
331 ## types are file, ext:memcached, ext:database, and memory (default).
336 beaker.session.type = file
332 beaker.session.type = file
337 beaker.session.data_dir = %(here)s/rc/data/sessions/data
333 beaker.session.data_dir = %(here)s/rc/data/sessions/data
338
334
339 ## db based session, fast, and allows easy management over logged in users
335 ## db based session, fast, and allows easy management over logged in users
340 #beaker.session.type = ext:database
336 #beaker.session.type = ext:database
341 #beaker.session.table_name = db_session
337 #beaker.session.table_name = db_session
342 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
338 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
343 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
339 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
344 #beaker.session.sa.pool_recycle = 3600
340 #beaker.session.sa.pool_recycle = 3600
345 #beaker.session.sa.echo = false
341 #beaker.session.sa.echo = false
346
342
347 beaker.session.key = rhodecode
343 beaker.session.key = rhodecode
348 beaker.session.secret = test-rc-uytcxaz
344 beaker.session.secret = test-rc-uytcxaz
349 beaker.session.lock_dir = %(here)s/rc/data/sessions/lock
345 beaker.session.lock_dir = %(here)s/rc/data/sessions/lock
350
346
351 ## Secure encrypted cookie. Requires AES and AES python libraries
347 ## Secure encrypted cookie. Requires AES and AES python libraries
352 ## you must disable beaker.session.secret to use this
348 ## you must disable beaker.session.secret to use this
353 #beaker.session.encrypt_key = key_for_encryption
349 #beaker.session.encrypt_key = key_for_encryption
354 #beaker.session.validate_key = validation_key
350 #beaker.session.validate_key = validation_key
355
351
356 ## sets session as invalid(also logging out user) if it haven not been
352 ## sets session as invalid(also logging out user) if it haven not been
357 ## accessed for given amount of time in seconds
353 ## accessed for given amount of time in seconds
358 beaker.session.timeout = 2592000
354 beaker.session.timeout = 2592000
359 beaker.session.httponly = true
355 beaker.session.httponly = true
360 ## Path to use for the cookie. Set to prefix if you use prefix middleware
356 ## Path to use for the cookie. Set to prefix if you use prefix middleware
361 #beaker.session.cookie_path = /custom_prefix
357 #beaker.session.cookie_path = /custom_prefix
362
358
363 ## uncomment for https secure cookie
359 ## uncomment for https secure cookie
364 beaker.session.secure = false
360 beaker.session.secure = false
365
361
366 ## auto save the session to not to use .save()
362 ## auto save the session to not to use .save()
367 beaker.session.auto = false
363 beaker.session.auto = false
368
364
369 ## default cookie expiration time in seconds, set to `true` to set expire
365 ## default cookie expiration time in seconds, set to `true` to set expire
370 ## at browser close
366 ## at browser close
371 #beaker.session.cookie_expires = 3600
367 #beaker.session.cookie_expires = 3600
372
368
373 ###################################
369 ###################################
374 ## SEARCH INDEXING CONFIGURATION ##
370 ## SEARCH INDEXING CONFIGURATION ##
375 ###################################
371 ###################################
376 ## Full text search indexer is available in rhodecode-tools under
372 ## Full text search indexer is available in rhodecode-tools under
377 ## `rhodecode-tools index` command
373 ## `rhodecode-tools index` command
378
374
379 ## WHOOSH Backend, doesn't require additional services to run
375 ## WHOOSH Backend, doesn't require additional services to run
380 ## it works good with few dozen repos
376 ## it works good with few dozen repos
381 search.module = rhodecode.lib.index.whoosh
377 search.module = rhodecode.lib.index.whoosh
382 search.location = %(here)s/data/index
378 search.location = %(here)s/data/index
383
379
384 ########################################
380 ########################################
385 ### CHANNELSTREAM CONFIG ####
381 ### CHANNELSTREAM CONFIG ####
386 ########################################
382 ########################################
387 ## channelstream enables persistent connections and live notification
383 ## channelstream enables persistent connections and live notification
388 ## in the system. It's also used by the chat system
384 ## in the system. It's also used by the chat system
389
385
390 channelstream.enabled = false
386 channelstream.enabled = false
391
387
392 ## server address for channelstream server on the backend
388 ## server address for channelstream server on the backend
393 channelstream.server = 127.0.0.1:9800
389 channelstream.server = 127.0.0.1:9800
394 ## location of the channelstream server from outside world
390 ## location of the channelstream server from outside world
395 ## use ws:// for http or wss:// for https. This address needs to be handled
391 ## use ws:// for http or wss:// for https. This address needs to be handled
396 ## by external HTTP server such as Nginx or Apache
392 ## by external HTTP server such as Nginx or Apache
397 ## see nginx/apache configuration examples in our docs
393 ## see nginx/apache configuration examples in our docs
398 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
394 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
399 channelstream.secret = secret
395 channelstream.secret = secret
400 channelstream.history.location = %(here)s/channelstream_history
396 channelstream.history.location = %(here)s/channelstream_history
401
397
402 ## Internal application path that Javascript uses to connect into.
398 ## Internal application path that Javascript uses to connect into.
403 ## If you use proxy-prefix the prefix should be added before /_channelstream
399 ## If you use proxy-prefix the prefix should be added before /_channelstream
404 channelstream.proxy_path = /_channelstream
400 channelstream.proxy_path = /_channelstream
405
401
406
402
407 ###################################
403 ###################################
408 ## APPENLIGHT CONFIG ##
404 ## APPENLIGHT CONFIG ##
409 ###################################
405 ###################################
410
406
411 ## Appenlight is tailored to work with RhodeCode, see
407 ## Appenlight is tailored to work with RhodeCode, see
412 ## http://appenlight.com for details how to obtain an account
408 ## http://appenlight.com for details how to obtain an account
413
409
414 ## appenlight integration enabled
410 ## appenlight integration enabled
415 appenlight = false
411 appenlight = false
416
412
417 appenlight.server_url = https://api.appenlight.com
413 appenlight.server_url = https://api.appenlight.com
418 appenlight.api_key = YOUR_API_KEY
414 appenlight.api_key = YOUR_API_KEY
419 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
415 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
420
416
421 # used for JS client
417 # used for JS client
422 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
418 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
423
419
424 ## TWEAK AMOUNT OF INFO SENT HERE
420 ## TWEAK AMOUNT OF INFO SENT HERE
425
421
426 ## enables 404 error logging (default False)
422 ## enables 404 error logging (default False)
427 appenlight.report_404 = false
423 appenlight.report_404 = false
428
424
429 ## time in seconds after request is considered being slow (default 1)
425 ## time in seconds after request is considered being slow (default 1)
430 appenlight.slow_request_time = 1
426 appenlight.slow_request_time = 1
431
427
432 ## record slow requests in application
428 ## record slow requests in application
433 ## (needs to be enabled for slow datastore recording and time tracking)
429 ## (needs to be enabled for slow datastore recording and time tracking)
434 appenlight.slow_requests = true
430 appenlight.slow_requests = true
435
431
436 ## enable hooking to application loggers
432 ## enable hooking to application loggers
437 appenlight.logging = true
433 appenlight.logging = true
438
434
439 ## minimum log level for log capture
435 ## minimum log level for log capture
440 appenlight.logging.level = WARNING
436 appenlight.logging.level = WARNING
441
437
442 ## send logs only from erroneous/slow requests
438 ## send logs only from erroneous/slow requests
443 ## (saves API quota for intensive logging)
439 ## (saves API quota for intensive logging)
444 appenlight.logging_on_error = false
440 appenlight.logging_on_error = false
445
441
446 ## list of additonal keywords that should be grabbed from environ object
442 ## list of additonal keywords that should be grabbed from environ object
447 ## can be string with comma separated list of words in lowercase
443 ## can be string with comma separated list of words in lowercase
448 ## (by default client will always send following info:
444 ## (by default client will always send following info:
449 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
445 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
450 ## start with HTTP* this list be extended with additional keywords here
446 ## start with HTTP* this list be extended with additional keywords here
451 appenlight.environ_keys_whitelist =
447 appenlight.environ_keys_whitelist =
452
448
453 ## list of keywords that should be blanked from request object
449 ## list of keywords that should be blanked from request object
454 ## can be string with comma separated list of words in lowercase
450 ## can be string with comma separated list of words in lowercase
455 ## (by default client will always blank keys that contain following words
451 ## (by default client will always blank keys that contain following words
456 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
452 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
457 ## this list be extended with additional keywords set here
453 ## this list be extended with additional keywords set here
458 appenlight.request_keys_blacklist =
454 appenlight.request_keys_blacklist =
459
455
460 ## list of namespaces that should be ignores when gathering log entries
456 ## list of namespaces that should be ignores when gathering log entries
461 ## can be string with comma separated list of namespaces
457 ## can be string with comma separated list of namespaces
462 ## (by default the client ignores own entries: appenlight_client.client)
458 ## (by default the client ignores own entries: appenlight_client.client)
463 appenlight.log_namespace_blacklist =
459 appenlight.log_namespace_blacklist =
464
460
465
461
466 ################################################################################
462 ################################################################################
467 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
463 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
468 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
464 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
469 ## execute malicious code after an exception is raised. ##
465 ## execute malicious code after an exception is raised. ##
470 ################################################################################
466 ################################################################################
471 set debug = false
467 set debug = false
472
468
473
469
474 ##############
470 ##############
475 ## STYLING ##
471 ## STYLING ##
476 ##############
472 ##############
477 debug_style = false
473 debug_style = false
478
474
479 ###########################################
475 ###########################################
480 ### MAIN RHODECODE DATABASE CONFIG ###
476 ### MAIN RHODECODE DATABASE CONFIG ###
481 ###########################################
477 ###########################################
482 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
478 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
483 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test
479 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test
484 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test
480 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test
485 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
481 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
486
482
487 # see sqlalchemy docs for other advanced settings
483 # see sqlalchemy docs for other advanced settings
488
484
489 ## print the sql statements to output
485 ## print the sql statements to output
490 sqlalchemy.db1.echo = false
486 sqlalchemy.db1.echo = false
491 ## recycle the connections after this amount of seconds
487 ## recycle the connections after this amount of seconds
492 sqlalchemy.db1.pool_recycle = 3600
488 sqlalchemy.db1.pool_recycle = 3600
493 sqlalchemy.db1.convert_unicode = true
489 sqlalchemy.db1.convert_unicode = true
494
490
495 ## the number of connections to keep open inside the connection pool.
491 ## the number of connections to keep open inside the connection pool.
496 ## 0 indicates no limit
492 ## 0 indicates no limit
497 #sqlalchemy.db1.pool_size = 5
493 #sqlalchemy.db1.pool_size = 5
498
494
499 ## the number of connections to allow in connection pool "overflow", that is
495 ## the number of connections to allow in connection pool "overflow", that is
500 ## connections that can be opened above and beyond the pool_size setting,
496 ## connections that can be opened above and beyond the pool_size setting,
501 ## which defaults to five.
497 ## which defaults to five.
502 #sqlalchemy.db1.max_overflow = 10
498 #sqlalchemy.db1.max_overflow = 10
503
499
504
500
505 ##################
501 ##################
506 ### VCS CONFIG ###
502 ### VCS CONFIG ###
507 ##################
503 ##################
508 vcs.server.enable = true
504 vcs.server.enable = true
509 vcs.server = localhost:9901
505 vcs.server = localhost:9901
510
506
511 ## Web server connectivity protocol, responsible for web based VCS operatations
507 ## Web server connectivity protocol, responsible for web based VCS operatations
512 ## Available protocols are:
508 ## Available protocols are:
513 ## `http` - use http-rpc backend (default)
509 ## `http` - use http-rpc backend (default)
514 vcs.server.protocol = http
510 vcs.server.protocol = http
515
511
516 ## Push/Pull operations protocol, available options are:
512 ## Push/Pull operations protocol, available options are:
517 ## `http` - use http-rpc backend (default)
513 ## `http` - use http-rpc backend (default)
518 ## `vcsserver.scm_app` - internal app (EE only)
514 ## `vcsserver.scm_app` - internal app (EE only)
519 vcs.scm_app_implementation = http
515 vcs.scm_app_implementation = http
520
516
521 ## Push/Pull operations hooks protocol, available options are:
517 ## Push/Pull operations hooks protocol, available options are:
522 ## `http` - use http-rpc backend (default)
518 ## `http` - use http-rpc backend (default)
523 vcs.hooks.protocol = http
519 vcs.hooks.protocol = http
524 vcs.hooks.host = 127.0.0.1
520 vcs.hooks.host = 127.0.0.1
525
521
526 vcs.server.log_level = debug
522 vcs.server.log_level = debug
527 ## Start VCSServer with this instance as a subprocess, usefull for development
523 ## Start VCSServer with this instance as a subprocess, usefull for development
528 vcs.start_server = false
524 vcs.start_server = false
529
525
530 ## List of enabled VCS backends, available options are:
526 ## List of enabled VCS backends, available options are:
531 ## `hg` - mercurial
527 ## `hg` - mercurial
532 ## `git` - git
528 ## `git` - git
533 ## `svn` - subversion
529 ## `svn` - subversion
534 vcs.backends = hg, git, svn
530 vcs.backends = hg, git, svn
535
531
536 vcs.connection_timeout = 3600
532 vcs.connection_timeout = 3600
537 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
533 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
538 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
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 #vcs.svn.compatible_version = pre-1.8-compatible
535 #vcs.svn.compatible_version = pre-1.8-compatible
540
536
541
537
542 ############################################################
538 ############################################################
543 ### Subversion proxy support (mod_dav_svn) ###
539 ### Subversion proxy support (mod_dav_svn) ###
544 ### Maps RhodeCode repo groups into SVN paths for Apache ###
540 ### Maps RhodeCode repo groups into SVN paths for Apache ###
545 ############################################################
541 ############################################################
546 ## Enable or disable the config file generation.
542 ## Enable or disable the config file generation.
547 svn.proxy.generate_config = false
543 svn.proxy.generate_config = false
548 ## Generate config file with `SVNListParentPath` set to `On`.
544 ## Generate config file with `SVNListParentPath` set to `On`.
549 svn.proxy.list_parent_path = true
545 svn.proxy.list_parent_path = true
550 ## Set location and file name of generated config file.
546 ## Set location and file name of generated config file.
551 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
547 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
552 ## Used as a prefix to the `Location` block in the generated config file.
548 ## Used as a prefix to the `Location` block in the generated config file.
553 ## In most cases it should be set to `/`.
549 ## In most cases it should be set to `/`.
554 svn.proxy.location_root = /
550 svn.proxy.location_root = /
555 ## Command to reload the mod dav svn configuration on change.
551 ## Command to reload the mod dav svn configuration on change.
556 ## Example: `/etc/init.d/apache2 reload`
552 ## Example: `/etc/init.d/apache2 reload`
557 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
553 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
558 ## If the timeout expires before the reload command finishes, the command will
554 ## If the timeout expires before the reload command finishes, the command will
559 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
555 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
560 #svn.proxy.reload_timeout = 10
556 #svn.proxy.reload_timeout = 10
561
557
562 ############################################################
558 ############################################################
563 ### SSH Support Settings ###
559 ### SSH Support Settings ###
564 ############################################################
560 ############################################################
565
561
566 ## Defines if the authorized_keys file should be written on any change of
562 ## Defines if the authorized_keys file should be written on any change of
567 ## user ssh keys, setting this to false also disables posibility of adding
563 ## user ssh keys, setting this to false also disables posibility of adding
568 ## ssh keys for users from web interface.
564 ## ssh keys for users from web interface.
569 ssh.generate_authorized_keyfile = true
565 ssh.generate_authorized_keyfile = true
570
566
571 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
567 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
572 # ssh.authorized_keys_ssh_opts =
568 # ssh.authorized_keys_ssh_opts =
573
569
574 ## File to generate the authorized keys together with options
570 ## File to generate the authorized keys together with options
575 ## It is possible to have multiple key files specified in `sshd_config` e.g.
571 ## It is possible to have multiple key files specified in `sshd_config` e.g.
576 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
572 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
577 ssh.authorized_keys_file_path = %(here)s/rc/authorized_keys_rhodecode
573 ssh.authorized_keys_file_path = %(here)s/rc/authorized_keys_rhodecode
578
574
579 ## Command to execute the SSH wrapper. The binary is available in the
575 ## Command to execute the SSH wrapper. The binary is available in the
580 ## rhodecode installation directory.
576 ## rhodecode installation directory.
581 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
577 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
582 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
578 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
583
579
584 ## Allow shell when executing the ssh-wrapper command
580 ## Allow shell when executing the ssh-wrapper command
585 ssh.wrapper_cmd_allow_shell = false
581 ssh.wrapper_cmd_allow_shell = false
586
582
587 ## Enables logging, and detailed output send back to the client. Usefull for
583 ## Enables logging, and detailed output send back to the client. Usefull for
588 ## debugging, shouldn't be used in production.
584 ## debugging, shouldn't be used in production.
589 ssh.enable_debug_logging = false
585 ssh.enable_debug_logging = false
590
586
591 ## Paths to binary executrables, by default they are the names, but we can
587 ## Paths to binary executrables, by default they are the names, but we can
592 ## override them if we want to use a custom one
588 ## override them if we want to use a custom one
593 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
589 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
594 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
590 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
595 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
591 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
596
592
597
593
598 ## Dummy marker to add new entries after.
594 ## Dummy marker to add new entries after.
599 ## Add any custom entries below. Please don't remove.
595 ## Add any custom entries below. Please don't remove.
600 custom.conf = 1
596 custom.conf = 1
601
597
602
598
603 ################################
599 ################################
604 ### LOGGING CONFIGURATION ####
600 ### LOGGING CONFIGURATION ####
605 ################################
601 ################################
606 [loggers]
602 [loggers]
607 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper
603 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper
608
604
609 [handlers]
605 [handlers]
610 keys = console, console_sql
606 keys = console, console_sql
611
607
612 [formatters]
608 [formatters]
613 keys = generic, color_formatter, color_formatter_sql
609 keys = generic, color_formatter, color_formatter_sql
614
610
615 #############
611 #############
616 ## LOGGERS ##
612 ## LOGGERS ##
617 #############
613 #############
618 [logger_root]
614 [logger_root]
619 level = NOTSET
615 level = NOTSET
620 handlers = console
616 handlers = console
621
617
622 [logger_routes]
618 [logger_routes]
623 level = DEBUG
619 level = DEBUG
624 handlers =
620 handlers =
625 qualname = routes.middleware
621 qualname = routes.middleware
626 ## "level = DEBUG" logs the route matched and routing variables.
622 ## "level = DEBUG" logs the route matched and routing variables.
627 propagate = 1
623 propagate = 1
628
624
629 [logger_beaker]
625 [logger_beaker]
630 level = DEBUG
626 level = DEBUG
631 handlers =
627 handlers =
632 qualname = beaker.container
628 qualname = beaker.container
633 propagate = 1
629 propagate = 1
634
630
635 [logger_rhodecode]
631 [logger_rhodecode]
636 level = DEBUG
632 level = DEBUG
637 handlers =
633 handlers =
638 qualname = rhodecode
634 qualname = rhodecode
639 propagate = 1
635 propagate = 1
640
636
641 [logger_sqlalchemy]
637 [logger_sqlalchemy]
642 level = ERROR
638 level = ERROR
643 handlers = console_sql
639 handlers = console_sql
644 qualname = sqlalchemy.engine
640 qualname = sqlalchemy.engine
645 propagate = 0
641 propagate = 0
646
642
647 [logger_ssh_wrapper]
643 [logger_ssh_wrapper]
648 level = DEBUG
644 level = DEBUG
649 handlers =
645 handlers =
650 qualname = ssh_wrapper
646 qualname = ssh_wrapper
651 propagate = 1
647 propagate = 1
652
648
653
649
654 ##############
650 ##############
655 ## HANDLERS ##
651 ## HANDLERS ##
656 ##############
652 ##############
657
653
658 [handler_console]
654 [handler_console]
659 class = StreamHandler
655 class = StreamHandler
660 args = (sys.stderr,)
656 args = (sys.stderr,)
661 level = DEBUG
657 level = DEBUG
662 formatter = generic
658 formatter = generic
663
659
664 [handler_console_sql]
660 [handler_console_sql]
665 class = StreamHandler
661 class = StreamHandler
666 args = (sys.stderr,)
662 args = (sys.stderr,)
667 level = WARN
663 level = WARN
668 formatter = generic
664 formatter = generic
669
665
670 ################
666 ################
671 ## FORMATTERS ##
667 ## FORMATTERS ##
672 ################
668 ################
673
669
674 [formatter_generic]
670 [formatter_generic]
675 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
671 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
676 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
672 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
677 datefmt = %Y-%m-%d %H:%M:%S
673 datefmt = %Y-%m-%d %H:%M:%S
678
674
679 [formatter_color_formatter]
675 [formatter_color_formatter]
680 class = rhodecode.lib.logging_formatter.ColorFormatter
676 class = rhodecode.lib.logging_formatter.ColorFormatter
681 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
677 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
682 datefmt = %Y-%m-%d %H:%M:%S
678 datefmt = %Y-%m-%d %H:%M:%S
683
679
684 [formatter_color_formatter_sql]
680 [formatter_color_formatter_sql]
685 class = rhodecode.lib.logging_formatter.ColorFormatterSql
681 class = rhodecode.lib.logging_formatter.ColorFormatterSql
686 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
682 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
687 datefmt = %Y-%m-%d %H:%M:%S
683 datefmt = %Y-%m-%d %H:%M:%S
General Comments 0
You need to be logged in to leave comments. Login now