##// END OF EJS Templates
encryption: Implement a slightly improved AesCipher encryption....
marcink -
r281:f41dae1c default
parent child Browse files
Show More
@@ -1,614 +1,618 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode Enterprise - configuration file #
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 # Built-in functions and variables #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
6 # #
7 ################################################################################
7 ################################################################################
8
8
9 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the email address which should receive ##
12 ## Uncomment and replace with the email address which should receive ##
13 ## any error reports after an application crash ##
13 ## any error reports after an application crash ##
14 ## Additionally these settings will be used by the RhodeCode mailing system ##
14 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ## COMMON ##
32 ## COMMON ##
33 host = 127.0.0.1
33 host = 127.0.0.1
34 port = 5000
34 port = 5000
35
35
36 ##################################
36 ##################################
37 ## WAITRESS WSGI SERVER ##
37 ## WAITRESS WSGI SERVER ##
38 ## Recommended for Development ##
38 ## Recommended for Development ##
39 ##################################
39 ##################################
40 use = egg:waitress#main
40 use = egg:waitress#main
41 ## number of worker threads
41 ## number of worker threads
42 threads = 5
42 threads = 5
43 ## MAX BODY SIZE 100GB
43 ## MAX BODY SIZE 100GB
44 max_request_body_size = 107374182400
44 max_request_body_size = 107374182400
45 ## Use poll instead of select, fixes file descriptors limits problems.
45 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## May not work on old windows systems.
46 ## May not work on old windows systems.
47 asyncore_use_poll = true
47 asyncore_use_poll = true
48
48
49
49
50 ##########################
50 ##########################
51 ## GUNICORN WSGI SERVER ##
51 ## GUNICORN WSGI SERVER ##
52 ##########################
52 ##########################
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
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
78
79 ## prefix middleware for RhodeCode, disables force_https flag.
79 ## prefix middleware for RhodeCode, disables force_https flag.
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/<prefix>. Enable `filter-with =` option below as well.
81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 #[filter:proxy-prefix]
82 #[filter:proxy-prefix]
83 #use = egg:PasteDeploy#prefix
83 #use = egg:PasteDeploy#prefix
84 #prefix = /<your-prefix>
84 #prefix = /<your-prefix>
85
85
86 [app:main]
86 [app:main]
87 use = egg:rhodecode-enterprise-ce
87 use = egg:rhodecode-enterprise-ce
88 ## enable proxy prefix middleware, defined below
88 ## enable proxy prefix middleware, defined below
89 #filter-with = proxy-prefix
89 #filter-with = proxy-prefix
90
90
91 # During development the we want to have the debug toolbar enabled
91 # During development the we want to have the debug toolbar enabled
92 pyramid.includes =
92 pyramid.includes =
93 pyramid_debugtoolbar
93 pyramid_debugtoolbar
94 rhodecode.utils.debugtoolbar
94 rhodecode.utils.debugtoolbar
95 rhodecode.lib.middleware.request_wrapper
95 rhodecode.lib.middleware.request_wrapper
96
96
97 pyramid.reload_templates = true
97 pyramid.reload_templates = true
98
98
99 debugtoolbar.hosts = 0.0.0.0/0
99 debugtoolbar.hosts = 0.0.0.0/0
100 debugtoolbar.exclude_prefixes =
100 debugtoolbar.exclude_prefixes =
101 /css
101 /css
102 /fonts
102 /fonts
103 /images
103 /images
104 /js
104 /js
105
105
106 ## RHODECODE PLUGINS ##
106 ## RHODECODE PLUGINS ##
107 rhodecode.includes =
107 rhodecode.includes =
108 rhodecode.api
108 rhodecode.api
109
109
110
110
111 # api prefix url
111 # api prefix url
112 rhodecode.api.url = /_admin/api
112 rhodecode.api.url = /_admin/api
113
113
114
114
115 ## END RHODECODE PLUGINS ##
115 ## END RHODECODE PLUGINS ##
116
116
117 ## encryption key used to encrypt social plugin tokens,
117 ## encryption key used to encrypt social plugin tokens,
118 ## remote_urls with credentials etc, if not set it defaults to
118 ## remote_urls with credentials etc, if not set it defaults to
119 ## `beaker.session.secret`
119 ## `beaker.session.secret`
120 #rhodecode.encrypted_values.secret =
120 #rhodecode.encrypted_values.secret =
121
121
122 ## decryption strict mode (enabled by default). It controls if decryption raises
123 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
124 #rhodecode.encrypted_values.strict = false
125
122 full_stack = true
126 full_stack = true
123
127
124 ## Serve static files via RhodeCode, disable to serve them via HTTP server
128 ## Serve static files via RhodeCode, disable to serve them via HTTP server
125 static_files = true
129 static_files = true
126
130
127 # autogenerate javascript routes file on startup
131 # autogenerate javascript routes file on startup
128 generate_js_files = false
132 generate_js_files = false
129
133
130 ## Optional Languages
134 ## Optional Languages
131 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
135 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
132 lang = en
136 lang = en
133
137
134 ## perform a full repository scan on each server start, this should be
138 ## perform a full repository scan on each server start, this should be
135 ## set to false after first startup, to allow faster server restarts.
139 ## set to false after first startup, to allow faster server restarts.
136 startup.import_repos = false
140 startup.import_repos = false
137
141
138 ## Uncomment and set this path to use archive download cache.
142 ## Uncomment and set this path to use archive download cache.
139 ## Once enabled, generated archives will be cached at this location
143 ## Once enabled, generated archives will be cached at this location
140 ## and served from the cache during subsequent requests for the same archive of
144 ## and served from the cache during subsequent requests for the same archive of
141 ## the repository.
145 ## the repository.
142 #archive_cache_dir = /tmp/tarballcache
146 #archive_cache_dir = /tmp/tarballcache
143
147
144 ## change this to unique ID for security
148 ## change this to unique ID for security
145 app_instance_uuid = rc-production
149 app_instance_uuid = rc-production
146
150
147 ## cut off limit for large diffs (size in bytes)
151 ## cut off limit for large diffs (size in bytes)
148 cut_off_limit_diff = 1024000
152 cut_off_limit_diff = 1024000
149 cut_off_limit_file = 256000
153 cut_off_limit_file = 256000
150
154
151 ## use cache version of scm repo everywhere
155 ## use cache version of scm repo everywhere
152 vcs_full_cache = true
156 vcs_full_cache = true
153
157
154 ## force https in RhodeCode, fixes https redirects, assumes it's always https
158 ## force https in RhodeCode, fixes https redirects, assumes it's always https
155 ## Normally this is controlled by proper http flags sent from http server
159 ## Normally this is controlled by proper http flags sent from http server
156 force_https = false
160 force_https = false
157
161
158 ## use Strict-Transport-Security headers
162 ## use Strict-Transport-Security headers
159 use_htsts = false
163 use_htsts = false
160
164
161 ## number of commits stats will parse on each iteration
165 ## number of commits stats will parse on each iteration
162 commit_parse_limit = 25
166 commit_parse_limit = 25
163
167
164 ## 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
165 ## hide all refs in changelog switch this to --branches --tags
169 ## hide all refs in changelog switch this to --branches --tags
166 git_rev_filter = --branches --tags
170 git_rev_filter = --branches --tags
167
171
168 # 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
169 git_update_server_info = false
173 git_update_server_info = false
170
174
171 ## RSS/ATOM feed options
175 ## RSS/ATOM feed options
172 rss_cut_off_limit = 256000
176 rss_cut_off_limit = 256000
173 rss_items_per_page = 10
177 rss_items_per_page = 10
174 rss_include_diff = false
178 rss_include_diff = false
175
179
176 ## 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
177 ## url that does rewrites to _admin/gists/<gistid>.
181 ## url that does rewrites to _admin/gists/<gistid>.
178 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
179 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
180 gist_alias_url =
184 gist_alias_url =
181
185
182 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
186 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
183 ## used for access.
187 ## used for access.
184 ## Adding ?auth_token = <token> to the url authenticates this request as if it
188 ## Adding ?auth_token = <token> to the url authenticates this request as if it
185 ## 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.
186 ##
190 ##
187 ## Syntax is <ControllerClass>:<function_pattern>.
191 ## Syntax is <ControllerClass>:<function_pattern>.
188 ## To enable access to raw_files put `FilesController:raw`.
192 ## To enable access to raw_files put `FilesController:raw`.
189 ## To enable access to patches add `ChangesetController:changeset_patch`.
193 ## To enable access to patches add `ChangesetController:changeset_patch`.
190 ## The list should be "," separated and on a single line.
194 ## The list should be "," separated and on a single line.
191 ##
195 ##
192 ## Recommended controllers to enable:
196 ## Recommended controllers to enable:
193 # ChangesetController:changeset_patch,
197 # ChangesetController:changeset_patch,
194 # ChangesetController:changeset_raw,
198 # ChangesetController:changeset_raw,
195 # FilesController:raw,
199 # FilesController:raw,
196 # FilesController:archivefile,
200 # FilesController:archivefile,
197 # GistsController:*,
201 # GistsController:*,
198 api_access_controllers_whitelist =
202 api_access_controllers_whitelist =
199
203
200 ## default encoding used to convert from and to unicode
204 ## default encoding used to convert from and to unicode
201 ## can be also a comma separated list of encoding in case of mixed encodings
205 ## can be also a comma separated list of encoding in case of mixed encodings
202 default_encoding = UTF-8
206 default_encoding = UTF-8
203
207
204 ## instance-id prefix
208 ## instance-id prefix
205 ## a prefix key for this instance used for cache invalidation when running
209 ## a prefix key for this instance used for cache invalidation when running
206 ## multiple instances of rhodecode, make sure it's globally unique for
210 ## multiple instances of rhodecode, make sure it's globally unique for
207 ## all running rhodecode instances. Leave empty if you don't use it
211 ## all running rhodecode instances. Leave empty if you don't use it
208 instance_id =
212 instance_id =
209
213
210 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
214 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
211 ## of an authentication plugin also if it is disabled by it's settings.
215 ## of an authentication plugin also if it is disabled by it's settings.
212 ## This could be useful if you are unable to log in to the system due to broken
216 ## This could be useful if you are unable to log in to the system due to broken
213 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
217 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
214 ## module to log in again and fix the settings.
218 ## module to log in again and fix the settings.
215 ##
219 ##
216 ## Available builtin plugin IDs (hash is part of the ID):
220 ## Available builtin plugin IDs (hash is part of the ID):
217 ## egg:rhodecode-enterprise-ce#rhodecode
221 ## egg:rhodecode-enterprise-ce#rhodecode
218 ## egg:rhodecode-enterprise-ce#pam
222 ## egg:rhodecode-enterprise-ce#pam
219 ## egg:rhodecode-enterprise-ce#ldap
223 ## egg:rhodecode-enterprise-ce#ldap
220 ## egg:rhodecode-enterprise-ce#jasig_cas
224 ## egg:rhodecode-enterprise-ce#jasig_cas
221 ## egg:rhodecode-enterprise-ce#headers
225 ## egg:rhodecode-enterprise-ce#headers
222 ## egg:rhodecode-enterprise-ce#crowd
226 ## egg:rhodecode-enterprise-ce#crowd
223 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
227 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
224
228
225 ## alternative return HTTP header for failed authentication. Default HTTP
229 ## alternative return HTTP header for failed authentication. Default HTTP
226 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
230 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
227 ## handling that causing a series of failed authentication calls.
231 ## handling that causing a series of failed authentication calls.
228 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
232 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
229 ## This will be served instead of default 401 on bad authnetication
233 ## This will be served instead of default 401 on bad authnetication
230 auth_ret_code =
234 auth_ret_code =
231
235
232 ## use special detection method when serving auth_ret_code, instead of serving
236 ## use special detection method when serving auth_ret_code, instead of serving
233 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
237 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
234 ## and then serve auth_ret_code to clients
238 ## and then serve auth_ret_code to clients
235 auth_ret_code_detection = false
239 auth_ret_code_detection = false
236
240
237 ## locking return code. When repository is locked return this HTTP code. 2XX
241 ## locking return code. When repository is locked return this HTTP code. 2XX
238 ## codes don't break the transactions while 4XX codes do
242 ## codes don't break the transactions while 4XX codes do
239 lock_ret_code = 423
243 lock_ret_code = 423
240
244
241 ## allows to change the repository location in settings page
245 ## allows to change the repository location in settings page
242 allow_repo_location_change = true
246 allow_repo_location_change = true
243
247
244 ## allows to setup custom hooks in settings page
248 ## allows to setup custom hooks in settings page
245 allow_custom_hooks_settings = true
249 allow_custom_hooks_settings = true
246
250
247 ## generated license token, goto license page in RhodeCode settings to obtain
251 ## generated license token, goto license page in RhodeCode settings to obtain
248 ## new token
252 ## new token
249 license_token =
253 license_token =
250
254
251 ## supervisor connection uri, for managing supervisor and logs.
255 ## supervisor connection uri, for managing supervisor and logs.
252 supervisor.uri =
256 supervisor.uri =
253 ## supervisord group name/id we only want this RC instance to handle
257 ## supervisord group name/id we only want this RC instance to handle
254 supervisor.group_id = dev
258 supervisor.group_id = dev
255
259
256 ## Display extended labs settings
260 ## Display extended labs settings
257 labs_settings_active = true
261 labs_settings_active = true
258
262
259 ####################################
263 ####################################
260 ### CELERY CONFIG ####
264 ### CELERY CONFIG ####
261 ####################################
265 ####################################
262 use_celery = false
266 use_celery = false
263 broker.host = localhost
267 broker.host = localhost
264 broker.vhost = rabbitmqhost
268 broker.vhost = rabbitmqhost
265 broker.port = 5672
269 broker.port = 5672
266 broker.user = rabbitmq
270 broker.user = rabbitmq
267 broker.password = qweqwe
271 broker.password = qweqwe
268
272
269 celery.imports = rhodecode.lib.celerylib.tasks
273 celery.imports = rhodecode.lib.celerylib.tasks
270
274
271 celery.result.backend = amqp
275 celery.result.backend = amqp
272 celery.result.dburi = amqp://
276 celery.result.dburi = amqp://
273 celery.result.serialier = json
277 celery.result.serialier = json
274
278
275 #celery.send.task.error.emails = true
279 #celery.send.task.error.emails = true
276 #celery.amqp.task.result.expires = 18000
280 #celery.amqp.task.result.expires = 18000
277
281
278 celeryd.concurrency = 2
282 celeryd.concurrency = 2
279 #celeryd.log.file = celeryd.log
283 #celeryd.log.file = celeryd.log
280 celeryd.log.level = debug
284 celeryd.log.level = debug
281 celeryd.max.tasks.per.child = 1
285 celeryd.max.tasks.per.child = 1
282
286
283 ## tasks will never be sent to the queue, but executed locally instead.
287 ## tasks will never be sent to the queue, but executed locally instead.
284 celery.always.eager = false
288 celery.always.eager = false
285
289
286 ####################################
290 ####################################
287 ### BEAKER CACHE ####
291 ### BEAKER CACHE ####
288 ####################################
292 ####################################
289 # default cache dir for templates. Putting this into a ramdisk
293 # default cache dir for templates. Putting this into a ramdisk
290 ## can boost performance, eg. %(here)s/data_ramdisk
294 ## can boost performance, eg. %(here)s/data_ramdisk
291 cache_dir = %(here)s/data
295 cache_dir = %(here)s/data
292
296
293 ## locking and default file storage for Beaker. Putting this into a ramdisk
297 ## locking and default file storage for Beaker. Putting this into a ramdisk
294 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
298 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
295 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
299 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
296 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
300 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
297
301
298 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
302 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
299
303
300 beaker.cache.super_short_term.type = memory
304 beaker.cache.super_short_term.type = memory
301 beaker.cache.super_short_term.expire = 10
305 beaker.cache.super_short_term.expire = 10
302 beaker.cache.super_short_term.key_length = 256
306 beaker.cache.super_short_term.key_length = 256
303
307
304 beaker.cache.short_term.type = memory
308 beaker.cache.short_term.type = memory
305 beaker.cache.short_term.expire = 60
309 beaker.cache.short_term.expire = 60
306 beaker.cache.short_term.key_length = 256
310 beaker.cache.short_term.key_length = 256
307
311
308 beaker.cache.long_term.type = memory
312 beaker.cache.long_term.type = memory
309 beaker.cache.long_term.expire = 36000
313 beaker.cache.long_term.expire = 36000
310 beaker.cache.long_term.key_length = 256
314 beaker.cache.long_term.key_length = 256
311
315
312 beaker.cache.sql_cache_short.type = memory
316 beaker.cache.sql_cache_short.type = memory
313 beaker.cache.sql_cache_short.expire = 10
317 beaker.cache.sql_cache_short.expire = 10
314 beaker.cache.sql_cache_short.key_length = 256
318 beaker.cache.sql_cache_short.key_length = 256
315
319
316 # default is memory cache, configure only if required
320 # default is memory cache, configure only if required
317 # using multi-node or multi-worker setup
321 # using multi-node or multi-worker setup
318 #beaker.cache.auth_plugins.type = ext:database
322 #beaker.cache.auth_plugins.type = ext:database
319 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
323 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
320 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
324 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
321 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
325 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
322 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
326 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
323 #beaker.cache.auth_plugins.sa.pool_size = 10
327 #beaker.cache.auth_plugins.sa.pool_size = 10
324 #beaker.cache.auth_plugins.sa.max_overflow = 0
328 #beaker.cache.auth_plugins.sa.max_overflow = 0
325
329
326 beaker.cache.repo_cache_long.type = memorylru_base
330 beaker.cache.repo_cache_long.type = memorylru_base
327 beaker.cache.repo_cache_long.max_items = 4096
331 beaker.cache.repo_cache_long.max_items = 4096
328 beaker.cache.repo_cache_long.expire = 2592000
332 beaker.cache.repo_cache_long.expire = 2592000
329
333
330 # default is memorylru_base cache, configure only if required
334 # default is memorylru_base cache, configure only if required
331 # using multi-node or multi-worker setup
335 # using multi-node or multi-worker setup
332 #beaker.cache.repo_cache_long.type = ext:memcached
336 #beaker.cache.repo_cache_long.type = ext:memcached
333 #beaker.cache.repo_cache_long.url = localhost:11211
337 #beaker.cache.repo_cache_long.url = localhost:11211
334 #beaker.cache.repo_cache_long.expire = 1209600
338 #beaker.cache.repo_cache_long.expire = 1209600
335 #beaker.cache.repo_cache_long.key_length = 256
339 #beaker.cache.repo_cache_long.key_length = 256
336
340
337 ####################################
341 ####################################
338 ### BEAKER SESSION ####
342 ### BEAKER SESSION ####
339 ####################################
343 ####################################
340
344
341 ## .session.type is type of storage options for the session, current allowed
345 ## .session.type is type of storage options for the session, current allowed
342 ## types are file, ext:memcached, ext:database, and memory (default).
346 ## types are file, ext:memcached, ext:database, and memory (default).
343 beaker.session.type = file
347 beaker.session.type = file
344 beaker.session.data_dir = %(here)s/data/sessions/data
348 beaker.session.data_dir = %(here)s/data/sessions/data
345
349
346 ## db based session, fast, and allows easy management over logged in users ##
350 ## db based session, fast, and allows easy management over logged in users ##
347 #beaker.session.type = ext:database
351 #beaker.session.type = ext:database
348 #beaker.session.table_name = db_session
352 #beaker.session.table_name = db_session
349 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
353 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
350 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
354 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
351 #beaker.session.sa.pool_recycle = 3600
355 #beaker.session.sa.pool_recycle = 3600
352 #beaker.session.sa.echo = false
356 #beaker.session.sa.echo = false
353
357
354 beaker.session.key = rhodecode
358 beaker.session.key = rhodecode
355 beaker.session.secret = develop-rc-uytcxaz
359 beaker.session.secret = develop-rc-uytcxaz
356 beaker.session.lock_dir = %(here)s/data/sessions/lock
360 beaker.session.lock_dir = %(here)s/data/sessions/lock
357
361
358 ## Secure encrypted cookie. Requires AES and AES python libraries
362 ## Secure encrypted cookie. Requires AES and AES python libraries
359 ## you must disable beaker.session.secret to use this
363 ## you must disable beaker.session.secret to use this
360 #beaker.session.encrypt_key = <key_for_encryption>
364 #beaker.session.encrypt_key = <key_for_encryption>
361 #beaker.session.validate_key = <validation_key>
365 #beaker.session.validate_key = <validation_key>
362
366
363 ## sets session as invalid(also logging out user) if it haven not been
367 ## sets session as invalid(also logging out user) if it haven not been
364 ## accessed for given amount of time in seconds
368 ## accessed for given amount of time in seconds
365 beaker.session.timeout = 2592000
369 beaker.session.timeout = 2592000
366 beaker.session.httponly = true
370 beaker.session.httponly = true
367 #beaker.session.cookie_path = /<your-prefix>
371 #beaker.session.cookie_path = /<your-prefix>
368
372
369 ## uncomment for https secure cookie
373 ## uncomment for https secure cookie
370 beaker.session.secure = false
374 beaker.session.secure = false
371
375
372 ## auto save the session to not to use .save()
376 ## auto save the session to not to use .save()
373 beaker.session.auto = false
377 beaker.session.auto = false
374
378
375 ## default cookie expiration time in seconds, set to `true` to set expire
379 ## default cookie expiration time in seconds, set to `true` to set expire
376 ## at browser close
380 ## at browser close
377 #beaker.session.cookie_expires = 3600
381 #beaker.session.cookie_expires = 3600
378
382
379 ###################################
383 ###################################
380 ## SEARCH INDEXING CONFIGURATION ##
384 ## SEARCH INDEXING CONFIGURATION ##
381 ###################################
385 ###################################
382 ## Full text search indexer is available in rhodecode-tools under
386 ## Full text search indexer is available in rhodecode-tools under
383 ## `rhodecode-tools index` command
387 ## `rhodecode-tools index` command
384
388
385 # WHOOSH Backend, doesn't require additional services to run
389 # WHOOSH Backend, doesn't require additional services to run
386 # it works good with few dozen repos
390 # it works good with few dozen repos
387 search.module = rhodecode.lib.index.whoosh
391 search.module = rhodecode.lib.index.whoosh
388 search.location = %(here)s/data/index
392 search.location = %(here)s/data/index
389
393
390 ###################################
394 ###################################
391 ## APPENLIGHT CONFIG ##
395 ## APPENLIGHT CONFIG ##
392 ###################################
396 ###################################
393
397
394 ## Appenlight is tailored to work with RhodeCode, see
398 ## Appenlight is tailored to work with RhodeCode, see
395 ## http://appenlight.com for details how to obtain an account
399 ## http://appenlight.com for details how to obtain an account
396
400
397 ## appenlight integration enabled
401 ## appenlight integration enabled
398 appenlight = false
402 appenlight = false
399
403
400 appenlight.server_url = https://api.appenlight.com
404 appenlight.server_url = https://api.appenlight.com
401 appenlight.api_key = YOUR_API_KEY
405 appenlight.api_key = YOUR_API_KEY
402 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
406 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
403
407
404 # used for JS client
408 # used for JS client
405 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
409 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
406
410
407 ## TWEAK AMOUNT OF INFO SENT HERE
411 ## TWEAK AMOUNT OF INFO SENT HERE
408
412
409 ## enables 404 error logging (default False)
413 ## enables 404 error logging (default False)
410 appenlight.report_404 = false
414 appenlight.report_404 = false
411
415
412 ## time in seconds after request is considered being slow (default 1)
416 ## time in seconds after request is considered being slow (default 1)
413 appenlight.slow_request_time = 1
417 appenlight.slow_request_time = 1
414
418
415 ## record slow requests in application
419 ## record slow requests in application
416 ## (needs to be enabled for slow datastore recording and time tracking)
420 ## (needs to be enabled for slow datastore recording and time tracking)
417 appenlight.slow_requests = true
421 appenlight.slow_requests = true
418
422
419 ## enable hooking to application loggers
423 ## enable hooking to application loggers
420 appenlight.logging = true
424 appenlight.logging = true
421
425
422 ## minimum log level for log capture
426 ## minimum log level for log capture
423 appenlight.logging.level = WARNING
427 appenlight.logging.level = WARNING
424
428
425 ## send logs only from erroneous/slow requests
429 ## send logs only from erroneous/slow requests
426 ## (saves API quota for intensive logging)
430 ## (saves API quota for intensive logging)
427 appenlight.logging_on_error = false
431 appenlight.logging_on_error = false
428
432
429 ## list of additonal keywords that should be grabbed from environ object
433 ## list of additonal keywords that should be grabbed from environ object
430 ## can be string with comma separated list of words in lowercase
434 ## can be string with comma separated list of words in lowercase
431 ## (by default client will always send following info:
435 ## (by default client will always send following info:
432 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
436 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
433 ## start with HTTP* this list be extended with additional keywords here
437 ## start with HTTP* this list be extended with additional keywords here
434 appenlight.environ_keys_whitelist =
438 appenlight.environ_keys_whitelist =
435
439
436 ## list of keywords that should be blanked from request object
440 ## list of keywords that should be blanked from request object
437 ## can be string with comma separated list of words in lowercase
441 ## can be string with comma separated list of words in lowercase
438 ## (by default client will always blank keys that contain following words
442 ## (by default client will always blank keys that contain following words
439 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
443 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
440 ## this list be extended with additional keywords set here
444 ## this list be extended with additional keywords set here
441 appenlight.request_keys_blacklist =
445 appenlight.request_keys_blacklist =
442
446
443 ## list of namespaces that should be ignores when gathering log entries
447 ## list of namespaces that should be ignores when gathering log entries
444 ## can be string with comma separated list of namespaces
448 ## can be string with comma separated list of namespaces
445 ## (by default the client ignores own entries: appenlight_client.client)
449 ## (by default the client ignores own entries: appenlight_client.client)
446 appenlight.log_namespace_blacklist =
450 appenlight.log_namespace_blacklist =
447
451
448
452
449 ################################################################################
453 ################################################################################
450 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
454 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
451 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
455 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
452 ## execute malicious code after an exception is raised. ##
456 ## execute malicious code after an exception is raised. ##
453 ################################################################################
457 ################################################################################
454 #set debug = false
458 #set debug = false
455
459
456
460
457 ##############
461 ##############
458 ## STYLING ##
462 ## STYLING ##
459 ##############
463 ##############
460 debug_style = true
464 debug_style = true
461
465
462 #########################################################
466 #########################################################
463 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
467 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
464 #########################################################
468 #########################################################
465 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
469 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
466 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
470 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
467 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
471 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
468
472
469 # see sqlalchemy docs for other advanced settings
473 # see sqlalchemy docs for other advanced settings
470
474
471 ## print the sql statements to output
475 ## print the sql statements to output
472 sqlalchemy.db1.echo = false
476 sqlalchemy.db1.echo = false
473 ## recycle the connections after this ammount of seconds
477 ## recycle the connections after this ammount of seconds
474 sqlalchemy.db1.pool_recycle = 3600
478 sqlalchemy.db1.pool_recycle = 3600
475 sqlalchemy.db1.convert_unicode = true
479 sqlalchemy.db1.convert_unicode = true
476
480
477 ## the number of connections to keep open inside the connection pool.
481 ## the number of connections to keep open inside the connection pool.
478 ## 0 indicates no limit
482 ## 0 indicates no limit
479 #sqlalchemy.db1.pool_size = 5
483 #sqlalchemy.db1.pool_size = 5
480
484
481 ## the number of connections to allow in connection pool "overflow", that is
485 ## the number of connections to allow in connection pool "overflow", that is
482 ## connections that can be opened above and beyond the pool_size setting,
486 ## connections that can be opened above and beyond the pool_size setting,
483 ## which defaults to five.
487 ## which defaults to five.
484 #sqlalchemy.db1.max_overflow = 10
488 #sqlalchemy.db1.max_overflow = 10
485
489
486
490
487 ##################
491 ##################
488 ### VCS CONFIG ###
492 ### VCS CONFIG ###
489 ##################
493 ##################
490 vcs.server.enable = true
494 vcs.server.enable = true
491 vcs.server = localhost:9900
495 vcs.server = localhost:9900
492
496
493 ## Web server connectivity protocol, responsible for web based VCS operatations
497 ## Web server connectivity protocol, responsible for web based VCS operatations
494 ## Available protocols are:
498 ## Available protocols are:
495 ## `pyro4` - using pyro4 server
499 ## `pyro4` - using pyro4 server
496 ## `http` - using http-rpc backend
500 ## `http` - using http-rpc backend
497 #vcs.server.protocol = http
501 #vcs.server.protocol = http
498
502
499 ## Push/Pull operations protocol, available options are:
503 ## Push/Pull operations protocol, available options are:
500 ## `pyro4` - using pyro4 server
504 ## `pyro4` - using pyro4 server
501 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
505 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
502 ## `vcsserver.scm_app` - internal app (EE only)
506 ## `vcsserver.scm_app` - internal app (EE only)
503 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
507 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
504
508
505 ## Push/Pull operations hooks protocol, available options are:
509 ## Push/Pull operations hooks protocol, available options are:
506 ## `pyro4` - using pyro4 server
510 ## `pyro4` - using pyro4 server
507 ## `http` - using http-rpc backend
511 ## `http` - using http-rpc backend
508 #vcs.hooks.protocol = http
512 #vcs.hooks.protocol = http
509
513
510 vcs.server.log_level = debug
514 vcs.server.log_level = debug
511 ## Start VCSServer with this instance as a subprocess, usefull for development
515 ## Start VCSServer with this instance as a subprocess, usefull for development
512 vcs.start_server = true
516 vcs.start_server = true
513 vcs.backends = hg, git, svn
517 vcs.backends = hg, git, svn
514 vcs.connection_timeout = 3600
518 vcs.connection_timeout = 3600
515 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
519 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
516 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
520 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
517 #vcs.svn.compatible_version = pre-1.8-compatible
521 #vcs.svn.compatible_version = pre-1.8-compatible
518
522
519 ################################
523 ################################
520 ### LOGGING CONFIGURATION ####
524 ### LOGGING CONFIGURATION ####
521 ################################
525 ################################
522 [loggers]
526 [loggers]
523 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
527 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
524
528
525 [handlers]
529 [handlers]
526 keys = console, console_sql
530 keys = console, console_sql
527
531
528 [formatters]
532 [formatters]
529 keys = generic, color_formatter, color_formatter_sql
533 keys = generic, color_formatter, color_formatter_sql
530
534
531 #############
535 #############
532 ## LOGGERS ##
536 ## LOGGERS ##
533 #############
537 #############
534 [logger_root]
538 [logger_root]
535 level = NOTSET
539 level = NOTSET
536 handlers = console
540 handlers = console
537
541
538 [logger_routes]
542 [logger_routes]
539 level = DEBUG
543 level = DEBUG
540 handlers =
544 handlers =
541 qualname = routes.middleware
545 qualname = routes.middleware
542 ## "level = DEBUG" logs the route matched and routing variables.
546 ## "level = DEBUG" logs the route matched and routing variables.
543 propagate = 1
547 propagate = 1
544
548
545 [logger_beaker]
549 [logger_beaker]
546 level = DEBUG
550 level = DEBUG
547 handlers =
551 handlers =
548 qualname = beaker.container
552 qualname = beaker.container
549 propagate = 1
553 propagate = 1
550
554
551 [logger_pyro4]
555 [logger_pyro4]
552 level = DEBUG
556 level = DEBUG
553 handlers =
557 handlers =
554 qualname = Pyro4
558 qualname = Pyro4
555 propagate = 1
559 propagate = 1
556
560
557 [logger_templates]
561 [logger_templates]
558 level = INFO
562 level = INFO
559 handlers =
563 handlers =
560 qualname = pylons.templating
564 qualname = pylons.templating
561 propagate = 1
565 propagate = 1
562
566
563 [logger_rhodecode]
567 [logger_rhodecode]
564 level = DEBUG
568 level = DEBUG
565 handlers =
569 handlers =
566 qualname = rhodecode
570 qualname = rhodecode
567 propagate = 1
571 propagate = 1
568
572
569 [logger_sqlalchemy]
573 [logger_sqlalchemy]
570 level = INFO
574 level = INFO
571 handlers = console_sql
575 handlers = console_sql
572 qualname = sqlalchemy.engine
576 qualname = sqlalchemy.engine
573 propagate = 0
577 propagate = 0
574
578
575 [logger_whoosh_indexer]
579 [logger_whoosh_indexer]
576 level = DEBUG
580 level = DEBUG
577 handlers =
581 handlers =
578 qualname = whoosh_indexer
582 qualname = whoosh_indexer
579 propagate = 1
583 propagate = 1
580
584
581 ##############
585 ##############
582 ## HANDLERS ##
586 ## HANDLERS ##
583 ##############
587 ##############
584
588
585 [handler_console]
589 [handler_console]
586 class = StreamHandler
590 class = StreamHandler
587 args = (sys.stderr,)
591 args = (sys.stderr,)
588 level = DEBUG
592 level = DEBUG
589 formatter = color_formatter
593 formatter = color_formatter
590
594
591 [handler_console_sql]
595 [handler_console_sql]
592 class = StreamHandler
596 class = StreamHandler
593 args = (sys.stderr,)
597 args = (sys.stderr,)
594 level = DEBUG
598 level = DEBUG
595 formatter = color_formatter_sql
599 formatter = color_formatter_sql
596
600
597 ################
601 ################
598 ## FORMATTERS ##
602 ## FORMATTERS ##
599 ################
603 ################
600
604
601 [formatter_generic]
605 [formatter_generic]
602 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
606 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
603 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
607 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
604 datefmt = %Y-%m-%d %H:%M:%S
608 datefmt = %Y-%m-%d %H:%M:%S
605
609
606 [formatter_color_formatter]
610 [formatter_color_formatter]
607 class = rhodecode.lib.logging_formatter.ColorFormatter
611 class = rhodecode.lib.logging_formatter.ColorFormatter
608 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
612 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
609 datefmt = %Y-%m-%d %H:%M:%S
613 datefmt = %Y-%m-%d %H:%M:%S
610
614
611 [formatter_color_formatter_sql]
615 [formatter_color_formatter_sql]
612 class = rhodecode.lib.logging_formatter.ColorFormatterSql
616 class = rhodecode.lib.logging_formatter.ColorFormatterSql
613 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
617 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
614 datefmt = %Y-%m-%d %H:%M:%S
618 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,583 +1,587 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode Enterprise - configuration file #
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 # Built-in functions and variables #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
6 # #
7 ################################################################################
7 ################################################################################
8
8
9 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the email address which should receive ##
12 ## Uncomment and replace with the email address which should receive ##
13 ## any error reports after an application crash ##
13 ## any error reports after an application crash ##
14 ## Additionally these settings will be used by the RhodeCode mailing system ##
14 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ## COMMON ##
32 ## COMMON ##
33 host = 127.0.0.1
33 host = 127.0.0.1
34 port = 5000
34 port = 5000
35
35
36 ##################################
36 ##################################
37 ## WAITRESS WSGI SERVER ##
37 ## WAITRESS WSGI SERVER ##
38 ## Recommended for Development ##
38 ## Recommended for Development ##
39 ##################################
39 ##################################
40 #use = egg:waitress#main
40 #use = egg:waitress#main
41 ## number of worker threads
41 ## number of worker threads
42 #threads = 5
42 #threads = 5
43 ## MAX BODY SIZE 100GB
43 ## MAX BODY SIZE 100GB
44 #max_request_body_size = 107374182400
44 #max_request_body_size = 107374182400
45 ## Use poll instead of select, fixes file descriptors limits problems.
45 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## May not work on old windows systems.
46 ## May not work on old windows systems.
47 #asyncore_use_poll = true
47 #asyncore_use_poll = true
48
48
49
49
50 ##########################
50 ##########################
51 ## GUNICORN WSGI SERVER ##
51 ## GUNICORN WSGI SERVER ##
52 ##########################
52 ##########################
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
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
78
79 ## prefix middleware for RhodeCode, disables force_https flag.
79 ## prefix middleware for RhodeCode, disables force_https flag.
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/<prefix>. Enable `filter-with =` option below as well.
81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 #[filter:proxy-prefix]
82 #[filter:proxy-prefix]
83 #use = egg:PasteDeploy#prefix
83 #use = egg:PasteDeploy#prefix
84 #prefix = /<your-prefix>
84 #prefix = /<your-prefix>
85
85
86 [app:main]
86 [app:main]
87 use = egg:rhodecode-enterprise-ce
87 use = egg:rhodecode-enterprise-ce
88 ## enable proxy prefix middleware, defined below
88 ## enable proxy prefix middleware, defined below
89 #filter-with = proxy-prefix
89 #filter-with = proxy-prefix
90
90
91 ## encryption key used to encrypt social plugin tokens,
91 ## encryption key used to encrypt social plugin tokens,
92 ## remote_urls with credentials etc, if not set it defaults to
92 ## remote_urls with credentials etc, if not set it defaults to
93 ## `beaker.session.secret`
93 ## `beaker.session.secret`
94 #rhodecode.encrypted_values.secret =
94 #rhodecode.encrypted_values.secret =
95
95
96 ## decryption strict mode (enabled by default). It controls if decryption raises
97 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
98 #rhodecode.encrypted_values.strict = false
99
96 full_stack = true
100 full_stack = true
97
101
98 ## Serve static files via RhodeCode, disable to serve them via HTTP server
102 ## Serve static files via RhodeCode, disable to serve them via HTTP server
99 static_files = true
103 static_files = true
100
104
101 # autogenerate javascript routes file on startup
105 # autogenerate javascript routes file on startup
102 generate_js_files = false
106 generate_js_files = false
103
107
104 ## Optional Languages
108 ## Optional Languages
105 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
109 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
106 lang = en
110 lang = en
107
111
108 ## perform a full repository scan on each server start, this should be
112 ## perform a full repository scan on each server start, this should be
109 ## set to false after first startup, to allow faster server restarts.
113 ## set to false after first startup, to allow faster server restarts.
110 startup.import_repos = false
114 startup.import_repos = false
111
115
112 ## Uncomment and set this path to use archive download cache.
116 ## Uncomment and set this path to use archive download cache.
113 ## Once enabled, generated archives will be cached at this location
117 ## Once enabled, generated archives will be cached at this location
114 ## and served from the cache during subsequent requests for the same archive of
118 ## and served from the cache during subsequent requests for the same archive of
115 ## the repository.
119 ## the repository.
116 #archive_cache_dir = /tmp/tarballcache
120 #archive_cache_dir = /tmp/tarballcache
117
121
118 ## change this to unique ID for security
122 ## change this to unique ID for security
119 app_instance_uuid = rc-production
123 app_instance_uuid = rc-production
120
124
121 ## cut off limit for large diffs (size in bytes)
125 ## cut off limit for large diffs (size in bytes)
122 cut_off_limit_diff = 1024000
126 cut_off_limit_diff = 1024000
123 cut_off_limit_file = 256000
127 cut_off_limit_file = 256000
124
128
125 ## use cache version of scm repo everywhere
129 ## use cache version of scm repo everywhere
126 vcs_full_cache = true
130 vcs_full_cache = true
127
131
128 ## force https in RhodeCode, fixes https redirects, assumes it's always https
132 ## force https in RhodeCode, fixes https redirects, assumes it's always https
129 ## Normally this is controlled by proper http flags sent from http server
133 ## Normally this is controlled by proper http flags sent from http server
130 force_https = false
134 force_https = false
131
135
132 ## use Strict-Transport-Security headers
136 ## use Strict-Transport-Security headers
133 use_htsts = false
137 use_htsts = false
134
138
135 ## number of commits stats will parse on each iteration
139 ## number of commits stats will parse on each iteration
136 commit_parse_limit = 25
140 commit_parse_limit = 25
137
141
138 ## git rev filter option, --all is the default filter, if you need to
142 ## git rev filter option, --all is the default filter, if you need to
139 ## hide all refs in changelog switch this to --branches --tags
143 ## hide all refs in changelog switch this to --branches --tags
140 git_rev_filter = --branches --tags
144 git_rev_filter = --branches --tags
141
145
142 # Set to true if your repos are exposed using the dumb protocol
146 # Set to true if your repos are exposed using the dumb protocol
143 git_update_server_info = false
147 git_update_server_info = false
144
148
145 ## RSS/ATOM feed options
149 ## RSS/ATOM feed options
146 rss_cut_off_limit = 256000
150 rss_cut_off_limit = 256000
147 rss_items_per_page = 10
151 rss_items_per_page = 10
148 rss_include_diff = false
152 rss_include_diff = false
149
153
150 ## gist URL alias, used to create nicer urls for gist. This should be an
154 ## gist URL alias, used to create nicer urls for gist. This should be an
151 ## url that does rewrites to _admin/gists/<gistid>.
155 ## url that does rewrites to _admin/gists/<gistid>.
152 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
156 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
153 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
157 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
154 gist_alias_url =
158 gist_alias_url =
155
159
156 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
160 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
157 ## used for access.
161 ## used for access.
158 ## Adding ?auth_token = <token> to the url authenticates this request as if it
162 ## Adding ?auth_token = <token> to the url authenticates this request as if it
159 ## came from the the logged in user who own this authentication token.
163 ## came from the the logged in user who own this authentication token.
160 ##
164 ##
161 ## Syntax is <ControllerClass>:<function_pattern>.
165 ## Syntax is <ControllerClass>:<function_pattern>.
162 ## To enable access to raw_files put `FilesController:raw`.
166 ## To enable access to raw_files put `FilesController:raw`.
163 ## To enable access to patches add `ChangesetController:changeset_patch`.
167 ## To enable access to patches add `ChangesetController:changeset_patch`.
164 ## The list should be "," separated and on a single line.
168 ## The list should be "," separated and on a single line.
165 ##
169 ##
166 ## Recommended controllers to enable:
170 ## Recommended controllers to enable:
167 # ChangesetController:changeset_patch,
171 # ChangesetController:changeset_patch,
168 # ChangesetController:changeset_raw,
172 # ChangesetController:changeset_raw,
169 # FilesController:raw,
173 # FilesController:raw,
170 # FilesController:archivefile,
174 # FilesController:archivefile,
171 # GistsController:*,
175 # GistsController:*,
172 api_access_controllers_whitelist =
176 api_access_controllers_whitelist =
173
177
174 ## default encoding used to convert from and to unicode
178 ## default encoding used to convert from and to unicode
175 ## can be also a comma separated list of encoding in case of mixed encodings
179 ## can be also a comma separated list of encoding in case of mixed encodings
176 default_encoding = UTF-8
180 default_encoding = UTF-8
177
181
178 ## instance-id prefix
182 ## instance-id prefix
179 ## a prefix key for this instance used for cache invalidation when running
183 ## a prefix key for this instance used for cache invalidation when running
180 ## multiple instances of rhodecode, make sure it's globally unique for
184 ## multiple instances of rhodecode, make sure it's globally unique for
181 ## all running rhodecode instances. Leave empty if you don't use it
185 ## all running rhodecode instances. Leave empty if you don't use it
182 instance_id =
186 instance_id =
183
187
184 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
188 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
185 ## of an authentication plugin also if it is disabled by it's settings.
189 ## of an authentication plugin also if it is disabled by it's settings.
186 ## This could be useful if you are unable to log in to the system due to broken
190 ## This could be useful if you are unable to log in to the system due to broken
187 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
191 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
188 ## module to log in again and fix the settings.
192 ## module to log in again and fix the settings.
189 ##
193 ##
190 ## Available builtin plugin IDs (hash is part of the ID):
194 ## Available builtin plugin IDs (hash is part of the ID):
191 ## egg:rhodecode-enterprise-ce#rhodecode
195 ## egg:rhodecode-enterprise-ce#rhodecode
192 ## egg:rhodecode-enterprise-ce#pam
196 ## egg:rhodecode-enterprise-ce#pam
193 ## egg:rhodecode-enterprise-ce#ldap
197 ## egg:rhodecode-enterprise-ce#ldap
194 ## egg:rhodecode-enterprise-ce#jasig_cas
198 ## egg:rhodecode-enterprise-ce#jasig_cas
195 ## egg:rhodecode-enterprise-ce#headers
199 ## egg:rhodecode-enterprise-ce#headers
196 ## egg:rhodecode-enterprise-ce#crowd
200 ## egg:rhodecode-enterprise-ce#crowd
197 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
201 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
198
202
199 ## alternative return HTTP header for failed authentication. Default HTTP
203 ## alternative return HTTP header for failed authentication. Default HTTP
200 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
204 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
201 ## handling that causing a series of failed authentication calls.
205 ## handling that causing a series of failed authentication calls.
202 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
206 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
203 ## This will be served instead of default 401 on bad authnetication
207 ## This will be served instead of default 401 on bad authnetication
204 auth_ret_code =
208 auth_ret_code =
205
209
206 ## use special detection method when serving auth_ret_code, instead of serving
210 ## use special detection method when serving auth_ret_code, instead of serving
207 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
211 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
208 ## and then serve auth_ret_code to clients
212 ## and then serve auth_ret_code to clients
209 auth_ret_code_detection = false
213 auth_ret_code_detection = false
210
214
211 ## locking return code. When repository is locked return this HTTP code. 2XX
215 ## locking return code. When repository is locked return this HTTP code. 2XX
212 ## codes don't break the transactions while 4XX codes do
216 ## codes don't break the transactions while 4XX codes do
213 lock_ret_code = 423
217 lock_ret_code = 423
214
218
215 ## allows to change the repository location in settings page
219 ## allows to change the repository location in settings page
216 allow_repo_location_change = true
220 allow_repo_location_change = true
217
221
218 ## allows to setup custom hooks in settings page
222 ## allows to setup custom hooks in settings page
219 allow_custom_hooks_settings = true
223 allow_custom_hooks_settings = true
220
224
221 ## generated license token, goto license page in RhodeCode settings to obtain
225 ## generated license token, goto license page in RhodeCode settings to obtain
222 ## new token
226 ## new token
223 license_token =
227 license_token =
224
228
225 ## supervisor connection uri, for managing supervisor and logs.
229 ## supervisor connection uri, for managing supervisor and logs.
226 supervisor.uri =
230 supervisor.uri =
227 ## supervisord group name/id we only want this RC instance to handle
231 ## supervisord group name/id we only want this RC instance to handle
228 supervisor.group_id = prod
232 supervisor.group_id = prod
229
233
230 ## Display extended labs settings
234 ## Display extended labs settings
231 labs_settings_active = true
235 labs_settings_active = true
232
236
233 ####################################
237 ####################################
234 ### CELERY CONFIG ####
238 ### CELERY CONFIG ####
235 ####################################
239 ####################################
236 use_celery = false
240 use_celery = false
237 broker.host = localhost
241 broker.host = localhost
238 broker.vhost = rabbitmqhost
242 broker.vhost = rabbitmqhost
239 broker.port = 5672
243 broker.port = 5672
240 broker.user = rabbitmq
244 broker.user = rabbitmq
241 broker.password = qweqwe
245 broker.password = qweqwe
242
246
243 celery.imports = rhodecode.lib.celerylib.tasks
247 celery.imports = rhodecode.lib.celerylib.tasks
244
248
245 celery.result.backend = amqp
249 celery.result.backend = amqp
246 celery.result.dburi = amqp://
250 celery.result.dburi = amqp://
247 celery.result.serialier = json
251 celery.result.serialier = json
248
252
249 #celery.send.task.error.emails = true
253 #celery.send.task.error.emails = true
250 #celery.amqp.task.result.expires = 18000
254 #celery.amqp.task.result.expires = 18000
251
255
252 celeryd.concurrency = 2
256 celeryd.concurrency = 2
253 #celeryd.log.file = celeryd.log
257 #celeryd.log.file = celeryd.log
254 celeryd.log.level = debug
258 celeryd.log.level = debug
255 celeryd.max.tasks.per.child = 1
259 celeryd.max.tasks.per.child = 1
256
260
257 ## tasks will never be sent to the queue, but executed locally instead.
261 ## tasks will never be sent to the queue, but executed locally instead.
258 celery.always.eager = false
262 celery.always.eager = false
259
263
260 ####################################
264 ####################################
261 ### BEAKER CACHE ####
265 ### BEAKER CACHE ####
262 ####################################
266 ####################################
263 # default cache dir for templates. Putting this into a ramdisk
267 # default cache dir for templates. Putting this into a ramdisk
264 ## can boost performance, eg. %(here)s/data_ramdisk
268 ## can boost performance, eg. %(here)s/data_ramdisk
265 cache_dir = %(here)s/data
269 cache_dir = %(here)s/data
266
270
267 ## locking and default file storage for Beaker. Putting this into a ramdisk
271 ## locking and default file storage for Beaker. Putting this into a ramdisk
268 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
272 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
269 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
273 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
270 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
274 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
271
275
272 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
276 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
273
277
274 beaker.cache.super_short_term.type = memory
278 beaker.cache.super_short_term.type = memory
275 beaker.cache.super_short_term.expire = 10
279 beaker.cache.super_short_term.expire = 10
276 beaker.cache.super_short_term.key_length = 256
280 beaker.cache.super_short_term.key_length = 256
277
281
278 beaker.cache.short_term.type = memory
282 beaker.cache.short_term.type = memory
279 beaker.cache.short_term.expire = 60
283 beaker.cache.short_term.expire = 60
280 beaker.cache.short_term.key_length = 256
284 beaker.cache.short_term.key_length = 256
281
285
282 beaker.cache.long_term.type = memory
286 beaker.cache.long_term.type = memory
283 beaker.cache.long_term.expire = 36000
287 beaker.cache.long_term.expire = 36000
284 beaker.cache.long_term.key_length = 256
288 beaker.cache.long_term.key_length = 256
285
289
286 beaker.cache.sql_cache_short.type = memory
290 beaker.cache.sql_cache_short.type = memory
287 beaker.cache.sql_cache_short.expire = 10
291 beaker.cache.sql_cache_short.expire = 10
288 beaker.cache.sql_cache_short.key_length = 256
292 beaker.cache.sql_cache_short.key_length = 256
289
293
290 # default is memory cache, configure only if required
294 # default is memory cache, configure only if required
291 # using multi-node or multi-worker setup
295 # using multi-node or multi-worker setup
292 #beaker.cache.auth_plugins.type = ext:database
296 #beaker.cache.auth_plugins.type = ext:database
293 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
297 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
294 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
298 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
295 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
299 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
296 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
300 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
297 #beaker.cache.auth_plugins.sa.pool_size = 10
301 #beaker.cache.auth_plugins.sa.pool_size = 10
298 #beaker.cache.auth_plugins.sa.max_overflow = 0
302 #beaker.cache.auth_plugins.sa.max_overflow = 0
299
303
300 beaker.cache.repo_cache_long.type = memorylru_base
304 beaker.cache.repo_cache_long.type = memorylru_base
301 beaker.cache.repo_cache_long.max_items = 4096
305 beaker.cache.repo_cache_long.max_items = 4096
302 beaker.cache.repo_cache_long.expire = 2592000
306 beaker.cache.repo_cache_long.expire = 2592000
303
307
304 # default is memorylru_base cache, configure only if required
308 # default is memorylru_base cache, configure only if required
305 # using multi-node or multi-worker setup
309 # using multi-node or multi-worker setup
306 #beaker.cache.repo_cache_long.type = ext:memcached
310 #beaker.cache.repo_cache_long.type = ext:memcached
307 #beaker.cache.repo_cache_long.url = localhost:11211
311 #beaker.cache.repo_cache_long.url = localhost:11211
308 #beaker.cache.repo_cache_long.expire = 1209600
312 #beaker.cache.repo_cache_long.expire = 1209600
309 #beaker.cache.repo_cache_long.key_length = 256
313 #beaker.cache.repo_cache_long.key_length = 256
310
314
311 ####################################
315 ####################################
312 ### BEAKER SESSION ####
316 ### BEAKER SESSION ####
313 ####################################
317 ####################################
314
318
315 ## .session.type is type of storage options for the session, current allowed
319 ## .session.type is type of storage options for the session, current allowed
316 ## types are file, ext:memcached, ext:database, and memory (default).
320 ## types are file, ext:memcached, ext:database, and memory (default).
317 beaker.session.type = file
321 beaker.session.type = file
318 beaker.session.data_dir = %(here)s/data/sessions/data
322 beaker.session.data_dir = %(here)s/data/sessions/data
319
323
320 ## db based session, fast, and allows easy management over logged in users ##
324 ## db based session, fast, and allows easy management over logged in users ##
321 #beaker.session.type = ext:database
325 #beaker.session.type = ext:database
322 #beaker.session.table_name = db_session
326 #beaker.session.table_name = db_session
323 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
327 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
324 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
328 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
325 #beaker.session.sa.pool_recycle = 3600
329 #beaker.session.sa.pool_recycle = 3600
326 #beaker.session.sa.echo = false
330 #beaker.session.sa.echo = false
327
331
328 beaker.session.key = rhodecode
332 beaker.session.key = rhodecode
329 beaker.session.secret = production-rc-uytcxaz
333 beaker.session.secret = production-rc-uytcxaz
330 beaker.session.lock_dir = %(here)s/data/sessions/lock
334 beaker.session.lock_dir = %(here)s/data/sessions/lock
331
335
332 ## Secure encrypted cookie. Requires AES and AES python libraries
336 ## Secure encrypted cookie. Requires AES and AES python libraries
333 ## you must disable beaker.session.secret to use this
337 ## you must disable beaker.session.secret to use this
334 #beaker.session.encrypt_key = <key_for_encryption>
338 #beaker.session.encrypt_key = <key_for_encryption>
335 #beaker.session.validate_key = <validation_key>
339 #beaker.session.validate_key = <validation_key>
336
340
337 ## sets session as invalid(also logging out user) if it haven not been
341 ## sets session as invalid(also logging out user) if it haven not been
338 ## accessed for given amount of time in seconds
342 ## accessed for given amount of time in seconds
339 beaker.session.timeout = 2592000
343 beaker.session.timeout = 2592000
340 beaker.session.httponly = true
344 beaker.session.httponly = true
341 #beaker.session.cookie_path = /<your-prefix>
345 #beaker.session.cookie_path = /<your-prefix>
342
346
343 ## uncomment for https secure cookie
347 ## uncomment for https secure cookie
344 beaker.session.secure = false
348 beaker.session.secure = false
345
349
346 ## auto save the session to not to use .save()
350 ## auto save the session to not to use .save()
347 beaker.session.auto = false
351 beaker.session.auto = false
348
352
349 ## default cookie expiration time in seconds, set to `true` to set expire
353 ## default cookie expiration time in seconds, set to `true` to set expire
350 ## at browser close
354 ## at browser close
351 #beaker.session.cookie_expires = 3600
355 #beaker.session.cookie_expires = 3600
352
356
353 ###################################
357 ###################################
354 ## SEARCH INDEXING CONFIGURATION ##
358 ## SEARCH INDEXING CONFIGURATION ##
355 ###################################
359 ###################################
356 ## Full text search indexer is available in rhodecode-tools under
360 ## Full text search indexer is available in rhodecode-tools under
357 ## `rhodecode-tools index` command
361 ## `rhodecode-tools index` command
358
362
359 # WHOOSH Backend, doesn't require additional services to run
363 # WHOOSH Backend, doesn't require additional services to run
360 # it works good with few dozen repos
364 # it works good with few dozen repos
361 search.module = rhodecode.lib.index.whoosh
365 search.module = rhodecode.lib.index.whoosh
362 search.location = %(here)s/data/index
366 search.location = %(here)s/data/index
363
367
364 ###################################
368 ###################################
365 ## APPENLIGHT CONFIG ##
369 ## APPENLIGHT CONFIG ##
366 ###################################
370 ###################################
367
371
368 ## Appenlight is tailored to work with RhodeCode, see
372 ## Appenlight is tailored to work with RhodeCode, see
369 ## http://appenlight.com for details how to obtain an account
373 ## http://appenlight.com for details how to obtain an account
370
374
371 ## appenlight integration enabled
375 ## appenlight integration enabled
372 appenlight = false
376 appenlight = false
373
377
374 appenlight.server_url = https://api.appenlight.com
378 appenlight.server_url = https://api.appenlight.com
375 appenlight.api_key = YOUR_API_KEY
379 appenlight.api_key = YOUR_API_KEY
376 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
380 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
377
381
378 # used for JS client
382 # used for JS client
379 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
383 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
380
384
381 ## TWEAK AMOUNT OF INFO SENT HERE
385 ## TWEAK AMOUNT OF INFO SENT HERE
382
386
383 ## enables 404 error logging (default False)
387 ## enables 404 error logging (default False)
384 appenlight.report_404 = false
388 appenlight.report_404 = false
385
389
386 ## time in seconds after request is considered being slow (default 1)
390 ## time in seconds after request is considered being slow (default 1)
387 appenlight.slow_request_time = 1
391 appenlight.slow_request_time = 1
388
392
389 ## record slow requests in application
393 ## record slow requests in application
390 ## (needs to be enabled for slow datastore recording and time tracking)
394 ## (needs to be enabled for slow datastore recording and time tracking)
391 appenlight.slow_requests = true
395 appenlight.slow_requests = true
392
396
393 ## enable hooking to application loggers
397 ## enable hooking to application loggers
394 appenlight.logging = true
398 appenlight.logging = true
395
399
396 ## minimum log level for log capture
400 ## minimum log level for log capture
397 appenlight.logging.level = WARNING
401 appenlight.logging.level = WARNING
398
402
399 ## send logs only from erroneous/slow requests
403 ## send logs only from erroneous/slow requests
400 ## (saves API quota for intensive logging)
404 ## (saves API quota for intensive logging)
401 appenlight.logging_on_error = false
405 appenlight.logging_on_error = false
402
406
403 ## list of additonal keywords that should be grabbed from environ object
407 ## list of additonal keywords that should be grabbed from environ object
404 ## can be string with comma separated list of words in lowercase
408 ## can be string with comma separated list of words in lowercase
405 ## (by default client will always send following info:
409 ## (by default client will always send following info:
406 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
410 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
407 ## start with HTTP* this list be extended with additional keywords here
411 ## start with HTTP* this list be extended with additional keywords here
408 appenlight.environ_keys_whitelist =
412 appenlight.environ_keys_whitelist =
409
413
410 ## list of keywords that should be blanked from request object
414 ## list of keywords that should be blanked from request object
411 ## can be string with comma separated list of words in lowercase
415 ## can be string with comma separated list of words in lowercase
412 ## (by default client will always blank keys that contain following words
416 ## (by default client will always blank keys that contain following words
413 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
417 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
414 ## this list be extended with additional keywords set here
418 ## this list be extended with additional keywords set here
415 appenlight.request_keys_blacklist =
419 appenlight.request_keys_blacklist =
416
420
417 ## list of namespaces that should be ignores when gathering log entries
421 ## list of namespaces that should be ignores when gathering log entries
418 ## can be string with comma separated list of namespaces
422 ## can be string with comma separated list of namespaces
419 ## (by default the client ignores own entries: appenlight_client.client)
423 ## (by default the client ignores own entries: appenlight_client.client)
420 appenlight.log_namespace_blacklist =
424 appenlight.log_namespace_blacklist =
421
425
422
426
423 ################################################################################
427 ################################################################################
424 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
428 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
425 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
429 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
426 ## execute malicious code after an exception is raised. ##
430 ## execute malicious code after an exception is raised. ##
427 ################################################################################
431 ################################################################################
428 set debug = false
432 set debug = false
429
433
430
434
431 #########################################################
435 #########################################################
432 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
436 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
433 #########################################################
437 #########################################################
434 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
438 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
435 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
439 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
436 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
440 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
437
441
438 # see sqlalchemy docs for other advanced settings
442 # see sqlalchemy docs for other advanced settings
439
443
440 ## print the sql statements to output
444 ## print the sql statements to output
441 sqlalchemy.db1.echo = false
445 sqlalchemy.db1.echo = false
442 ## recycle the connections after this ammount of seconds
446 ## recycle the connections after this ammount of seconds
443 sqlalchemy.db1.pool_recycle = 3600
447 sqlalchemy.db1.pool_recycle = 3600
444 sqlalchemy.db1.convert_unicode = true
448 sqlalchemy.db1.convert_unicode = true
445
449
446 ## the number of connections to keep open inside the connection pool.
450 ## the number of connections to keep open inside the connection pool.
447 ## 0 indicates no limit
451 ## 0 indicates no limit
448 #sqlalchemy.db1.pool_size = 5
452 #sqlalchemy.db1.pool_size = 5
449
453
450 ## the number of connections to allow in connection pool "overflow", that is
454 ## the number of connections to allow in connection pool "overflow", that is
451 ## connections that can be opened above and beyond the pool_size setting,
455 ## connections that can be opened above and beyond the pool_size setting,
452 ## which defaults to five.
456 ## which defaults to five.
453 #sqlalchemy.db1.max_overflow = 10
457 #sqlalchemy.db1.max_overflow = 10
454
458
455
459
456 ##################
460 ##################
457 ### VCS CONFIG ###
461 ### VCS CONFIG ###
458 ##################
462 ##################
459 vcs.server.enable = true
463 vcs.server.enable = true
460 vcs.server = localhost:9900
464 vcs.server = localhost:9900
461
465
462 ## Web server connectivity protocol, responsible for web based VCS operatations
466 ## Web server connectivity protocol, responsible for web based VCS operatations
463 ## Available protocols are:
467 ## Available protocols are:
464 ## `pyro4` - using pyro4 server
468 ## `pyro4` - using pyro4 server
465 ## `http` - using http-rpc backend
469 ## `http` - using http-rpc backend
466 #vcs.server.protocol = http
470 #vcs.server.protocol = http
467
471
468 ## Push/Pull operations protocol, available options are:
472 ## Push/Pull operations protocol, available options are:
469 ## `pyro4` - using pyro4 server
473 ## `pyro4` - using pyro4 server
470 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
474 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
471 ## `vcsserver.scm_app` - internal app (EE only)
475 ## `vcsserver.scm_app` - internal app (EE only)
472 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
476 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
473
477
474 ## Push/Pull operations hooks protocol, available options are:
478 ## Push/Pull operations hooks protocol, available options are:
475 ## `pyro4` - using pyro4 server
479 ## `pyro4` - using pyro4 server
476 ## `http` - using http-rpc backend
480 ## `http` - using http-rpc backend
477 #vcs.hooks.protocol = http
481 #vcs.hooks.protocol = http
478
482
479 vcs.server.log_level = info
483 vcs.server.log_level = info
480 ## Start VCSServer with this instance as a subprocess, usefull for development
484 ## Start VCSServer with this instance as a subprocess, usefull for development
481 vcs.start_server = false
485 vcs.start_server = false
482 vcs.backends = hg, git, svn
486 vcs.backends = hg, git, svn
483 vcs.connection_timeout = 3600
487 vcs.connection_timeout = 3600
484 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
488 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
485 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
489 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
486 #vcs.svn.compatible_version = pre-1.8-compatible
490 #vcs.svn.compatible_version = pre-1.8-compatible
487
491
488 ################################
492 ################################
489 ### LOGGING CONFIGURATION ####
493 ### LOGGING CONFIGURATION ####
490 ################################
494 ################################
491 [loggers]
495 [loggers]
492 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
496 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
493
497
494 [handlers]
498 [handlers]
495 keys = console, console_sql
499 keys = console, console_sql
496
500
497 [formatters]
501 [formatters]
498 keys = generic, color_formatter, color_formatter_sql
502 keys = generic, color_formatter, color_formatter_sql
499
503
500 #############
504 #############
501 ## LOGGERS ##
505 ## LOGGERS ##
502 #############
506 #############
503 [logger_root]
507 [logger_root]
504 level = NOTSET
508 level = NOTSET
505 handlers = console
509 handlers = console
506
510
507 [logger_routes]
511 [logger_routes]
508 level = DEBUG
512 level = DEBUG
509 handlers =
513 handlers =
510 qualname = routes.middleware
514 qualname = routes.middleware
511 ## "level = DEBUG" logs the route matched and routing variables.
515 ## "level = DEBUG" logs the route matched and routing variables.
512 propagate = 1
516 propagate = 1
513
517
514 [logger_beaker]
518 [logger_beaker]
515 level = DEBUG
519 level = DEBUG
516 handlers =
520 handlers =
517 qualname = beaker.container
521 qualname = beaker.container
518 propagate = 1
522 propagate = 1
519
523
520 [logger_pyro4]
524 [logger_pyro4]
521 level = DEBUG
525 level = DEBUG
522 handlers =
526 handlers =
523 qualname = Pyro4
527 qualname = Pyro4
524 propagate = 1
528 propagate = 1
525
529
526 [logger_templates]
530 [logger_templates]
527 level = INFO
531 level = INFO
528 handlers =
532 handlers =
529 qualname = pylons.templating
533 qualname = pylons.templating
530 propagate = 1
534 propagate = 1
531
535
532 [logger_rhodecode]
536 [logger_rhodecode]
533 level = DEBUG
537 level = DEBUG
534 handlers =
538 handlers =
535 qualname = rhodecode
539 qualname = rhodecode
536 propagate = 1
540 propagate = 1
537
541
538 [logger_sqlalchemy]
542 [logger_sqlalchemy]
539 level = INFO
543 level = INFO
540 handlers = console_sql
544 handlers = console_sql
541 qualname = sqlalchemy.engine
545 qualname = sqlalchemy.engine
542 propagate = 0
546 propagate = 0
543
547
544 [logger_whoosh_indexer]
548 [logger_whoosh_indexer]
545 level = DEBUG
549 level = DEBUG
546 handlers =
550 handlers =
547 qualname = whoosh_indexer
551 qualname = whoosh_indexer
548 propagate = 1
552 propagate = 1
549
553
550 ##############
554 ##############
551 ## HANDLERS ##
555 ## HANDLERS ##
552 ##############
556 ##############
553
557
554 [handler_console]
558 [handler_console]
555 class = StreamHandler
559 class = StreamHandler
556 args = (sys.stderr,)
560 args = (sys.stderr,)
557 level = INFO
561 level = INFO
558 formatter = generic
562 formatter = generic
559
563
560 [handler_console_sql]
564 [handler_console_sql]
561 class = StreamHandler
565 class = StreamHandler
562 args = (sys.stderr,)
566 args = (sys.stderr,)
563 level = WARN
567 level = WARN
564 formatter = generic
568 formatter = generic
565
569
566 ################
570 ################
567 ## FORMATTERS ##
571 ## FORMATTERS ##
568 ################
572 ################
569
573
570 [formatter_generic]
574 [formatter_generic]
571 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
575 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
572 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
576 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
573 datefmt = %Y-%m-%d %H:%M:%S
577 datefmt = %Y-%m-%d %H:%M:%S
574
578
575 [formatter_color_formatter]
579 [formatter_color_formatter]
576 class = rhodecode.lib.logging_formatter.ColorFormatter
580 class = rhodecode.lib.logging_formatter.ColorFormatter
577 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
581 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
578 datefmt = %Y-%m-%d %H:%M:%S
582 datefmt = %Y-%m-%d %H:%M:%S
579
583
580 [formatter_color_formatter_sql]
584 [formatter_color_formatter_sql]
581 class = rhodecode.lib.logging_formatter.ColorFormatterSql
585 class = rhodecode.lib.logging_formatter.ColorFormatterSql
582 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
586 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
583 datefmt = %Y-%m-%d %H:%M:%S
587 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,61 +1,114 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 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 """
22 """
23 Generic encryption library for RhodeCode
23 Generic encryption library for RhodeCode
24 """
24 """
25
25
26 import hashlib
27 import base64
26 import base64
28
27
29 from Crypto.Cipher import AES
28 from Crypto.Cipher import AES
30 from Crypto import Random
29 from Crypto import Random
30 from Crypto.Hash import HMAC, SHA256
31
31
32 from rhodecode.lib.utils2 import safe_str
32 from rhodecode.lib.utils2 import safe_str
33
33
34
34
35 class SignatureVerificationError(Exception):
36 pass
37
38
39 class InvalidDecryptedValue(str):
40
41 def __new__(cls, content):
42 """
43 This will generate something like this::
44 <InvalidDecryptedValue(QkWusFgLJXR6m42v...)>
45 And represent a safe indicator that encryption key is broken
46 """
47 content = '<{}({}...)>'.format(cls.__name__, content[:16])
48 return str.__new__(cls, content)
49
50
35 class AESCipher(object):
51 class AESCipher(object):
36 def __init__(self, key):
52 def __init__(self, key, hmac=False, strict_verification=True):
37 # create padding, trim to long enc key
38 if not key:
53 if not key:
39 raise ValueError('passed key variable is empty')
54 raise ValueError('passed key variable is empty')
55 self.strict_verification = strict_verification
40 self.block_size = 32
56 self.block_size = 32
41 self.key = hashlib.sha256(safe_str(key)).digest()
57 self.hmac_size = 32
58 self.hmac = hmac
59
60 self.key = SHA256.new(safe_str(key)).digest()
61 self.hmac_key = SHA256.new(self.key).digest()
62
63 def verify_hmac_signature(self, raw_data):
64 org_hmac_signature = raw_data[-self.hmac_size:]
65 data_without_sig = raw_data[:-self.hmac_size]
66 recomputed_hmac = HMAC.new(
67 self.hmac_key, data_without_sig, digestmod=SHA256).digest()
68 return org_hmac_signature == recomputed_hmac
42
69
43 def encrypt(self, raw):
70 def encrypt(self, raw):
44 raw = self._pad(raw)
71 raw = self._pad(raw)
45 iv = Random.new().read(AES.block_size)
72 iv = Random.new().read(AES.block_size)
46 cipher = AES.new(self.key, AES.MODE_CBC, iv)
73 cipher = AES.new(self.key, AES.MODE_CBC, iv)
47 return base64.b64encode(iv + cipher.encrypt(raw))
74 enc_value = cipher.encrypt(raw)
75
76 hmac_signature = ''
77 if self.hmac:
78 # compute hmac+sha256 on iv + enc text, we use
79 # encrypt then mac method to create the signature
80 hmac_signature = HMAC.new(
81 self.hmac_key, iv + enc_value, digestmod=SHA256).digest()
82
83 return base64.b64encode(iv + enc_value + hmac_signature)
48
84
49 def decrypt(self, enc):
85 def decrypt(self, enc):
86 enc_org = enc
50 enc = base64.b64decode(enc)
87 enc = base64.b64decode(enc)
88
89 if self.hmac and len(enc) > self.hmac_size:
90 if self.verify_hmac_signature(enc):
91 # cut off the HMAC verification digest
92 enc = enc[:-self.hmac_size]
93 else:
94 if self.strict_verification:
95 raise SignatureVerificationError(
96 "Encryption signature verification failed. "
97 "Please check your secret key, and/or encrypted value. "
98 "Secret key is stored as "
99 "`rhodecode.encrypted_values.secret` or "
100 "`beaker.session.secret` inside .ini file")
101
102 return InvalidDecryptedValue(enc_org)
103
51 iv = enc[:AES.block_size]
104 iv = enc[:AES.block_size]
52 cipher = AES.new(self.key, AES.MODE_CBC, iv)
105 cipher = AES.new(self.key, AES.MODE_CBC, iv)
53 return self._unpad(cipher.decrypt(enc[AES.block_size:]))
106 return self._unpad(cipher.decrypt(enc[AES.block_size:]))
54
107
55 def _pad(self, s):
108 def _pad(self, s):
56 return (s + (self.block_size - len(s) % self.block_size)
109 return (s + (self.block_size - len(s) % self.block_size)
57 * chr(self.block_size - len(s) % self.block_size))
110 * chr(self.block_size - len(s) % self.block_size))
58
111
59 @staticmethod
112 @staticmethod
60 def _unpad(s):
113 def _unpad(s):
61 return s[:-ord(s[len(s)-1:])] No newline at end of file
114 return s[:-ord(s[len(s)-1:])]
@@ -1,3462 +1,3476 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import os
25 import os
26 import sys
26 import sys
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.exc import IntegrityError
39 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.orm import (
42 from sqlalchemy.orm import (
43 relationship, joinedload, class_mapper, validates, aliased)
43 relationship, joinedload, class_mapper, validates, aliased)
44 from sqlalchemy.sql.expression import true
44 from sqlalchemy.sql.expression import true
45 from beaker.cache import cache_region, region_invalidate
45 from beaker.cache import cache_region, region_invalidate
46 from webob.exc import HTTPNotFound
46 from webob.exc import HTTPNotFound
47 from zope.cachedescriptors.property import Lazy as LazyProperty
47 from zope.cachedescriptors.property import Lazy as LazyProperty
48
48
49 from pylons import url
49 from pylons import url
50 from pylons.i18n.translation import lazy_ugettext as _
50 from pylons.i18n.translation import lazy_ugettext as _
51
51
52 from rhodecode.lib.vcs import get_backend
52 from rhodecode.lib.vcs import get_backend
53 from rhodecode.lib.vcs.utils.helpers import get_scm
53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.exceptions import VCSError
54 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.backends.base import (
55 from rhodecode.lib.vcs.backends.base import (
56 EmptyCommit, Reference, MergeFailureReason)
56 EmptyCommit, Reference, MergeFailureReason)
57 from rhodecode.lib.utils2 import (
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 from rhodecode.lib.ext_json import json
60 from rhodecode.lib.ext_json import json
61 from rhodecode.lib.caching_query import FromCache
61 from rhodecode.lib.caching_query import FromCache
62 from rhodecode.lib.encrypt import AESCipher
62 from rhodecode.lib.encrypt import AESCipher
63
63
64 from rhodecode.model.meta import Base, Session
64 from rhodecode.model.meta import Base, Session
65
65
66 URL_SEP = '/'
66 URL_SEP = '/'
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69 # =============================================================================
69 # =============================================================================
70 # BASE CLASSES
70 # BASE CLASSES
71 # =============================================================================
71 # =============================================================================
72
72
73 # this is propagated from .ini file beaker.session.secret
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
74 # beaker.session.secret if first is not set.
74 # and initialized at environment.py
75 # and initialized at environment.py
75 ENCRYPTION_KEY = None
76 ENCRYPTION_KEY = None
76
77
77 # used to sort permissions by types, '#' used here is not allowed to be in
78 # used to sort permissions by types, '#' used here is not allowed to be in
78 # usernames, and it's very early in sorted string.printable table.
79 # usernames, and it's very early in sorted string.printable table.
79 PERMISSION_TYPE_SORT = {
80 PERMISSION_TYPE_SORT = {
80 'admin': '####',
81 'admin': '####',
81 'write': '###',
82 'write': '###',
82 'read': '##',
83 'read': '##',
83 'none': '#',
84 'none': '#',
84 }
85 }
85
86
86
87
87 def display_sort(obj):
88 def display_sort(obj):
88 """
89 """
89 Sort function used to sort permissions in .permissions() function of
90 Sort function used to sort permissions in .permissions() function of
90 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 of all other resources
92 of all other resources
92 """
93 """
93
94
94 if obj.username == User.DEFAULT_USER:
95 if obj.username == User.DEFAULT_USER:
95 return '#####'
96 return '#####'
96 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 return prefix + obj.username
98 return prefix + obj.username
98
99
99
100
100 def _hash_key(k):
101 def _hash_key(k):
101 return md5_safe(k)
102 return md5_safe(k)
102
103
103
104
104 class EncryptedTextValue(TypeDecorator):
105 class EncryptedTextValue(TypeDecorator):
105 """
106 """
106 Special column for encrypted long text data, use like::
107 Special column for encrypted long text data, use like::
107
108
108 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109
110
110 This column is intelligent so if value is in unencrypted form it return
111 This column is intelligent so if value is in unencrypted form it return
111 unencrypted form, but on save it always encrypts
112 unencrypted form, but on save it always encrypts
112 """
113 """
113 impl = Text
114 impl = Text
114
115
115 def process_bind_param(self, value, dialect):
116 def process_bind_param(self, value, dialect):
116 if not value:
117 if not value:
117 return value
118 return value
118 if value.startswith('enc$aes$'):
119 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
119 # protect against double encrypting if someone manually starts
120 # protect against double encrypting if someone manually starts
120 # doing
121 # doing
121 raise ValueError('value needs to be in unencrypted format, ie. '
122 raise ValueError('value needs to be in unencrypted format, ie. '
122 'not starting with enc$aes$')
123 'not starting with enc$aes')
123 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
124 return 'enc$aes_hmac$%s' % AESCipher(
125 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
126
125 def process_result_value(self, value, dialect):
127 def process_result_value(self, value, dialect):
128 import rhodecode
129
126 if not value:
130 if not value:
127 return value
131 return value
128
132
129 parts = value.split('$', 3)
133 parts = value.split('$', 3)
130 if not len(parts) == 3:
134 if not len(parts) == 3:
131 # probably not encrypted values
135 # probably not encrypted values
132 return value
136 return value
133 else:
137 else:
134 if parts[0] != 'enc':
138 if parts[0] != 'enc':
135 # parts ok but without our header ?
139 # parts ok but without our header ?
136 return value
140 return value
137
141 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
142 'rhodecode.encrypted_values.strict') or True)
138 # at that stage we know it's our encryption
143 # at that stage we know it's our encryption
139 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 if parts[1] == 'aes':
145 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
146 elif parts[1] == 'aes_hmac':
147 decrypted_data = AESCipher(
148 ENCRYPTION_KEY, hmac=True,
149 strict_verification=enc_strict_mode).decrypt(parts[2])
150 else:
151 raise ValueError(
152 'Encryption type part is wrong, must be `aes` '
153 'or `aes_hmac`, got `%s` instead' % (parts[1]))
140 return decrypted_data
154 return decrypted_data
141
155
142
156
143 class BaseModel(object):
157 class BaseModel(object):
144 """
158 """
145 Base Model for all classes
159 Base Model for all classes
146 """
160 """
147
161
148 @classmethod
162 @classmethod
149 def _get_keys(cls):
163 def _get_keys(cls):
150 """return column names for this model """
164 """return column names for this model """
151 return class_mapper(cls).c.keys()
165 return class_mapper(cls).c.keys()
152
166
153 def get_dict(self):
167 def get_dict(self):
154 """
168 """
155 return dict with keys and values corresponding
169 return dict with keys and values corresponding
156 to this model data """
170 to this model data """
157
171
158 d = {}
172 d = {}
159 for k in self._get_keys():
173 for k in self._get_keys():
160 d[k] = getattr(self, k)
174 d[k] = getattr(self, k)
161
175
162 # also use __json__() if present to get additional fields
176 # also use __json__() if present to get additional fields
163 _json_attr = getattr(self, '__json__', None)
177 _json_attr = getattr(self, '__json__', None)
164 if _json_attr:
178 if _json_attr:
165 # update with attributes from __json__
179 # update with attributes from __json__
166 if callable(_json_attr):
180 if callable(_json_attr):
167 _json_attr = _json_attr()
181 _json_attr = _json_attr()
168 for k, val in _json_attr.iteritems():
182 for k, val in _json_attr.iteritems():
169 d[k] = val
183 d[k] = val
170 return d
184 return d
171
185
172 def get_appstruct(self):
186 def get_appstruct(self):
173 """return list with keys and values tuples corresponding
187 """return list with keys and values tuples corresponding
174 to this model data """
188 to this model data """
175
189
176 l = []
190 l = []
177 for k in self._get_keys():
191 for k in self._get_keys():
178 l.append((k, getattr(self, k),))
192 l.append((k, getattr(self, k),))
179 return l
193 return l
180
194
181 def populate_obj(self, populate_dict):
195 def populate_obj(self, populate_dict):
182 """populate model with data from given populate_dict"""
196 """populate model with data from given populate_dict"""
183
197
184 for k in self._get_keys():
198 for k in self._get_keys():
185 if k in populate_dict:
199 if k in populate_dict:
186 setattr(self, k, populate_dict[k])
200 setattr(self, k, populate_dict[k])
187
201
188 @classmethod
202 @classmethod
189 def query(cls):
203 def query(cls):
190 return Session().query(cls)
204 return Session().query(cls)
191
205
192 @classmethod
206 @classmethod
193 def get(cls, id_):
207 def get(cls, id_):
194 if id_:
208 if id_:
195 return cls.query().get(id_)
209 return cls.query().get(id_)
196
210
197 @classmethod
211 @classmethod
198 def get_or_404(cls, id_):
212 def get_or_404(cls, id_):
199 try:
213 try:
200 id_ = int(id_)
214 id_ = int(id_)
201 except (TypeError, ValueError):
215 except (TypeError, ValueError):
202 raise HTTPNotFound
216 raise HTTPNotFound
203
217
204 res = cls.query().get(id_)
218 res = cls.query().get(id_)
205 if not res:
219 if not res:
206 raise HTTPNotFound
220 raise HTTPNotFound
207 return res
221 return res
208
222
209 @classmethod
223 @classmethod
210 def getAll(cls):
224 def getAll(cls):
211 # deprecated and left for backward compatibility
225 # deprecated and left for backward compatibility
212 return cls.get_all()
226 return cls.get_all()
213
227
214 @classmethod
228 @classmethod
215 def get_all(cls):
229 def get_all(cls):
216 return cls.query().all()
230 return cls.query().all()
217
231
218 @classmethod
232 @classmethod
219 def delete(cls, id_):
233 def delete(cls, id_):
220 obj = cls.query().get(id_)
234 obj = cls.query().get(id_)
221 Session().delete(obj)
235 Session().delete(obj)
222
236
223 @classmethod
237 @classmethod
224 def identity_cache(cls, session, attr_name, value):
238 def identity_cache(cls, session, attr_name, value):
225 exist_in_session = []
239 exist_in_session = []
226 for (item_cls, pkey), instance in session.identity_map.items():
240 for (item_cls, pkey), instance in session.identity_map.items():
227 if cls == item_cls and getattr(instance, attr_name) == value:
241 if cls == item_cls and getattr(instance, attr_name) == value:
228 exist_in_session.append(instance)
242 exist_in_session.append(instance)
229 if exist_in_session:
243 if exist_in_session:
230 if len(exist_in_session) == 1:
244 if len(exist_in_session) == 1:
231 return exist_in_session[0]
245 return exist_in_session[0]
232 log.exception(
246 log.exception(
233 'multiple objects with attr %s and '
247 'multiple objects with attr %s and '
234 'value %s found with same name: %r',
248 'value %s found with same name: %r',
235 attr_name, value, exist_in_session)
249 attr_name, value, exist_in_session)
236
250
237 def __repr__(self):
251 def __repr__(self):
238 if hasattr(self, '__unicode__'):
252 if hasattr(self, '__unicode__'):
239 # python repr needs to return str
253 # python repr needs to return str
240 try:
254 try:
241 return safe_str(self.__unicode__())
255 return safe_str(self.__unicode__())
242 except UnicodeDecodeError:
256 except UnicodeDecodeError:
243 pass
257 pass
244 return '<DB:%s>' % (self.__class__.__name__)
258 return '<DB:%s>' % (self.__class__.__name__)
245
259
246
260
247 class RhodeCodeSetting(Base, BaseModel):
261 class RhodeCodeSetting(Base, BaseModel):
248 __tablename__ = 'rhodecode_settings'
262 __tablename__ = 'rhodecode_settings'
249 __table_args__ = (
263 __table_args__ = (
250 UniqueConstraint('app_settings_name'),
264 UniqueConstraint('app_settings_name'),
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
253 )
267 )
254
268
255 SETTINGS_TYPES = {
269 SETTINGS_TYPES = {
256 'str': safe_str,
270 'str': safe_str,
257 'int': safe_int,
271 'int': safe_int,
258 'unicode': safe_unicode,
272 'unicode': safe_unicode,
259 'bool': str2bool,
273 'bool': str2bool,
260 'list': functools.partial(aslist, sep=',')
274 'list': functools.partial(aslist, sep=',')
261 }
275 }
262 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
263 GLOBAL_CONF_KEY = 'app_settings'
277 GLOBAL_CONF_KEY = 'app_settings'
264
278
265 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
267 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
268 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
269
283
270 def __init__(self, key='', val='', type='unicode'):
284 def __init__(self, key='', val='', type='unicode'):
271 self.app_settings_name = key
285 self.app_settings_name = key
272 self.app_settings_type = type
286 self.app_settings_type = type
273 self.app_settings_value = val
287 self.app_settings_value = val
274
288
275 @validates('_app_settings_value')
289 @validates('_app_settings_value')
276 def validate_settings_value(self, key, val):
290 def validate_settings_value(self, key, val):
277 assert type(val) == unicode
291 assert type(val) == unicode
278 return val
292 return val
279
293
280 @hybrid_property
294 @hybrid_property
281 def app_settings_value(self):
295 def app_settings_value(self):
282 v = self._app_settings_value
296 v = self._app_settings_value
283 _type = self.app_settings_type
297 _type = self.app_settings_type
284 if _type:
298 if _type:
285 _type = self.app_settings_type.split('.')[0]
299 _type = self.app_settings_type.split('.')[0]
286 # decode the encrypted value
300 # decode the encrypted value
287 if 'encrypted' in self.app_settings_type:
301 if 'encrypted' in self.app_settings_type:
288 cipher = EncryptedTextValue()
302 cipher = EncryptedTextValue()
289 v = safe_unicode(cipher.process_result_value(v, None))
303 v = safe_unicode(cipher.process_result_value(v, None))
290
304
291 converter = self.SETTINGS_TYPES.get(_type) or \
305 converter = self.SETTINGS_TYPES.get(_type) or \
292 self.SETTINGS_TYPES['unicode']
306 self.SETTINGS_TYPES['unicode']
293 return converter(v)
307 return converter(v)
294
308
295 @app_settings_value.setter
309 @app_settings_value.setter
296 def app_settings_value(self, val):
310 def app_settings_value(self, val):
297 """
311 """
298 Setter that will always make sure we use unicode in app_settings_value
312 Setter that will always make sure we use unicode in app_settings_value
299
313
300 :param val:
314 :param val:
301 """
315 """
302 val = safe_unicode(val)
316 val = safe_unicode(val)
303 # encode the encrypted value
317 # encode the encrypted value
304 if 'encrypted' in self.app_settings_type:
318 if 'encrypted' in self.app_settings_type:
305 cipher = EncryptedTextValue()
319 cipher = EncryptedTextValue()
306 val = safe_unicode(cipher.process_bind_param(val, None))
320 val = safe_unicode(cipher.process_bind_param(val, None))
307 self._app_settings_value = val
321 self._app_settings_value = val
308
322
309 @hybrid_property
323 @hybrid_property
310 def app_settings_type(self):
324 def app_settings_type(self):
311 return self._app_settings_type
325 return self._app_settings_type
312
326
313 @app_settings_type.setter
327 @app_settings_type.setter
314 def app_settings_type(self, val):
328 def app_settings_type(self, val):
315 if val.split('.')[0] not in self.SETTINGS_TYPES:
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
316 raise Exception('type must be one of %s got %s'
330 raise Exception('type must be one of %s got %s'
317 % (self.SETTINGS_TYPES.keys(), val))
331 % (self.SETTINGS_TYPES.keys(), val))
318 self._app_settings_type = val
332 self._app_settings_type = val
319
333
320 def __unicode__(self):
334 def __unicode__(self):
321 return u"<%s('%s:%s[%s]')>" % (
335 return u"<%s('%s:%s[%s]')>" % (
322 self.__class__.__name__,
336 self.__class__.__name__,
323 self.app_settings_name, self.app_settings_value,
337 self.app_settings_name, self.app_settings_value,
324 self.app_settings_type
338 self.app_settings_type
325 )
339 )
326
340
327
341
328 class RhodeCodeUi(Base, BaseModel):
342 class RhodeCodeUi(Base, BaseModel):
329 __tablename__ = 'rhodecode_ui'
343 __tablename__ = 'rhodecode_ui'
330 __table_args__ = (
344 __table_args__ = (
331 UniqueConstraint('ui_key'),
345 UniqueConstraint('ui_key'),
332 {'extend_existing': True, 'mysql_engine': 'InnoDB',
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
333 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
334 )
348 )
335
349
336 HOOK_REPO_SIZE = 'changegroup.repo_size'
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
337 # HG
351 # HG
338 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
339 HOOK_PULL = 'outgoing.pull_logger'
353 HOOK_PULL = 'outgoing.pull_logger'
340 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
341 HOOK_PUSH = 'changegroup.push_logger'
355 HOOK_PUSH = 'changegroup.push_logger'
342
356
343 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # TODO: johbo: Unify way how hooks are configured for git and hg,
344 # git part is currently hardcoded.
358 # git part is currently hardcoded.
345
359
346 # SVN PATTERNS
360 # SVN PATTERNS
347 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_BRANCH_ID = 'vcs_svn_branch'
348 SVN_TAG_ID = 'vcs_svn_tag'
362 SVN_TAG_ID = 'vcs_svn_tag'
349
363
350 ui_id = Column(
364 ui_id = Column(
351 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 "ui_id", Integer(), nullable=False, unique=True, default=None,
352 primary_key=True)
366 primary_key=True)
353 ui_section = Column(
367 ui_section = Column(
354 "ui_section", String(255), nullable=True, unique=None, default=None)
368 "ui_section", String(255), nullable=True, unique=None, default=None)
355 ui_key = Column(
369 ui_key = Column(
356 "ui_key", String(255), nullable=True, unique=None, default=None)
370 "ui_key", String(255), nullable=True, unique=None, default=None)
357 ui_value = Column(
371 ui_value = Column(
358 "ui_value", String(255), nullable=True, unique=None, default=None)
372 "ui_value", String(255), nullable=True, unique=None, default=None)
359 ui_active = Column(
373 ui_active = Column(
360 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374 "ui_active", Boolean(), nullable=True, unique=None, default=True)
361
375
362 def __repr__(self):
376 def __repr__(self):
363 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
364 self.ui_key, self.ui_value)
378 self.ui_key, self.ui_value)
365
379
366
380
367 class RepoRhodeCodeSetting(Base, BaseModel):
381 class RepoRhodeCodeSetting(Base, BaseModel):
368 __tablename__ = 'repo_rhodecode_settings'
382 __tablename__ = 'repo_rhodecode_settings'
369 __table_args__ = (
383 __table_args__ = (
370 UniqueConstraint(
384 UniqueConstraint(
371 'app_settings_name', 'repository_id',
385 'app_settings_name', 'repository_id',
372 name='uq_repo_rhodecode_setting_name_repo_id'),
386 name='uq_repo_rhodecode_setting_name_repo_id'),
373 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
374 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
375 )
389 )
376
390
377 repository_id = Column(
391 repository_id = Column(
378 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
379 nullable=False)
393 nullable=False)
380 app_settings_id = Column(
394 app_settings_id = Column(
381 "app_settings_id", Integer(), nullable=False, unique=True,
395 "app_settings_id", Integer(), nullable=False, unique=True,
382 default=None, primary_key=True)
396 default=None, primary_key=True)
383 app_settings_name = Column(
397 app_settings_name = Column(
384 "app_settings_name", String(255), nullable=True, unique=None,
398 "app_settings_name", String(255), nullable=True, unique=None,
385 default=None)
399 default=None)
386 _app_settings_value = Column(
400 _app_settings_value = Column(
387 "app_settings_value", String(4096), nullable=True, unique=None,
401 "app_settings_value", String(4096), nullable=True, unique=None,
388 default=None)
402 default=None)
389 _app_settings_type = Column(
403 _app_settings_type = Column(
390 "app_settings_type", String(255), nullable=True, unique=None,
404 "app_settings_type", String(255), nullable=True, unique=None,
391 default=None)
405 default=None)
392
406
393 repository = relationship('Repository')
407 repository = relationship('Repository')
394
408
395 def __init__(self, repository_id, key='', val='', type='unicode'):
409 def __init__(self, repository_id, key='', val='', type='unicode'):
396 self.repository_id = repository_id
410 self.repository_id = repository_id
397 self.app_settings_name = key
411 self.app_settings_name = key
398 self.app_settings_type = type
412 self.app_settings_type = type
399 self.app_settings_value = val
413 self.app_settings_value = val
400
414
401 @validates('_app_settings_value')
415 @validates('_app_settings_value')
402 def validate_settings_value(self, key, val):
416 def validate_settings_value(self, key, val):
403 assert type(val) == unicode
417 assert type(val) == unicode
404 return val
418 return val
405
419
406 @hybrid_property
420 @hybrid_property
407 def app_settings_value(self):
421 def app_settings_value(self):
408 v = self._app_settings_value
422 v = self._app_settings_value
409 type_ = self.app_settings_type
423 type_ = self.app_settings_type
410 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
411 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
412 return converter(v)
426 return converter(v)
413
427
414 @app_settings_value.setter
428 @app_settings_value.setter
415 def app_settings_value(self, val):
429 def app_settings_value(self, val):
416 """
430 """
417 Setter that will always make sure we use unicode in app_settings_value
431 Setter that will always make sure we use unicode in app_settings_value
418
432
419 :param val:
433 :param val:
420 """
434 """
421 self._app_settings_value = safe_unicode(val)
435 self._app_settings_value = safe_unicode(val)
422
436
423 @hybrid_property
437 @hybrid_property
424 def app_settings_type(self):
438 def app_settings_type(self):
425 return self._app_settings_type
439 return self._app_settings_type
426
440
427 @app_settings_type.setter
441 @app_settings_type.setter
428 def app_settings_type(self, val):
442 def app_settings_type(self, val):
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 if val not in SETTINGS_TYPES:
444 if val not in SETTINGS_TYPES:
431 raise Exception('type must be one of %s got %s'
445 raise Exception('type must be one of %s got %s'
432 % (SETTINGS_TYPES.keys(), val))
446 % (SETTINGS_TYPES.keys(), val))
433 self._app_settings_type = val
447 self._app_settings_type = val
434
448
435 def __unicode__(self):
449 def __unicode__(self):
436 return u"<%s('%s:%s:%s[%s]')>" % (
450 return u"<%s('%s:%s:%s[%s]')>" % (
437 self.__class__.__name__, self.repository.repo_name,
451 self.__class__.__name__, self.repository.repo_name,
438 self.app_settings_name, self.app_settings_value,
452 self.app_settings_name, self.app_settings_value,
439 self.app_settings_type
453 self.app_settings_type
440 )
454 )
441
455
442
456
443 class RepoRhodeCodeUi(Base, BaseModel):
457 class RepoRhodeCodeUi(Base, BaseModel):
444 __tablename__ = 'repo_rhodecode_ui'
458 __tablename__ = 'repo_rhodecode_ui'
445 __table_args__ = (
459 __table_args__ = (
446 UniqueConstraint(
460 UniqueConstraint(
447 'repository_id', 'ui_section', 'ui_key',
461 'repository_id', 'ui_section', 'ui_key',
448 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 name='uq_repo_rhodecode_ui_repository_id_section_key'),
449 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
450 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
451 )
465 )
452
466
453 repository_id = Column(
467 repository_id = Column(
454 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
455 nullable=False)
469 nullable=False)
456 ui_id = Column(
470 ui_id = Column(
457 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 "ui_id", Integer(), nullable=False, unique=True, default=None,
458 primary_key=True)
472 primary_key=True)
459 ui_section = Column(
473 ui_section = Column(
460 "ui_section", String(255), nullable=True, unique=None, default=None)
474 "ui_section", String(255), nullable=True, unique=None, default=None)
461 ui_key = Column(
475 ui_key = Column(
462 "ui_key", String(255), nullable=True, unique=None, default=None)
476 "ui_key", String(255), nullable=True, unique=None, default=None)
463 ui_value = Column(
477 ui_value = Column(
464 "ui_value", String(255), nullable=True, unique=None, default=None)
478 "ui_value", String(255), nullable=True, unique=None, default=None)
465 ui_active = Column(
479 ui_active = Column(
466 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480 "ui_active", Boolean(), nullable=True, unique=None, default=True)
467
481
468 repository = relationship('Repository')
482 repository = relationship('Repository')
469
483
470 def __repr__(self):
484 def __repr__(self):
471 return '<%s[%s:%s]%s=>%s]>' % (
485 return '<%s[%s:%s]%s=>%s]>' % (
472 self.__class__.__name__, self.repository.repo_name,
486 self.__class__.__name__, self.repository.repo_name,
473 self.ui_section, self.ui_key, self.ui_value)
487 self.ui_section, self.ui_key, self.ui_value)
474
488
475
489
476 class User(Base, BaseModel):
490 class User(Base, BaseModel):
477 __tablename__ = 'users'
491 __tablename__ = 'users'
478 __table_args__ = (
492 __table_args__ = (
479 UniqueConstraint('username'), UniqueConstraint('email'),
493 UniqueConstraint('username'), UniqueConstraint('email'),
480 Index('u_username_idx', 'username'),
494 Index('u_username_idx', 'username'),
481 Index('u_email_idx', 'email'),
495 Index('u_email_idx', 'email'),
482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
484 )
498 )
485 DEFAULT_USER = 'default'
499 DEFAULT_USER = 'default'
486 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
487 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
488
502
489 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
503 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 username = Column("username", String(255), nullable=True, unique=None, default=None)
491 password = Column("password", String(255), nullable=True, unique=None, default=None)
505 password = Column("password", String(255), nullable=True, unique=None, default=None)
492 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
493 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
494 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
495 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
509 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
496 _email = Column("email", String(255), nullable=True, unique=None, default=None)
510 _email = Column("email", String(255), nullable=True, unique=None, default=None)
497 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
498 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
499 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
500 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
501 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
502 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
503 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
504
518
505 user_log = relationship('UserLog')
519 user_log = relationship('UserLog')
506 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
507
521
508 repositories = relationship('Repository')
522 repositories = relationship('Repository')
509 repository_groups = relationship('RepoGroup')
523 repository_groups = relationship('RepoGroup')
510 user_groups = relationship('UserGroup')
524 user_groups = relationship('UserGroup')
511
525
512 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
513 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
514
528
515 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
516 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
517 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
518
532
519 group_member = relationship('UserGroupMember', cascade='all')
533 group_member = relationship('UserGroupMember', cascade='all')
520
534
521 notifications = relationship('UserNotification', cascade='all')
535 notifications = relationship('UserNotification', cascade='all')
522 # notifications assigned to this user
536 # notifications assigned to this user
523 user_created_notifications = relationship('Notification', cascade='all')
537 user_created_notifications = relationship('Notification', cascade='all')
524 # comments created by this user
538 # comments created by this user
525 user_comments = relationship('ChangesetComment', cascade='all')
539 user_comments = relationship('ChangesetComment', cascade='all')
526 # user profile extra info
540 # user profile extra info
527 user_emails = relationship('UserEmailMap', cascade='all')
541 user_emails = relationship('UserEmailMap', cascade='all')
528 user_ip_map = relationship('UserIpMap', cascade='all')
542 user_ip_map = relationship('UserIpMap', cascade='all')
529 user_auth_tokens = relationship('UserApiKeys', cascade='all')
543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
530 # gists
544 # gists
531 user_gists = relationship('Gist', cascade='all')
545 user_gists = relationship('Gist', cascade='all')
532 # user pull requests
546 # user pull requests
533 user_pull_requests = relationship('PullRequest', cascade='all')
547 user_pull_requests = relationship('PullRequest', cascade='all')
534 # external identities
548 # external identities
535 extenal_identities = relationship(
549 extenal_identities = relationship(
536 'ExternalIdentity',
550 'ExternalIdentity',
537 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
538 cascade='all')
552 cascade='all')
539
553
540 def __unicode__(self):
554 def __unicode__(self):
541 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
542 self.user_id, self.username)
556 self.user_id, self.username)
543
557
544 @hybrid_property
558 @hybrid_property
545 def email(self):
559 def email(self):
546 return self._email
560 return self._email
547
561
548 @email.setter
562 @email.setter
549 def email(self, val):
563 def email(self, val):
550 self._email = val.lower() if val else None
564 self._email = val.lower() if val else None
551
565
552 @property
566 @property
553 def firstname(self):
567 def firstname(self):
554 # alias for future
568 # alias for future
555 return self.name
569 return self.name
556
570
557 @property
571 @property
558 def emails(self):
572 def emails(self):
559 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
560 return [self.email] + [x.email for x in other]
574 return [self.email] + [x.email for x in other]
561
575
562 @property
576 @property
563 def auth_tokens(self):
577 def auth_tokens(self):
564 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
565
579
566 @property
580 @property
567 def extra_auth_tokens(self):
581 def extra_auth_tokens(self):
568 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
569
583
570 @property
584 @property
571 def feed_token(self):
585 def feed_token(self):
572 feed_tokens = UserApiKeys.query()\
586 feed_tokens = UserApiKeys.query()\
573 .filter(UserApiKeys.user == self)\
587 .filter(UserApiKeys.user == self)\
574 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
588 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
575 .all()
589 .all()
576 if feed_tokens:
590 if feed_tokens:
577 return feed_tokens[0].api_key
591 return feed_tokens[0].api_key
578 else:
592 else:
579 # use the main token so we don't end up with nothing...
593 # use the main token so we don't end up with nothing...
580 return self.api_key
594 return self.api_key
581
595
582 @classmethod
596 @classmethod
583 def extra_valid_auth_tokens(cls, user, role=None):
597 def extra_valid_auth_tokens(cls, user, role=None):
584 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
585 .filter(or_(UserApiKeys.expires == -1,
599 .filter(or_(UserApiKeys.expires == -1,
586 UserApiKeys.expires >= time.time()))
600 UserApiKeys.expires >= time.time()))
587 if role:
601 if role:
588 tokens = tokens.filter(or_(UserApiKeys.role == role,
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
589 UserApiKeys.role == UserApiKeys.ROLE_ALL))
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
590 return tokens.all()
604 return tokens.all()
591
605
592 @property
606 @property
593 def ip_addresses(self):
607 def ip_addresses(self):
594 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
608 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
595 return [x.ip_addr for x in ret]
609 return [x.ip_addr for x in ret]
596
610
597 @property
611 @property
598 def username_and_name(self):
612 def username_and_name(self):
599 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
613 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
600
614
601 @property
615 @property
602 def username_or_name_or_email(self):
616 def username_or_name_or_email(self):
603 full_name = self.full_name if self.full_name is not ' ' else None
617 full_name = self.full_name if self.full_name is not ' ' else None
604 return self.username or full_name or self.email
618 return self.username or full_name or self.email
605
619
606 @property
620 @property
607 def full_name(self):
621 def full_name(self):
608 return '%s %s' % (self.firstname, self.lastname)
622 return '%s %s' % (self.firstname, self.lastname)
609
623
610 @property
624 @property
611 def full_name_or_username(self):
625 def full_name_or_username(self):
612 return ('%s %s' % (self.firstname, self.lastname)
626 return ('%s %s' % (self.firstname, self.lastname)
613 if (self.firstname and self.lastname) else self.username)
627 if (self.firstname and self.lastname) else self.username)
614
628
615 @property
629 @property
616 def full_contact(self):
630 def full_contact(self):
617 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
631 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
618
632
619 @property
633 @property
620 def short_contact(self):
634 def short_contact(self):
621 return '%s %s' % (self.firstname, self.lastname)
635 return '%s %s' % (self.firstname, self.lastname)
622
636
623 @property
637 @property
624 def is_admin(self):
638 def is_admin(self):
625 return self.admin
639 return self.admin
626
640
627 @property
641 @property
628 def AuthUser(self):
642 def AuthUser(self):
629 """
643 """
630 Returns instance of AuthUser for this user
644 Returns instance of AuthUser for this user
631 """
645 """
632 from rhodecode.lib.auth import AuthUser
646 from rhodecode.lib.auth import AuthUser
633 return AuthUser(user_id=self.user_id, api_key=self.api_key,
647 return AuthUser(user_id=self.user_id, api_key=self.api_key,
634 username=self.username)
648 username=self.username)
635
649
636 @hybrid_property
650 @hybrid_property
637 def user_data(self):
651 def user_data(self):
638 if not self._user_data:
652 if not self._user_data:
639 return {}
653 return {}
640
654
641 try:
655 try:
642 return json.loads(self._user_data)
656 return json.loads(self._user_data)
643 except TypeError:
657 except TypeError:
644 return {}
658 return {}
645
659
646 @user_data.setter
660 @user_data.setter
647 def user_data(self, val):
661 def user_data(self, val):
648 if not isinstance(val, dict):
662 if not isinstance(val, dict):
649 raise Exception('user_data must be dict, got %s' % type(val))
663 raise Exception('user_data must be dict, got %s' % type(val))
650 try:
664 try:
651 self._user_data = json.dumps(val)
665 self._user_data = json.dumps(val)
652 except Exception:
666 except Exception:
653 log.error(traceback.format_exc())
667 log.error(traceback.format_exc())
654
668
655 @classmethod
669 @classmethod
656 def get_by_username(cls, username, case_insensitive=False,
670 def get_by_username(cls, username, case_insensitive=False,
657 cache=False, identity_cache=False):
671 cache=False, identity_cache=False):
658 session = Session()
672 session = Session()
659
673
660 if case_insensitive:
674 if case_insensitive:
661 q = cls.query().filter(
675 q = cls.query().filter(
662 func.lower(cls.username) == func.lower(username))
676 func.lower(cls.username) == func.lower(username))
663 else:
677 else:
664 q = cls.query().filter(cls.username == username)
678 q = cls.query().filter(cls.username == username)
665
679
666 if cache:
680 if cache:
667 if identity_cache:
681 if identity_cache:
668 val = cls.identity_cache(session, 'username', username)
682 val = cls.identity_cache(session, 'username', username)
669 if val:
683 if val:
670 return val
684 return val
671 else:
685 else:
672 q = q.options(
686 q = q.options(
673 FromCache("sql_cache_short",
687 FromCache("sql_cache_short",
674 "get_user_by_name_%s" % _hash_key(username)))
688 "get_user_by_name_%s" % _hash_key(username)))
675
689
676 return q.scalar()
690 return q.scalar()
677
691
678 @classmethod
692 @classmethod
679 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
693 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
680 q = cls.query().filter(cls.api_key == auth_token)
694 q = cls.query().filter(cls.api_key == auth_token)
681
695
682 if cache:
696 if cache:
683 q = q.options(FromCache("sql_cache_short",
697 q = q.options(FromCache("sql_cache_short",
684 "get_auth_token_%s" % auth_token))
698 "get_auth_token_%s" % auth_token))
685 res = q.scalar()
699 res = q.scalar()
686
700
687 if fallback and not res:
701 if fallback and not res:
688 #fallback to additional keys
702 #fallback to additional keys
689 _res = UserApiKeys.query()\
703 _res = UserApiKeys.query()\
690 .filter(UserApiKeys.api_key == auth_token)\
704 .filter(UserApiKeys.api_key == auth_token)\
691 .filter(or_(UserApiKeys.expires == -1,
705 .filter(or_(UserApiKeys.expires == -1,
692 UserApiKeys.expires >= time.time()))\
706 UserApiKeys.expires >= time.time()))\
693 .first()
707 .first()
694 if _res:
708 if _res:
695 res = _res.user
709 res = _res.user
696 return res
710 return res
697
711
698 @classmethod
712 @classmethod
699 def get_by_email(cls, email, case_insensitive=False, cache=False):
713 def get_by_email(cls, email, case_insensitive=False, cache=False):
700
714
701 if case_insensitive:
715 if case_insensitive:
702 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
716 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
703
717
704 else:
718 else:
705 q = cls.query().filter(cls.email == email)
719 q = cls.query().filter(cls.email == email)
706
720
707 if cache:
721 if cache:
708 q = q.options(FromCache("sql_cache_short",
722 q = q.options(FromCache("sql_cache_short",
709 "get_email_key_%s" % email))
723 "get_email_key_%s" % email))
710
724
711 ret = q.scalar()
725 ret = q.scalar()
712 if ret is None:
726 if ret is None:
713 q = UserEmailMap.query()
727 q = UserEmailMap.query()
714 # try fetching in alternate email map
728 # try fetching in alternate email map
715 if case_insensitive:
729 if case_insensitive:
716 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
730 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
717 else:
731 else:
718 q = q.filter(UserEmailMap.email == email)
732 q = q.filter(UserEmailMap.email == email)
719 q = q.options(joinedload(UserEmailMap.user))
733 q = q.options(joinedload(UserEmailMap.user))
720 if cache:
734 if cache:
721 q = q.options(FromCache("sql_cache_short",
735 q = q.options(FromCache("sql_cache_short",
722 "get_email_map_key_%s" % email))
736 "get_email_map_key_%s" % email))
723 ret = getattr(q.scalar(), 'user', None)
737 ret = getattr(q.scalar(), 'user', None)
724
738
725 return ret
739 return ret
726
740
727 @classmethod
741 @classmethod
728 def get_from_cs_author(cls, author):
742 def get_from_cs_author(cls, author):
729 """
743 """
730 Tries to get User objects out of commit author string
744 Tries to get User objects out of commit author string
731
745
732 :param author:
746 :param author:
733 """
747 """
734 from rhodecode.lib.helpers import email, author_name
748 from rhodecode.lib.helpers import email, author_name
735 # Valid email in the attribute passed, see if they're in the system
749 # Valid email in the attribute passed, see if they're in the system
736 _email = email(author)
750 _email = email(author)
737 if _email:
751 if _email:
738 user = cls.get_by_email(_email, case_insensitive=True)
752 user = cls.get_by_email(_email, case_insensitive=True)
739 if user:
753 if user:
740 return user
754 return user
741 # Maybe we can match by username?
755 # Maybe we can match by username?
742 _author = author_name(author)
756 _author = author_name(author)
743 user = cls.get_by_username(_author, case_insensitive=True)
757 user = cls.get_by_username(_author, case_insensitive=True)
744 if user:
758 if user:
745 return user
759 return user
746
760
747 def update_userdata(self, **kwargs):
761 def update_userdata(self, **kwargs):
748 usr = self
762 usr = self
749 old = usr.user_data
763 old = usr.user_data
750 old.update(**kwargs)
764 old.update(**kwargs)
751 usr.user_data = old
765 usr.user_data = old
752 Session().add(usr)
766 Session().add(usr)
753 log.debug('updated userdata with ', kwargs)
767 log.debug('updated userdata with ', kwargs)
754
768
755 def update_lastlogin(self):
769 def update_lastlogin(self):
756 """Update user lastlogin"""
770 """Update user lastlogin"""
757 self.last_login = datetime.datetime.now()
771 self.last_login = datetime.datetime.now()
758 Session().add(self)
772 Session().add(self)
759 log.debug('updated user %s lastlogin', self.username)
773 log.debug('updated user %s lastlogin', self.username)
760
774
761 def update_lastactivity(self):
775 def update_lastactivity(self):
762 """Update user lastactivity"""
776 """Update user lastactivity"""
763 usr = self
777 usr = self
764 old = usr.user_data
778 old = usr.user_data
765 old.update({'last_activity': time.time()})
779 old.update({'last_activity': time.time()})
766 usr.user_data = old
780 usr.user_data = old
767 Session().add(usr)
781 Session().add(usr)
768 log.debug('updated user %s lastactivity', usr.username)
782 log.debug('updated user %s lastactivity', usr.username)
769
783
770 def update_password(self, new_password, change_api_key=False):
784 def update_password(self, new_password, change_api_key=False):
771 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
785 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
772
786
773 self.password = get_crypt_password(new_password)
787 self.password = get_crypt_password(new_password)
774 if change_api_key:
788 if change_api_key:
775 self.api_key = generate_auth_token(self.username)
789 self.api_key = generate_auth_token(self.username)
776 Session().add(self)
790 Session().add(self)
777
791
778 @classmethod
792 @classmethod
779 def get_first_super_admin(cls):
793 def get_first_super_admin(cls):
780 user = User.query().filter(User.admin == true()).first()
794 user = User.query().filter(User.admin == true()).first()
781 if user is None:
795 if user is None:
782 raise Exception('FATAL: Missing administrative account!')
796 raise Exception('FATAL: Missing administrative account!')
783 return user
797 return user
784
798
785 @classmethod
799 @classmethod
786 def get_all_super_admins(cls):
800 def get_all_super_admins(cls):
787 """
801 """
788 Returns all admin accounts sorted by username
802 Returns all admin accounts sorted by username
789 """
803 """
790 return User.query().filter(User.admin == true())\
804 return User.query().filter(User.admin == true())\
791 .order_by(User.username.asc()).all()
805 .order_by(User.username.asc()).all()
792
806
793 @classmethod
807 @classmethod
794 def get_default_user(cls, cache=False):
808 def get_default_user(cls, cache=False):
795 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
809 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
796 if user is None:
810 if user is None:
797 raise Exception('FATAL: Missing default account!')
811 raise Exception('FATAL: Missing default account!')
798 return user
812 return user
799
813
800 def _get_default_perms(self, user, suffix=''):
814 def _get_default_perms(self, user, suffix=''):
801 from rhodecode.model.permission import PermissionModel
815 from rhodecode.model.permission import PermissionModel
802 return PermissionModel().get_default_perms(user.user_perms, suffix)
816 return PermissionModel().get_default_perms(user.user_perms, suffix)
803
817
804 def get_default_perms(self, suffix=''):
818 def get_default_perms(self, suffix=''):
805 return self._get_default_perms(self, suffix)
819 return self._get_default_perms(self, suffix)
806
820
807 def get_api_data(self, include_secrets=False, details='full'):
821 def get_api_data(self, include_secrets=False, details='full'):
808 """
822 """
809 Common function for generating user related data for API
823 Common function for generating user related data for API
810
824
811 :param include_secrets: By default secrets in the API data will be replaced
825 :param include_secrets: By default secrets in the API data will be replaced
812 by a placeholder value to prevent exposing this data by accident. In case
826 by a placeholder value to prevent exposing this data by accident. In case
813 this data shall be exposed, set this flag to ``True``.
827 this data shall be exposed, set this flag to ``True``.
814
828
815 :param details: details can be 'basic|full' basic gives only a subset of
829 :param details: details can be 'basic|full' basic gives only a subset of
816 the available user information that includes user_id, name and emails.
830 the available user information that includes user_id, name and emails.
817 """
831 """
818 user = self
832 user = self
819 user_data = self.user_data
833 user_data = self.user_data
820 data = {
834 data = {
821 'user_id': user.user_id,
835 'user_id': user.user_id,
822 'username': user.username,
836 'username': user.username,
823 'firstname': user.name,
837 'firstname': user.name,
824 'lastname': user.lastname,
838 'lastname': user.lastname,
825 'email': user.email,
839 'email': user.email,
826 'emails': user.emails,
840 'emails': user.emails,
827 }
841 }
828 if details == 'basic':
842 if details == 'basic':
829 return data
843 return data
830
844
831 api_key_length = 40
845 api_key_length = 40
832 api_key_replacement = '*' * api_key_length
846 api_key_replacement = '*' * api_key_length
833
847
834 extras = {
848 extras = {
835 'api_key': api_key_replacement,
849 'api_key': api_key_replacement,
836 'api_keys': [api_key_replacement],
850 'api_keys': [api_key_replacement],
837 'active': user.active,
851 'active': user.active,
838 'admin': user.admin,
852 'admin': user.admin,
839 'extern_type': user.extern_type,
853 'extern_type': user.extern_type,
840 'extern_name': user.extern_name,
854 'extern_name': user.extern_name,
841 'last_login': user.last_login,
855 'last_login': user.last_login,
842 'ip_addresses': user.ip_addresses,
856 'ip_addresses': user.ip_addresses,
843 'language': user_data.get('language')
857 'language': user_data.get('language')
844 }
858 }
845 data.update(extras)
859 data.update(extras)
846
860
847 if include_secrets:
861 if include_secrets:
848 data['api_key'] = user.api_key
862 data['api_key'] = user.api_key
849 data['api_keys'] = user.auth_tokens
863 data['api_keys'] = user.auth_tokens
850 return data
864 return data
851
865
852 def __json__(self):
866 def __json__(self):
853 data = {
867 data = {
854 'full_name': self.full_name,
868 'full_name': self.full_name,
855 'full_name_or_username': self.full_name_or_username,
869 'full_name_or_username': self.full_name_or_username,
856 'short_contact': self.short_contact,
870 'short_contact': self.short_contact,
857 'full_contact': self.full_contact,
871 'full_contact': self.full_contact,
858 }
872 }
859 data.update(self.get_api_data())
873 data.update(self.get_api_data())
860 return data
874 return data
861
875
862
876
863 class UserApiKeys(Base, BaseModel):
877 class UserApiKeys(Base, BaseModel):
864 __tablename__ = 'user_api_keys'
878 __tablename__ = 'user_api_keys'
865 __table_args__ = (
879 __table_args__ = (
866 Index('uak_api_key_idx', 'api_key'),
880 Index('uak_api_key_idx', 'api_key'),
867 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
881 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
868 UniqueConstraint('api_key'),
882 UniqueConstraint('api_key'),
869 {'extend_existing': True, 'mysql_engine': 'InnoDB',
883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
870 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
871 )
885 )
872 __mapper_args__ = {}
886 __mapper_args__ = {}
873
887
874 # ApiKey role
888 # ApiKey role
875 ROLE_ALL = 'token_role_all'
889 ROLE_ALL = 'token_role_all'
876 ROLE_HTTP = 'token_role_http'
890 ROLE_HTTP = 'token_role_http'
877 ROLE_VCS = 'token_role_vcs'
891 ROLE_VCS = 'token_role_vcs'
878 ROLE_API = 'token_role_api'
892 ROLE_API = 'token_role_api'
879 ROLE_FEED = 'token_role_feed'
893 ROLE_FEED = 'token_role_feed'
880 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
894 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
881
895
882 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
896 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
883 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
884 api_key = Column("api_key", String(255), nullable=False, unique=True)
898 api_key = Column("api_key", String(255), nullable=False, unique=True)
885 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
899 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
886 expires = Column('expires', Float(53), nullable=False)
900 expires = Column('expires', Float(53), nullable=False)
887 role = Column('role', String(255), nullable=True)
901 role = Column('role', String(255), nullable=True)
888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
889
903
890 user = relationship('User', lazy='joined')
904 user = relationship('User', lazy='joined')
891
905
892 @classmethod
906 @classmethod
893 def _get_role_name(cls, role):
907 def _get_role_name(cls, role):
894 return {
908 return {
895 cls.ROLE_ALL: _('all'),
909 cls.ROLE_ALL: _('all'),
896 cls.ROLE_HTTP: _('http/web interface'),
910 cls.ROLE_HTTP: _('http/web interface'),
897 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
911 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
898 cls.ROLE_API: _('api calls'),
912 cls.ROLE_API: _('api calls'),
899 cls.ROLE_FEED: _('feed access'),
913 cls.ROLE_FEED: _('feed access'),
900 }.get(role, role)
914 }.get(role, role)
901
915
902 @property
916 @property
903 def expired(self):
917 def expired(self):
904 if self.expires == -1:
918 if self.expires == -1:
905 return False
919 return False
906 return time.time() > self.expires
920 return time.time() > self.expires
907
921
908 @property
922 @property
909 def role_humanized(self):
923 def role_humanized(self):
910 return self._get_role_name(self.role)
924 return self._get_role_name(self.role)
911
925
912
926
913 class UserEmailMap(Base, BaseModel):
927 class UserEmailMap(Base, BaseModel):
914 __tablename__ = 'user_email_map'
928 __tablename__ = 'user_email_map'
915 __table_args__ = (
929 __table_args__ = (
916 Index('uem_email_idx', 'email'),
930 Index('uem_email_idx', 'email'),
917 UniqueConstraint('email'),
931 UniqueConstraint('email'),
918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
919 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
920 )
934 )
921 __mapper_args__ = {}
935 __mapper_args__ = {}
922
936
923 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
937 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
924 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
925 _email = Column("email", String(255), nullable=True, unique=False, default=None)
939 _email = Column("email", String(255), nullable=True, unique=False, default=None)
926 user = relationship('User', lazy='joined')
940 user = relationship('User', lazy='joined')
927
941
928 @validates('_email')
942 @validates('_email')
929 def validate_email(self, key, email):
943 def validate_email(self, key, email):
930 # check if this email is not main one
944 # check if this email is not main one
931 main_email = Session().query(User).filter(User.email == email).scalar()
945 main_email = Session().query(User).filter(User.email == email).scalar()
932 if main_email is not None:
946 if main_email is not None:
933 raise AttributeError('email %s is present is user table' % email)
947 raise AttributeError('email %s is present is user table' % email)
934 return email
948 return email
935
949
936 @hybrid_property
950 @hybrid_property
937 def email(self):
951 def email(self):
938 return self._email
952 return self._email
939
953
940 @email.setter
954 @email.setter
941 def email(self, val):
955 def email(self, val):
942 self._email = val.lower() if val else None
956 self._email = val.lower() if val else None
943
957
944
958
945 class UserIpMap(Base, BaseModel):
959 class UserIpMap(Base, BaseModel):
946 __tablename__ = 'user_ip_map'
960 __tablename__ = 'user_ip_map'
947 __table_args__ = (
961 __table_args__ = (
948 UniqueConstraint('user_id', 'ip_addr'),
962 UniqueConstraint('user_id', 'ip_addr'),
949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
951 )
965 )
952 __mapper_args__ = {}
966 __mapper_args__ = {}
953
967
954 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
968 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
955 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
956 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
970 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
957 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
971 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
958 description = Column("description", String(10000), nullable=True, unique=None, default=None)
972 description = Column("description", String(10000), nullable=True, unique=None, default=None)
959 user = relationship('User', lazy='joined')
973 user = relationship('User', lazy='joined')
960
974
961 @classmethod
975 @classmethod
962 def _get_ip_range(cls, ip_addr):
976 def _get_ip_range(cls, ip_addr):
963 net = ipaddress.ip_network(ip_addr, strict=False)
977 net = ipaddress.ip_network(ip_addr, strict=False)
964 return [str(net.network_address), str(net.broadcast_address)]
978 return [str(net.network_address), str(net.broadcast_address)]
965
979
966 def __json__(self):
980 def __json__(self):
967 return {
981 return {
968 'ip_addr': self.ip_addr,
982 'ip_addr': self.ip_addr,
969 'ip_range': self._get_ip_range(self.ip_addr),
983 'ip_range': self._get_ip_range(self.ip_addr),
970 }
984 }
971
985
972 def __unicode__(self):
986 def __unicode__(self):
973 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
987 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
974 self.user_id, self.ip_addr)
988 self.user_id, self.ip_addr)
975
989
976 class UserLog(Base, BaseModel):
990 class UserLog(Base, BaseModel):
977 __tablename__ = 'user_logs'
991 __tablename__ = 'user_logs'
978 __table_args__ = (
992 __table_args__ = (
979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
981 )
995 )
982 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
983 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
984 username = Column("username", String(255), nullable=True, unique=None, default=None)
998 username = Column("username", String(255), nullable=True, unique=None, default=None)
985 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
999 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
986 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1000 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
987 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1001 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
988 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1002 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
989 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1003 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
990
1004
991 def __unicode__(self):
1005 def __unicode__(self):
992 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1006 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
993 self.repository_name,
1007 self.repository_name,
994 self.action)
1008 self.action)
995
1009
996 @property
1010 @property
997 def action_as_day(self):
1011 def action_as_day(self):
998 return datetime.date(*self.action_date.timetuple()[:3])
1012 return datetime.date(*self.action_date.timetuple()[:3])
999
1013
1000 user = relationship('User')
1014 user = relationship('User')
1001 repository = relationship('Repository', cascade='')
1015 repository = relationship('Repository', cascade='')
1002
1016
1003
1017
1004 class UserGroup(Base, BaseModel):
1018 class UserGroup(Base, BaseModel):
1005 __tablename__ = 'users_groups'
1019 __tablename__ = 'users_groups'
1006 __table_args__ = (
1020 __table_args__ = (
1007 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1008 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1009 )
1023 )
1010
1024
1011 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1025 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1012 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1026 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1013 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1027 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1014 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1028 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1015 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1029 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1016 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1017 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1018 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1032 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1019
1033
1020 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1034 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1021 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1035 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1022 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1036 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1023 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1037 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1024 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1038 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1025 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1039 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1026
1040
1027 user = relationship('User')
1041 user = relationship('User')
1028
1042
1029 @hybrid_property
1043 @hybrid_property
1030 def group_data(self):
1044 def group_data(self):
1031 if not self._group_data:
1045 if not self._group_data:
1032 return {}
1046 return {}
1033
1047
1034 try:
1048 try:
1035 return json.loads(self._group_data)
1049 return json.loads(self._group_data)
1036 except TypeError:
1050 except TypeError:
1037 return {}
1051 return {}
1038
1052
1039 @group_data.setter
1053 @group_data.setter
1040 def group_data(self, val):
1054 def group_data(self, val):
1041 try:
1055 try:
1042 self._group_data = json.dumps(val)
1056 self._group_data = json.dumps(val)
1043 except Exception:
1057 except Exception:
1044 log.error(traceback.format_exc())
1058 log.error(traceback.format_exc())
1045
1059
1046 def __unicode__(self):
1060 def __unicode__(self):
1047 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1061 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1048 self.users_group_id,
1062 self.users_group_id,
1049 self.users_group_name)
1063 self.users_group_name)
1050
1064
1051 @classmethod
1065 @classmethod
1052 def get_by_group_name(cls, group_name, cache=False,
1066 def get_by_group_name(cls, group_name, cache=False,
1053 case_insensitive=False):
1067 case_insensitive=False):
1054 if case_insensitive:
1068 if case_insensitive:
1055 q = cls.query().filter(func.lower(cls.users_group_name) ==
1069 q = cls.query().filter(func.lower(cls.users_group_name) ==
1056 func.lower(group_name))
1070 func.lower(group_name))
1057
1071
1058 else:
1072 else:
1059 q = cls.query().filter(cls.users_group_name == group_name)
1073 q = cls.query().filter(cls.users_group_name == group_name)
1060 if cache:
1074 if cache:
1061 q = q.options(FromCache(
1075 q = q.options(FromCache(
1062 "sql_cache_short",
1076 "sql_cache_short",
1063 "get_group_%s" % _hash_key(group_name)))
1077 "get_group_%s" % _hash_key(group_name)))
1064 return q.scalar()
1078 return q.scalar()
1065
1079
1066 @classmethod
1080 @classmethod
1067 def get(cls, user_group_id, cache=False):
1081 def get(cls, user_group_id, cache=False):
1068 user_group = cls.query()
1082 user_group = cls.query()
1069 if cache:
1083 if cache:
1070 user_group = user_group.options(FromCache("sql_cache_short",
1084 user_group = user_group.options(FromCache("sql_cache_short",
1071 "get_users_group_%s" % user_group_id))
1085 "get_users_group_%s" % user_group_id))
1072 return user_group.get(user_group_id)
1086 return user_group.get(user_group_id)
1073
1087
1074 def permissions(self, with_admins=True, with_owner=True):
1088 def permissions(self, with_admins=True, with_owner=True):
1075 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1089 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1076 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1090 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1077 joinedload(UserUserGroupToPerm.user),
1091 joinedload(UserUserGroupToPerm.user),
1078 joinedload(UserUserGroupToPerm.permission),)
1092 joinedload(UserUserGroupToPerm.permission),)
1079
1093
1080 # get owners and admins and permissions. We do a trick of re-writing
1094 # get owners and admins and permissions. We do a trick of re-writing
1081 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1095 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1082 # has a global reference and changing one object propagates to all
1096 # has a global reference and changing one object propagates to all
1083 # others. This means if admin is also an owner admin_row that change
1097 # others. This means if admin is also an owner admin_row that change
1084 # would propagate to both objects
1098 # would propagate to both objects
1085 perm_rows = []
1099 perm_rows = []
1086 for _usr in q.all():
1100 for _usr in q.all():
1087 usr = AttributeDict(_usr.user.get_dict())
1101 usr = AttributeDict(_usr.user.get_dict())
1088 usr.permission = _usr.permission.permission_name
1102 usr.permission = _usr.permission.permission_name
1089 perm_rows.append(usr)
1103 perm_rows.append(usr)
1090
1104
1091 # filter the perm rows by 'default' first and then sort them by
1105 # filter the perm rows by 'default' first and then sort them by
1092 # admin,write,read,none permissions sorted again alphabetically in
1106 # admin,write,read,none permissions sorted again alphabetically in
1093 # each group
1107 # each group
1094 perm_rows = sorted(perm_rows, key=display_sort)
1108 perm_rows = sorted(perm_rows, key=display_sort)
1095
1109
1096 _admin_perm = 'usergroup.admin'
1110 _admin_perm = 'usergroup.admin'
1097 owner_row = []
1111 owner_row = []
1098 if with_owner:
1112 if with_owner:
1099 usr = AttributeDict(self.user.get_dict())
1113 usr = AttributeDict(self.user.get_dict())
1100 usr.owner_row = True
1114 usr.owner_row = True
1101 usr.permission = _admin_perm
1115 usr.permission = _admin_perm
1102 owner_row.append(usr)
1116 owner_row.append(usr)
1103
1117
1104 super_admin_rows = []
1118 super_admin_rows = []
1105 if with_admins:
1119 if with_admins:
1106 for usr in User.get_all_super_admins():
1120 for usr in User.get_all_super_admins():
1107 # if this admin is also owner, don't double the record
1121 # if this admin is also owner, don't double the record
1108 if usr.user_id == owner_row[0].user_id:
1122 if usr.user_id == owner_row[0].user_id:
1109 owner_row[0].admin_row = True
1123 owner_row[0].admin_row = True
1110 else:
1124 else:
1111 usr = AttributeDict(usr.get_dict())
1125 usr = AttributeDict(usr.get_dict())
1112 usr.admin_row = True
1126 usr.admin_row = True
1113 usr.permission = _admin_perm
1127 usr.permission = _admin_perm
1114 super_admin_rows.append(usr)
1128 super_admin_rows.append(usr)
1115
1129
1116 return super_admin_rows + owner_row + perm_rows
1130 return super_admin_rows + owner_row + perm_rows
1117
1131
1118 def permission_user_groups(self):
1132 def permission_user_groups(self):
1119 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1133 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1120 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1134 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1121 joinedload(UserGroupUserGroupToPerm.target_user_group),
1135 joinedload(UserGroupUserGroupToPerm.target_user_group),
1122 joinedload(UserGroupUserGroupToPerm.permission),)
1136 joinedload(UserGroupUserGroupToPerm.permission),)
1123
1137
1124 perm_rows = []
1138 perm_rows = []
1125 for _user_group in q.all():
1139 for _user_group in q.all():
1126 usr = AttributeDict(_user_group.user_group.get_dict())
1140 usr = AttributeDict(_user_group.user_group.get_dict())
1127 usr.permission = _user_group.permission.permission_name
1141 usr.permission = _user_group.permission.permission_name
1128 perm_rows.append(usr)
1142 perm_rows.append(usr)
1129
1143
1130 return perm_rows
1144 return perm_rows
1131
1145
1132 def _get_default_perms(self, user_group, suffix=''):
1146 def _get_default_perms(self, user_group, suffix=''):
1133 from rhodecode.model.permission import PermissionModel
1147 from rhodecode.model.permission import PermissionModel
1134 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1148 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1135
1149
1136 def get_default_perms(self, suffix=''):
1150 def get_default_perms(self, suffix=''):
1137 return self._get_default_perms(self, suffix)
1151 return self._get_default_perms(self, suffix)
1138
1152
1139 def get_api_data(self, with_group_members=True, include_secrets=False):
1153 def get_api_data(self, with_group_members=True, include_secrets=False):
1140 """
1154 """
1141 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1155 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1142 basically forwarded.
1156 basically forwarded.
1143
1157
1144 """
1158 """
1145 user_group = self
1159 user_group = self
1146
1160
1147 data = {
1161 data = {
1148 'users_group_id': user_group.users_group_id,
1162 'users_group_id': user_group.users_group_id,
1149 'group_name': user_group.users_group_name,
1163 'group_name': user_group.users_group_name,
1150 'group_description': user_group.user_group_description,
1164 'group_description': user_group.user_group_description,
1151 'active': user_group.users_group_active,
1165 'active': user_group.users_group_active,
1152 'owner': user_group.user.username,
1166 'owner': user_group.user.username,
1153 }
1167 }
1154 if with_group_members:
1168 if with_group_members:
1155 users = []
1169 users = []
1156 for user in user_group.members:
1170 for user in user_group.members:
1157 user = user.user
1171 user = user.user
1158 users.append(user.get_api_data(include_secrets=include_secrets))
1172 users.append(user.get_api_data(include_secrets=include_secrets))
1159 data['users'] = users
1173 data['users'] = users
1160
1174
1161 return data
1175 return data
1162
1176
1163
1177
1164 class UserGroupMember(Base, BaseModel):
1178 class UserGroupMember(Base, BaseModel):
1165 __tablename__ = 'users_groups_members'
1179 __tablename__ = 'users_groups_members'
1166 __table_args__ = (
1180 __table_args__ = (
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1169 )
1183 )
1170
1184
1171 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1185 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1186 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1187 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1174
1188
1175 user = relationship('User', lazy='joined')
1189 user = relationship('User', lazy='joined')
1176 users_group = relationship('UserGroup')
1190 users_group = relationship('UserGroup')
1177
1191
1178 def __init__(self, gr_id='', u_id=''):
1192 def __init__(self, gr_id='', u_id=''):
1179 self.users_group_id = gr_id
1193 self.users_group_id = gr_id
1180 self.user_id = u_id
1194 self.user_id = u_id
1181
1195
1182
1196
1183 class RepositoryField(Base, BaseModel):
1197 class RepositoryField(Base, BaseModel):
1184 __tablename__ = 'repositories_fields'
1198 __tablename__ = 'repositories_fields'
1185 __table_args__ = (
1199 __table_args__ = (
1186 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1200 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1189 )
1203 )
1190 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1204 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1191
1205
1192 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1206 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1193 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1207 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1194 field_key = Column("field_key", String(250))
1208 field_key = Column("field_key", String(250))
1195 field_label = Column("field_label", String(1024), nullable=False)
1209 field_label = Column("field_label", String(1024), nullable=False)
1196 field_value = Column("field_value", String(10000), nullable=False)
1210 field_value = Column("field_value", String(10000), nullable=False)
1197 field_desc = Column("field_desc", String(1024), nullable=False)
1211 field_desc = Column("field_desc", String(1024), nullable=False)
1198 field_type = Column("field_type", String(255), nullable=False, unique=None)
1212 field_type = Column("field_type", String(255), nullable=False, unique=None)
1199 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1200
1214
1201 repository = relationship('Repository')
1215 repository = relationship('Repository')
1202
1216
1203 @property
1217 @property
1204 def field_key_prefixed(self):
1218 def field_key_prefixed(self):
1205 return 'ex_%s' % self.field_key
1219 return 'ex_%s' % self.field_key
1206
1220
1207 @classmethod
1221 @classmethod
1208 def un_prefix_key(cls, key):
1222 def un_prefix_key(cls, key):
1209 if key.startswith(cls.PREFIX):
1223 if key.startswith(cls.PREFIX):
1210 return key[len(cls.PREFIX):]
1224 return key[len(cls.PREFIX):]
1211 return key
1225 return key
1212
1226
1213 @classmethod
1227 @classmethod
1214 def get_by_key_name(cls, key, repo):
1228 def get_by_key_name(cls, key, repo):
1215 row = cls.query()\
1229 row = cls.query()\
1216 .filter(cls.repository == repo)\
1230 .filter(cls.repository == repo)\
1217 .filter(cls.field_key == key).scalar()
1231 .filter(cls.field_key == key).scalar()
1218 return row
1232 return row
1219
1233
1220
1234
1221 class Repository(Base, BaseModel):
1235 class Repository(Base, BaseModel):
1222 __tablename__ = 'repositories'
1236 __tablename__ = 'repositories'
1223 __table_args__ = (
1237 __table_args__ = (
1224 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1238 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1225 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1226 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1227 )
1241 )
1228 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1242 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1229 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1243 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1230
1244
1231 STATE_CREATED = 'repo_state_created'
1245 STATE_CREATED = 'repo_state_created'
1232 STATE_PENDING = 'repo_state_pending'
1246 STATE_PENDING = 'repo_state_pending'
1233 STATE_ERROR = 'repo_state_error'
1247 STATE_ERROR = 'repo_state_error'
1234
1248
1235 LOCK_AUTOMATIC = 'lock_auto'
1249 LOCK_AUTOMATIC = 'lock_auto'
1236 LOCK_API = 'lock_api'
1250 LOCK_API = 'lock_api'
1237 LOCK_WEB = 'lock_web'
1251 LOCK_WEB = 'lock_web'
1238 LOCK_PULL = 'lock_pull'
1252 LOCK_PULL = 'lock_pull'
1239
1253
1240 NAME_SEP = URL_SEP
1254 NAME_SEP = URL_SEP
1241
1255
1242 repo_id = Column(
1256 repo_id = Column(
1243 "repo_id", Integer(), nullable=False, unique=True, default=None,
1257 "repo_id", Integer(), nullable=False, unique=True, default=None,
1244 primary_key=True)
1258 primary_key=True)
1245 _repo_name = Column(
1259 _repo_name = Column(
1246 "repo_name", Text(), nullable=False, default=None)
1260 "repo_name", Text(), nullable=False, default=None)
1247 _repo_name_hash = Column(
1261 _repo_name_hash = Column(
1248 "repo_name_hash", String(255), nullable=False, unique=True)
1262 "repo_name_hash", String(255), nullable=False, unique=True)
1249 repo_state = Column("repo_state", String(255), nullable=True)
1263 repo_state = Column("repo_state", String(255), nullable=True)
1250
1264
1251 clone_uri = Column(
1265 clone_uri = Column(
1252 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1266 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1253 default=None)
1267 default=None)
1254 repo_type = Column(
1268 repo_type = Column(
1255 "repo_type", String(255), nullable=False, unique=False, default=None)
1269 "repo_type", String(255), nullable=False, unique=False, default=None)
1256 user_id = Column(
1270 user_id = Column(
1257 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1271 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1258 unique=False, default=None)
1272 unique=False, default=None)
1259 private = Column(
1273 private = Column(
1260 "private", Boolean(), nullable=True, unique=None, default=None)
1274 "private", Boolean(), nullable=True, unique=None, default=None)
1261 enable_statistics = Column(
1275 enable_statistics = Column(
1262 "statistics", Boolean(), nullable=True, unique=None, default=True)
1276 "statistics", Boolean(), nullable=True, unique=None, default=True)
1263 enable_downloads = Column(
1277 enable_downloads = Column(
1264 "downloads", Boolean(), nullable=True, unique=None, default=True)
1278 "downloads", Boolean(), nullable=True, unique=None, default=True)
1265 description = Column(
1279 description = Column(
1266 "description", String(10000), nullable=True, unique=None, default=None)
1280 "description", String(10000), nullable=True, unique=None, default=None)
1267 created_on = Column(
1281 created_on = Column(
1268 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1282 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1269 default=datetime.datetime.now)
1283 default=datetime.datetime.now)
1270 updated_on = Column(
1284 updated_on = Column(
1271 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1285 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1272 default=datetime.datetime.now)
1286 default=datetime.datetime.now)
1273 _landing_revision = Column(
1287 _landing_revision = Column(
1274 "landing_revision", String(255), nullable=False, unique=False,
1288 "landing_revision", String(255), nullable=False, unique=False,
1275 default=None)
1289 default=None)
1276 enable_locking = Column(
1290 enable_locking = Column(
1277 "enable_locking", Boolean(), nullable=False, unique=None,
1291 "enable_locking", Boolean(), nullable=False, unique=None,
1278 default=False)
1292 default=False)
1279 _locked = Column(
1293 _locked = Column(
1280 "locked", String(255), nullable=True, unique=False, default=None)
1294 "locked", String(255), nullable=True, unique=False, default=None)
1281 _changeset_cache = Column(
1295 _changeset_cache = Column(
1282 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1296 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1283
1297
1284 fork_id = Column(
1298 fork_id = Column(
1285 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1299 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1286 nullable=True, unique=False, default=None)
1300 nullable=True, unique=False, default=None)
1287 group_id = Column(
1301 group_id = Column(
1288 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1302 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1289 unique=False, default=None)
1303 unique=False, default=None)
1290
1304
1291 user = relationship('User', lazy='joined')
1305 user = relationship('User', lazy='joined')
1292 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1306 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1293 group = relationship('RepoGroup', lazy='joined')
1307 group = relationship('RepoGroup', lazy='joined')
1294 repo_to_perm = relationship(
1308 repo_to_perm = relationship(
1295 'UserRepoToPerm', cascade='all',
1309 'UserRepoToPerm', cascade='all',
1296 order_by='UserRepoToPerm.repo_to_perm_id')
1310 order_by='UserRepoToPerm.repo_to_perm_id')
1297 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1311 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1298 stats = relationship('Statistics', cascade='all', uselist=False)
1312 stats = relationship('Statistics', cascade='all', uselist=False)
1299
1313
1300 followers = relationship(
1314 followers = relationship(
1301 'UserFollowing',
1315 'UserFollowing',
1302 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1316 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1303 cascade='all')
1317 cascade='all')
1304 extra_fields = relationship(
1318 extra_fields = relationship(
1305 'RepositoryField', cascade="all, delete, delete-orphan")
1319 'RepositoryField', cascade="all, delete, delete-orphan")
1306 logs = relationship('UserLog')
1320 logs = relationship('UserLog')
1307 comments = relationship(
1321 comments = relationship(
1308 'ChangesetComment', cascade="all, delete, delete-orphan")
1322 'ChangesetComment', cascade="all, delete, delete-orphan")
1309 pull_requests_source = relationship(
1323 pull_requests_source = relationship(
1310 'PullRequest',
1324 'PullRequest',
1311 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1325 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1312 cascade="all, delete, delete-orphan")
1326 cascade="all, delete, delete-orphan")
1313 pull_requests_target = relationship(
1327 pull_requests_target = relationship(
1314 'PullRequest',
1328 'PullRequest',
1315 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1329 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1316 cascade="all, delete, delete-orphan")
1330 cascade="all, delete, delete-orphan")
1317 ui = relationship('RepoRhodeCodeUi', cascade="all")
1331 ui = relationship('RepoRhodeCodeUi', cascade="all")
1318 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1332 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1319
1333
1320 def __unicode__(self):
1334 def __unicode__(self):
1321 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1335 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1322 safe_unicode(self.repo_name))
1336 safe_unicode(self.repo_name))
1323
1337
1324 @hybrid_property
1338 @hybrid_property
1325 def landing_rev(self):
1339 def landing_rev(self):
1326 # always should return [rev_type, rev]
1340 # always should return [rev_type, rev]
1327 if self._landing_revision:
1341 if self._landing_revision:
1328 _rev_info = self._landing_revision.split(':')
1342 _rev_info = self._landing_revision.split(':')
1329 if len(_rev_info) < 2:
1343 if len(_rev_info) < 2:
1330 _rev_info.insert(0, 'rev')
1344 _rev_info.insert(0, 'rev')
1331 return [_rev_info[0], _rev_info[1]]
1345 return [_rev_info[0], _rev_info[1]]
1332 return [None, None]
1346 return [None, None]
1333
1347
1334 @landing_rev.setter
1348 @landing_rev.setter
1335 def landing_rev(self, val):
1349 def landing_rev(self, val):
1336 if ':' not in val:
1350 if ':' not in val:
1337 raise ValueError('value must be delimited with `:` and consist '
1351 raise ValueError('value must be delimited with `:` and consist '
1338 'of <rev_type>:<rev>, got %s instead' % val)
1352 'of <rev_type>:<rev>, got %s instead' % val)
1339 self._landing_revision = val
1353 self._landing_revision = val
1340
1354
1341 @hybrid_property
1355 @hybrid_property
1342 def locked(self):
1356 def locked(self):
1343 if self._locked:
1357 if self._locked:
1344 user_id, timelocked, reason = self._locked.split(':')
1358 user_id, timelocked, reason = self._locked.split(':')
1345 lock_values = int(user_id), timelocked, reason
1359 lock_values = int(user_id), timelocked, reason
1346 else:
1360 else:
1347 lock_values = [None, None, None]
1361 lock_values = [None, None, None]
1348 return lock_values
1362 return lock_values
1349
1363
1350 @locked.setter
1364 @locked.setter
1351 def locked(self, val):
1365 def locked(self, val):
1352 if val and isinstance(val, (list, tuple)):
1366 if val and isinstance(val, (list, tuple)):
1353 self._locked = ':'.join(map(str, val))
1367 self._locked = ':'.join(map(str, val))
1354 else:
1368 else:
1355 self._locked = None
1369 self._locked = None
1356
1370
1357 @hybrid_property
1371 @hybrid_property
1358 def changeset_cache(self):
1372 def changeset_cache(self):
1359 from rhodecode.lib.vcs.backends.base import EmptyCommit
1373 from rhodecode.lib.vcs.backends.base import EmptyCommit
1360 dummy = EmptyCommit().__json__()
1374 dummy = EmptyCommit().__json__()
1361 if not self._changeset_cache:
1375 if not self._changeset_cache:
1362 return dummy
1376 return dummy
1363 try:
1377 try:
1364 return json.loads(self._changeset_cache)
1378 return json.loads(self._changeset_cache)
1365 except TypeError:
1379 except TypeError:
1366 return dummy
1380 return dummy
1367 except Exception:
1381 except Exception:
1368 log.error(traceback.format_exc())
1382 log.error(traceback.format_exc())
1369 return dummy
1383 return dummy
1370
1384
1371 @changeset_cache.setter
1385 @changeset_cache.setter
1372 def changeset_cache(self, val):
1386 def changeset_cache(self, val):
1373 try:
1387 try:
1374 self._changeset_cache = json.dumps(val)
1388 self._changeset_cache = json.dumps(val)
1375 except Exception:
1389 except Exception:
1376 log.error(traceback.format_exc())
1390 log.error(traceback.format_exc())
1377
1391
1378 @hybrid_property
1392 @hybrid_property
1379 def repo_name(self):
1393 def repo_name(self):
1380 return self._repo_name
1394 return self._repo_name
1381
1395
1382 @repo_name.setter
1396 @repo_name.setter
1383 def repo_name(self, value):
1397 def repo_name(self, value):
1384 self._repo_name = value
1398 self._repo_name = value
1385 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1399 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1386
1400
1387 @classmethod
1401 @classmethod
1388 def normalize_repo_name(cls, repo_name):
1402 def normalize_repo_name(cls, repo_name):
1389 """
1403 """
1390 Normalizes os specific repo_name to the format internally stored inside
1404 Normalizes os specific repo_name to the format internally stored inside
1391 database using URL_SEP
1405 database using URL_SEP
1392
1406
1393 :param cls:
1407 :param cls:
1394 :param repo_name:
1408 :param repo_name:
1395 """
1409 """
1396 return cls.NAME_SEP.join(repo_name.split(os.sep))
1410 return cls.NAME_SEP.join(repo_name.split(os.sep))
1397
1411
1398 @classmethod
1412 @classmethod
1399 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1413 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1400 session = Session()
1414 session = Session()
1401 q = session.query(cls).filter(cls.repo_name == repo_name)
1415 q = session.query(cls).filter(cls.repo_name == repo_name)
1402
1416
1403 if cache:
1417 if cache:
1404 if identity_cache:
1418 if identity_cache:
1405 val = cls.identity_cache(session, 'repo_name', repo_name)
1419 val = cls.identity_cache(session, 'repo_name', repo_name)
1406 if val:
1420 if val:
1407 return val
1421 return val
1408 else:
1422 else:
1409 q = q.options(
1423 q = q.options(
1410 FromCache("sql_cache_short",
1424 FromCache("sql_cache_short",
1411 "get_repo_by_name_%s" % _hash_key(repo_name)))
1425 "get_repo_by_name_%s" % _hash_key(repo_name)))
1412
1426
1413 return q.scalar()
1427 return q.scalar()
1414
1428
1415 @classmethod
1429 @classmethod
1416 def get_by_full_path(cls, repo_full_path):
1430 def get_by_full_path(cls, repo_full_path):
1417 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1431 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1418 repo_name = cls.normalize_repo_name(repo_name)
1432 repo_name = cls.normalize_repo_name(repo_name)
1419 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1433 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1420
1434
1421 @classmethod
1435 @classmethod
1422 def get_repo_forks(cls, repo_id):
1436 def get_repo_forks(cls, repo_id):
1423 return cls.query().filter(Repository.fork_id == repo_id)
1437 return cls.query().filter(Repository.fork_id == repo_id)
1424
1438
1425 @classmethod
1439 @classmethod
1426 def base_path(cls):
1440 def base_path(cls):
1427 """
1441 """
1428 Returns base path when all repos are stored
1442 Returns base path when all repos are stored
1429
1443
1430 :param cls:
1444 :param cls:
1431 """
1445 """
1432 q = Session().query(RhodeCodeUi)\
1446 q = Session().query(RhodeCodeUi)\
1433 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1447 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1434 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1448 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1435 return q.one().ui_value
1449 return q.one().ui_value
1436
1450
1437 @classmethod
1451 @classmethod
1438 def is_valid(cls, repo_name):
1452 def is_valid(cls, repo_name):
1439 """
1453 """
1440 returns True if given repo name is a valid filesystem repository
1454 returns True if given repo name is a valid filesystem repository
1441
1455
1442 :param cls:
1456 :param cls:
1443 :param repo_name:
1457 :param repo_name:
1444 """
1458 """
1445 from rhodecode.lib.utils import is_valid_repo
1459 from rhodecode.lib.utils import is_valid_repo
1446
1460
1447 return is_valid_repo(repo_name, cls.base_path())
1461 return is_valid_repo(repo_name, cls.base_path())
1448
1462
1449 @classmethod
1463 @classmethod
1450 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1464 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1451 case_insensitive=True):
1465 case_insensitive=True):
1452 q = Repository.query()
1466 q = Repository.query()
1453
1467
1454 if not isinstance(user_id, Optional):
1468 if not isinstance(user_id, Optional):
1455 q = q.filter(Repository.user_id == user_id)
1469 q = q.filter(Repository.user_id == user_id)
1456
1470
1457 if not isinstance(group_id, Optional):
1471 if not isinstance(group_id, Optional):
1458 q = q.filter(Repository.group_id == group_id)
1472 q = q.filter(Repository.group_id == group_id)
1459
1473
1460 if case_insensitive:
1474 if case_insensitive:
1461 q = q.order_by(func.lower(Repository.repo_name))
1475 q = q.order_by(func.lower(Repository.repo_name))
1462 else:
1476 else:
1463 q = q.order_by(Repository.repo_name)
1477 q = q.order_by(Repository.repo_name)
1464 return q.all()
1478 return q.all()
1465
1479
1466 @property
1480 @property
1467 def forks(self):
1481 def forks(self):
1468 """
1482 """
1469 Return forks of this repo
1483 Return forks of this repo
1470 """
1484 """
1471 return Repository.get_repo_forks(self.repo_id)
1485 return Repository.get_repo_forks(self.repo_id)
1472
1486
1473 @property
1487 @property
1474 def parent(self):
1488 def parent(self):
1475 """
1489 """
1476 Returns fork parent
1490 Returns fork parent
1477 """
1491 """
1478 return self.fork
1492 return self.fork
1479
1493
1480 @property
1494 @property
1481 def just_name(self):
1495 def just_name(self):
1482 return self.repo_name.split(self.NAME_SEP)[-1]
1496 return self.repo_name.split(self.NAME_SEP)[-1]
1483
1497
1484 @property
1498 @property
1485 def groups_with_parents(self):
1499 def groups_with_parents(self):
1486 groups = []
1500 groups = []
1487 if self.group is None:
1501 if self.group is None:
1488 return groups
1502 return groups
1489
1503
1490 cur_gr = self.group
1504 cur_gr = self.group
1491 groups.insert(0, cur_gr)
1505 groups.insert(0, cur_gr)
1492 while 1:
1506 while 1:
1493 gr = getattr(cur_gr, 'parent_group', None)
1507 gr = getattr(cur_gr, 'parent_group', None)
1494 cur_gr = cur_gr.parent_group
1508 cur_gr = cur_gr.parent_group
1495 if gr is None:
1509 if gr is None:
1496 break
1510 break
1497 groups.insert(0, gr)
1511 groups.insert(0, gr)
1498
1512
1499 return groups
1513 return groups
1500
1514
1501 @property
1515 @property
1502 def groups_and_repo(self):
1516 def groups_and_repo(self):
1503 return self.groups_with_parents, self
1517 return self.groups_with_parents, self
1504
1518
1505 @LazyProperty
1519 @LazyProperty
1506 def repo_path(self):
1520 def repo_path(self):
1507 """
1521 """
1508 Returns base full path for that repository means where it actually
1522 Returns base full path for that repository means where it actually
1509 exists on a filesystem
1523 exists on a filesystem
1510 """
1524 """
1511 q = Session().query(RhodeCodeUi).filter(
1525 q = Session().query(RhodeCodeUi).filter(
1512 RhodeCodeUi.ui_key == self.NAME_SEP)
1526 RhodeCodeUi.ui_key == self.NAME_SEP)
1513 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1527 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1514 return q.one().ui_value
1528 return q.one().ui_value
1515
1529
1516 @property
1530 @property
1517 def repo_full_path(self):
1531 def repo_full_path(self):
1518 p = [self.repo_path]
1532 p = [self.repo_path]
1519 # we need to split the name by / since this is how we store the
1533 # we need to split the name by / since this is how we store the
1520 # names in the database, but that eventually needs to be converted
1534 # names in the database, but that eventually needs to be converted
1521 # into a valid system path
1535 # into a valid system path
1522 p += self.repo_name.split(self.NAME_SEP)
1536 p += self.repo_name.split(self.NAME_SEP)
1523 return os.path.join(*map(safe_unicode, p))
1537 return os.path.join(*map(safe_unicode, p))
1524
1538
1525 @property
1539 @property
1526 def cache_keys(self):
1540 def cache_keys(self):
1527 """
1541 """
1528 Returns associated cache keys for that repo
1542 Returns associated cache keys for that repo
1529 """
1543 """
1530 return CacheKey.query()\
1544 return CacheKey.query()\
1531 .filter(CacheKey.cache_args == self.repo_name)\
1545 .filter(CacheKey.cache_args == self.repo_name)\
1532 .order_by(CacheKey.cache_key)\
1546 .order_by(CacheKey.cache_key)\
1533 .all()
1547 .all()
1534
1548
1535 def get_new_name(self, repo_name):
1549 def get_new_name(self, repo_name):
1536 """
1550 """
1537 returns new full repository name based on assigned group and new new
1551 returns new full repository name based on assigned group and new new
1538
1552
1539 :param group_name:
1553 :param group_name:
1540 """
1554 """
1541 path_prefix = self.group.full_path_splitted if self.group else []
1555 path_prefix = self.group.full_path_splitted if self.group else []
1542 return self.NAME_SEP.join(path_prefix + [repo_name])
1556 return self.NAME_SEP.join(path_prefix + [repo_name])
1543
1557
1544 @property
1558 @property
1545 def _config(self):
1559 def _config(self):
1546 """
1560 """
1547 Returns db based config object.
1561 Returns db based config object.
1548 """
1562 """
1549 from rhodecode.lib.utils import make_db_config
1563 from rhodecode.lib.utils import make_db_config
1550 return make_db_config(clear_session=False, repo=self)
1564 return make_db_config(clear_session=False, repo=self)
1551
1565
1552 def permissions(self, with_admins=True, with_owner=True):
1566 def permissions(self, with_admins=True, with_owner=True):
1553 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1567 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1554 q = q.options(joinedload(UserRepoToPerm.repository),
1568 q = q.options(joinedload(UserRepoToPerm.repository),
1555 joinedload(UserRepoToPerm.user),
1569 joinedload(UserRepoToPerm.user),
1556 joinedload(UserRepoToPerm.permission),)
1570 joinedload(UserRepoToPerm.permission),)
1557
1571
1558 # get owners and admins and permissions. We do a trick of re-writing
1572 # get owners and admins and permissions. We do a trick of re-writing
1559 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1573 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1560 # has a global reference and changing one object propagates to all
1574 # has a global reference and changing one object propagates to all
1561 # others. This means if admin is also an owner admin_row that change
1575 # others. This means if admin is also an owner admin_row that change
1562 # would propagate to both objects
1576 # would propagate to both objects
1563 perm_rows = []
1577 perm_rows = []
1564 for _usr in q.all():
1578 for _usr in q.all():
1565 usr = AttributeDict(_usr.user.get_dict())
1579 usr = AttributeDict(_usr.user.get_dict())
1566 usr.permission = _usr.permission.permission_name
1580 usr.permission = _usr.permission.permission_name
1567 perm_rows.append(usr)
1581 perm_rows.append(usr)
1568
1582
1569 # filter the perm rows by 'default' first and then sort them by
1583 # filter the perm rows by 'default' first and then sort them by
1570 # admin,write,read,none permissions sorted again alphabetically in
1584 # admin,write,read,none permissions sorted again alphabetically in
1571 # each group
1585 # each group
1572 perm_rows = sorted(perm_rows, key=display_sort)
1586 perm_rows = sorted(perm_rows, key=display_sort)
1573
1587
1574 _admin_perm = 'repository.admin'
1588 _admin_perm = 'repository.admin'
1575 owner_row = []
1589 owner_row = []
1576 if with_owner:
1590 if with_owner:
1577 usr = AttributeDict(self.user.get_dict())
1591 usr = AttributeDict(self.user.get_dict())
1578 usr.owner_row = True
1592 usr.owner_row = True
1579 usr.permission = _admin_perm
1593 usr.permission = _admin_perm
1580 owner_row.append(usr)
1594 owner_row.append(usr)
1581
1595
1582 super_admin_rows = []
1596 super_admin_rows = []
1583 if with_admins:
1597 if with_admins:
1584 for usr in User.get_all_super_admins():
1598 for usr in User.get_all_super_admins():
1585 # if this admin is also owner, don't double the record
1599 # if this admin is also owner, don't double the record
1586 if usr.user_id == owner_row[0].user_id:
1600 if usr.user_id == owner_row[0].user_id:
1587 owner_row[0].admin_row = True
1601 owner_row[0].admin_row = True
1588 else:
1602 else:
1589 usr = AttributeDict(usr.get_dict())
1603 usr = AttributeDict(usr.get_dict())
1590 usr.admin_row = True
1604 usr.admin_row = True
1591 usr.permission = _admin_perm
1605 usr.permission = _admin_perm
1592 super_admin_rows.append(usr)
1606 super_admin_rows.append(usr)
1593
1607
1594 return super_admin_rows + owner_row + perm_rows
1608 return super_admin_rows + owner_row + perm_rows
1595
1609
1596 def permission_user_groups(self):
1610 def permission_user_groups(self):
1597 q = UserGroupRepoToPerm.query().filter(
1611 q = UserGroupRepoToPerm.query().filter(
1598 UserGroupRepoToPerm.repository == self)
1612 UserGroupRepoToPerm.repository == self)
1599 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1613 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1600 joinedload(UserGroupRepoToPerm.users_group),
1614 joinedload(UserGroupRepoToPerm.users_group),
1601 joinedload(UserGroupRepoToPerm.permission),)
1615 joinedload(UserGroupRepoToPerm.permission),)
1602
1616
1603 perm_rows = []
1617 perm_rows = []
1604 for _user_group in q.all():
1618 for _user_group in q.all():
1605 usr = AttributeDict(_user_group.users_group.get_dict())
1619 usr = AttributeDict(_user_group.users_group.get_dict())
1606 usr.permission = _user_group.permission.permission_name
1620 usr.permission = _user_group.permission.permission_name
1607 perm_rows.append(usr)
1621 perm_rows.append(usr)
1608
1622
1609 return perm_rows
1623 return perm_rows
1610
1624
1611 def get_api_data(self, include_secrets=False):
1625 def get_api_data(self, include_secrets=False):
1612 """
1626 """
1613 Common function for generating repo api data
1627 Common function for generating repo api data
1614
1628
1615 :param include_secrets: See :meth:`User.get_api_data`.
1629 :param include_secrets: See :meth:`User.get_api_data`.
1616
1630
1617 """
1631 """
1618 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1632 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1619 # move this methods on models level.
1633 # move this methods on models level.
1620 from rhodecode.model.settings import SettingsModel
1634 from rhodecode.model.settings import SettingsModel
1621
1635
1622 repo = self
1636 repo = self
1623 _user_id, _time, _reason = self.locked
1637 _user_id, _time, _reason = self.locked
1624
1638
1625 data = {
1639 data = {
1626 'repo_id': repo.repo_id,
1640 'repo_id': repo.repo_id,
1627 'repo_name': repo.repo_name,
1641 'repo_name': repo.repo_name,
1628 'repo_type': repo.repo_type,
1642 'repo_type': repo.repo_type,
1629 'clone_uri': repo.clone_uri or '',
1643 'clone_uri': repo.clone_uri or '',
1630 'private': repo.private,
1644 'private': repo.private,
1631 'created_on': repo.created_on,
1645 'created_on': repo.created_on,
1632 'description': repo.description,
1646 'description': repo.description,
1633 'landing_rev': repo.landing_rev,
1647 'landing_rev': repo.landing_rev,
1634 'owner': repo.user.username,
1648 'owner': repo.user.username,
1635 'fork_of': repo.fork.repo_name if repo.fork else None,
1649 'fork_of': repo.fork.repo_name if repo.fork else None,
1636 'enable_statistics': repo.enable_statistics,
1650 'enable_statistics': repo.enable_statistics,
1637 'enable_locking': repo.enable_locking,
1651 'enable_locking': repo.enable_locking,
1638 'enable_downloads': repo.enable_downloads,
1652 'enable_downloads': repo.enable_downloads,
1639 'last_changeset': repo.changeset_cache,
1653 'last_changeset': repo.changeset_cache,
1640 'locked_by': User.get(_user_id).get_api_data(
1654 'locked_by': User.get(_user_id).get_api_data(
1641 include_secrets=include_secrets) if _user_id else None,
1655 include_secrets=include_secrets) if _user_id else None,
1642 'locked_date': time_to_datetime(_time) if _time else None,
1656 'locked_date': time_to_datetime(_time) if _time else None,
1643 'lock_reason': _reason if _reason else None,
1657 'lock_reason': _reason if _reason else None,
1644 }
1658 }
1645
1659
1646 # TODO: mikhail: should be per-repo settings here
1660 # TODO: mikhail: should be per-repo settings here
1647 rc_config = SettingsModel().get_all_settings()
1661 rc_config = SettingsModel().get_all_settings()
1648 repository_fields = str2bool(
1662 repository_fields = str2bool(
1649 rc_config.get('rhodecode_repository_fields'))
1663 rc_config.get('rhodecode_repository_fields'))
1650 if repository_fields:
1664 if repository_fields:
1651 for f in self.extra_fields:
1665 for f in self.extra_fields:
1652 data[f.field_key_prefixed] = f.field_value
1666 data[f.field_key_prefixed] = f.field_value
1653
1667
1654 return data
1668 return data
1655
1669
1656 @classmethod
1670 @classmethod
1657 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1671 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1658 if not lock_time:
1672 if not lock_time:
1659 lock_time = time.time()
1673 lock_time = time.time()
1660 if not lock_reason:
1674 if not lock_reason:
1661 lock_reason = cls.LOCK_AUTOMATIC
1675 lock_reason = cls.LOCK_AUTOMATIC
1662 repo.locked = [user_id, lock_time, lock_reason]
1676 repo.locked = [user_id, lock_time, lock_reason]
1663 Session().add(repo)
1677 Session().add(repo)
1664 Session().commit()
1678 Session().commit()
1665
1679
1666 @classmethod
1680 @classmethod
1667 def unlock(cls, repo):
1681 def unlock(cls, repo):
1668 repo.locked = None
1682 repo.locked = None
1669 Session().add(repo)
1683 Session().add(repo)
1670 Session().commit()
1684 Session().commit()
1671
1685
1672 @classmethod
1686 @classmethod
1673 def getlock(cls, repo):
1687 def getlock(cls, repo):
1674 return repo.locked
1688 return repo.locked
1675
1689
1676 def is_user_lock(self, user_id):
1690 def is_user_lock(self, user_id):
1677 if self.lock[0]:
1691 if self.lock[0]:
1678 lock_user_id = safe_int(self.lock[0])
1692 lock_user_id = safe_int(self.lock[0])
1679 user_id = safe_int(user_id)
1693 user_id = safe_int(user_id)
1680 # both are ints, and they are equal
1694 # both are ints, and they are equal
1681 return all([lock_user_id, user_id]) and lock_user_id == user_id
1695 return all([lock_user_id, user_id]) and lock_user_id == user_id
1682
1696
1683 return False
1697 return False
1684
1698
1685 def get_locking_state(self, action, user_id, only_when_enabled=True):
1699 def get_locking_state(self, action, user_id, only_when_enabled=True):
1686 """
1700 """
1687 Checks locking on this repository, if locking is enabled and lock is
1701 Checks locking on this repository, if locking is enabled and lock is
1688 present returns a tuple of make_lock, locked, locked_by.
1702 present returns a tuple of make_lock, locked, locked_by.
1689 make_lock can have 3 states None (do nothing) True, make lock
1703 make_lock can have 3 states None (do nothing) True, make lock
1690 False release lock, This value is later propagated to hooks, which
1704 False release lock, This value is later propagated to hooks, which
1691 do the locking. Think about this as signals passed to hooks what to do.
1705 do the locking. Think about this as signals passed to hooks what to do.
1692
1706
1693 """
1707 """
1694 # TODO: johbo: This is part of the business logic and should be moved
1708 # TODO: johbo: This is part of the business logic and should be moved
1695 # into the RepositoryModel.
1709 # into the RepositoryModel.
1696
1710
1697 if action not in ('push', 'pull'):
1711 if action not in ('push', 'pull'):
1698 raise ValueError("Invalid action value: %s" % repr(action))
1712 raise ValueError("Invalid action value: %s" % repr(action))
1699
1713
1700 # defines if locked error should be thrown to user
1714 # defines if locked error should be thrown to user
1701 currently_locked = False
1715 currently_locked = False
1702 # defines if new lock should be made, tri-state
1716 # defines if new lock should be made, tri-state
1703 make_lock = None
1717 make_lock = None
1704 repo = self
1718 repo = self
1705 user = User.get(user_id)
1719 user = User.get(user_id)
1706
1720
1707 lock_info = repo.locked
1721 lock_info = repo.locked
1708
1722
1709 if repo and (repo.enable_locking or not only_when_enabled):
1723 if repo and (repo.enable_locking or not only_when_enabled):
1710 if action == 'push':
1724 if action == 'push':
1711 # check if it's already locked !, if it is compare users
1725 # check if it's already locked !, if it is compare users
1712 locked_by_user_id = lock_info[0]
1726 locked_by_user_id = lock_info[0]
1713 if user.user_id == locked_by_user_id:
1727 if user.user_id == locked_by_user_id:
1714 log.debug(
1728 log.debug(
1715 'Got `push` action from user %s, now unlocking', user)
1729 'Got `push` action from user %s, now unlocking', user)
1716 # unlock if we have push from user who locked
1730 # unlock if we have push from user who locked
1717 make_lock = False
1731 make_lock = False
1718 else:
1732 else:
1719 # we're not the same user who locked, ban with
1733 # we're not the same user who locked, ban with
1720 # code defined in settings (default is 423 HTTP Locked) !
1734 # code defined in settings (default is 423 HTTP Locked) !
1721 log.debug('Repo %s is currently locked by %s', repo, user)
1735 log.debug('Repo %s is currently locked by %s', repo, user)
1722 currently_locked = True
1736 currently_locked = True
1723 elif action == 'pull':
1737 elif action == 'pull':
1724 # [0] user [1] date
1738 # [0] user [1] date
1725 if lock_info[0] and lock_info[1]:
1739 if lock_info[0] and lock_info[1]:
1726 log.debug('Repo %s is currently locked by %s', repo, user)
1740 log.debug('Repo %s is currently locked by %s', repo, user)
1727 currently_locked = True
1741 currently_locked = True
1728 else:
1742 else:
1729 log.debug('Setting lock on repo %s by %s', repo, user)
1743 log.debug('Setting lock on repo %s by %s', repo, user)
1730 make_lock = True
1744 make_lock = True
1731
1745
1732 else:
1746 else:
1733 log.debug('Repository %s do not have locking enabled', repo)
1747 log.debug('Repository %s do not have locking enabled', repo)
1734
1748
1735 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1749 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1736 make_lock, currently_locked, lock_info)
1750 make_lock, currently_locked, lock_info)
1737
1751
1738 from rhodecode.lib.auth import HasRepoPermissionAny
1752 from rhodecode.lib.auth import HasRepoPermissionAny
1739 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1753 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1740 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1754 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1741 # if we don't have at least write permission we cannot make a lock
1755 # if we don't have at least write permission we cannot make a lock
1742 log.debug('lock state reset back to FALSE due to lack '
1756 log.debug('lock state reset back to FALSE due to lack '
1743 'of at least read permission')
1757 'of at least read permission')
1744 make_lock = False
1758 make_lock = False
1745
1759
1746 return make_lock, currently_locked, lock_info
1760 return make_lock, currently_locked, lock_info
1747
1761
1748 @property
1762 @property
1749 def last_db_change(self):
1763 def last_db_change(self):
1750 return self.updated_on
1764 return self.updated_on
1751
1765
1752 @property
1766 @property
1753 def clone_uri_hidden(self):
1767 def clone_uri_hidden(self):
1754 clone_uri = self.clone_uri
1768 clone_uri = self.clone_uri
1755 if clone_uri:
1769 if clone_uri:
1756 import urlobject
1770 import urlobject
1757 url_obj = urlobject.URLObject(self.clone_uri)
1771 url_obj = urlobject.URLObject(clone_uri)
1758 if url_obj.password:
1772 if url_obj.password:
1759 clone_uri = url_obj.with_password('*****')
1773 clone_uri = url_obj.with_password('*****')
1760 return clone_uri
1774 return clone_uri
1761
1775
1762 def clone_url(self, **override):
1776 def clone_url(self, **override):
1763 qualified_home_url = url('home', qualified=True)
1777 qualified_home_url = url('home', qualified=True)
1764
1778
1765 uri_tmpl = None
1779 uri_tmpl = None
1766 if 'with_id' in override:
1780 if 'with_id' in override:
1767 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1781 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1768 del override['with_id']
1782 del override['with_id']
1769
1783
1770 if 'uri_tmpl' in override:
1784 if 'uri_tmpl' in override:
1771 uri_tmpl = override['uri_tmpl']
1785 uri_tmpl = override['uri_tmpl']
1772 del override['uri_tmpl']
1786 del override['uri_tmpl']
1773
1787
1774 # we didn't override our tmpl from **overrides
1788 # we didn't override our tmpl from **overrides
1775 if not uri_tmpl:
1789 if not uri_tmpl:
1776 uri_tmpl = self.DEFAULT_CLONE_URI
1790 uri_tmpl = self.DEFAULT_CLONE_URI
1777 try:
1791 try:
1778 from pylons import tmpl_context as c
1792 from pylons import tmpl_context as c
1779 uri_tmpl = c.clone_uri_tmpl
1793 uri_tmpl = c.clone_uri_tmpl
1780 except Exception:
1794 except Exception:
1781 # in any case if we call this outside of request context,
1795 # in any case if we call this outside of request context,
1782 # ie, not having tmpl_context set up
1796 # ie, not having tmpl_context set up
1783 pass
1797 pass
1784
1798
1785 return get_clone_url(uri_tmpl=uri_tmpl,
1799 return get_clone_url(uri_tmpl=uri_tmpl,
1786 qualifed_home_url=qualified_home_url,
1800 qualifed_home_url=qualified_home_url,
1787 repo_name=self.repo_name,
1801 repo_name=self.repo_name,
1788 repo_id=self.repo_id, **override)
1802 repo_id=self.repo_id, **override)
1789
1803
1790 def set_state(self, state):
1804 def set_state(self, state):
1791 self.repo_state = state
1805 self.repo_state = state
1792 Session().add(self)
1806 Session().add(self)
1793 #==========================================================================
1807 #==========================================================================
1794 # SCM PROPERTIES
1808 # SCM PROPERTIES
1795 #==========================================================================
1809 #==========================================================================
1796
1810
1797 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1811 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1798 return get_commit_safe(
1812 return get_commit_safe(
1799 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1813 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1800
1814
1801 def get_changeset(self, rev=None, pre_load=None):
1815 def get_changeset(self, rev=None, pre_load=None):
1802 warnings.warn("Use get_commit", DeprecationWarning)
1816 warnings.warn("Use get_commit", DeprecationWarning)
1803 commit_id = None
1817 commit_id = None
1804 commit_idx = None
1818 commit_idx = None
1805 if isinstance(rev, basestring):
1819 if isinstance(rev, basestring):
1806 commit_id = rev
1820 commit_id = rev
1807 else:
1821 else:
1808 commit_idx = rev
1822 commit_idx = rev
1809 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1823 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1810 pre_load=pre_load)
1824 pre_load=pre_load)
1811
1825
1812 def get_landing_commit(self):
1826 def get_landing_commit(self):
1813 """
1827 """
1814 Returns landing commit, or if that doesn't exist returns the tip
1828 Returns landing commit, or if that doesn't exist returns the tip
1815 """
1829 """
1816 _rev_type, _rev = self.landing_rev
1830 _rev_type, _rev = self.landing_rev
1817 commit = self.get_commit(_rev)
1831 commit = self.get_commit(_rev)
1818 if isinstance(commit, EmptyCommit):
1832 if isinstance(commit, EmptyCommit):
1819 return self.get_commit()
1833 return self.get_commit()
1820 return commit
1834 return commit
1821
1835
1822 def update_commit_cache(self, cs_cache=None, config=None):
1836 def update_commit_cache(self, cs_cache=None, config=None):
1823 """
1837 """
1824 Update cache of last changeset for repository, keys should be::
1838 Update cache of last changeset for repository, keys should be::
1825
1839
1826 short_id
1840 short_id
1827 raw_id
1841 raw_id
1828 revision
1842 revision
1829 parents
1843 parents
1830 message
1844 message
1831 date
1845 date
1832 author
1846 author
1833
1847
1834 :param cs_cache:
1848 :param cs_cache:
1835 """
1849 """
1836 from rhodecode.lib.vcs.backends.base import BaseChangeset
1850 from rhodecode.lib.vcs.backends.base import BaseChangeset
1837 if cs_cache is None:
1851 if cs_cache is None:
1838 # use no-cache version here
1852 # use no-cache version here
1839 scm_repo = self.scm_instance(cache=False, config=config)
1853 scm_repo = self.scm_instance(cache=False, config=config)
1840 if scm_repo:
1854 if scm_repo:
1841 cs_cache = scm_repo.get_commit(
1855 cs_cache = scm_repo.get_commit(
1842 pre_load=["author", "date", "message", "parents"])
1856 pre_load=["author", "date", "message", "parents"])
1843 else:
1857 else:
1844 cs_cache = EmptyCommit()
1858 cs_cache = EmptyCommit()
1845
1859
1846 if isinstance(cs_cache, BaseChangeset):
1860 if isinstance(cs_cache, BaseChangeset):
1847 cs_cache = cs_cache.__json__()
1861 cs_cache = cs_cache.__json__()
1848
1862
1849 def is_outdated(new_cs_cache):
1863 def is_outdated(new_cs_cache):
1850 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1864 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1851 return True
1865 return True
1852 return False
1866 return False
1853
1867
1854 # check if we have maybe already latest cached revision
1868 # check if we have maybe already latest cached revision
1855 if is_outdated(cs_cache) or not self.changeset_cache:
1869 if is_outdated(cs_cache) or not self.changeset_cache:
1856 _default = datetime.datetime.fromtimestamp(0)
1870 _default = datetime.datetime.fromtimestamp(0)
1857 last_change = cs_cache.get('date') or _default
1871 last_change = cs_cache.get('date') or _default
1858 log.debug('updated repo %s with new cs cache %s',
1872 log.debug('updated repo %s with new cs cache %s',
1859 self.repo_name, cs_cache)
1873 self.repo_name, cs_cache)
1860 self.updated_on = last_change
1874 self.updated_on = last_change
1861 self.changeset_cache = cs_cache
1875 self.changeset_cache = cs_cache
1862 Session().add(self)
1876 Session().add(self)
1863 Session().commit()
1877 Session().commit()
1864 else:
1878 else:
1865 log.debug('Skipping update_commit_cache for repo:`%s` '
1879 log.debug('Skipping update_commit_cache for repo:`%s` '
1866 'commit already with latest changes', self.repo_name)
1880 'commit already with latest changes', self.repo_name)
1867
1881
1868 @property
1882 @property
1869 def tip(self):
1883 def tip(self):
1870 return self.get_commit('tip')
1884 return self.get_commit('tip')
1871
1885
1872 @property
1886 @property
1873 def author(self):
1887 def author(self):
1874 return self.tip.author
1888 return self.tip.author
1875
1889
1876 @property
1890 @property
1877 def last_change(self):
1891 def last_change(self):
1878 return self.scm_instance().last_change
1892 return self.scm_instance().last_change
1879
1893
1880 def get_comments(self, revisions=None):
1894 def get_comments(self, revisions=None):
1881 """
1895 """
1882 Returns comments for this repository grouped by revisions
1896 Returns comments for this repository grouped by revisions
1883
1897
1884 :param revisions: filter query by revisions only
1898 :param revisions: filter query by revisions only
1885 """
1899 """
1886 cmts = ChangesetComment.query()\
1900 cmts = ChangesetComment.query()\
1887 .filter(ChangesetComment.repo == self)
1901 .filter(ChangesetComment.repo == self)
1888 if revisions:
1902 if revisions:
1889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1903 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1890 grouped = collections.defaultdict(list)
1904 grouped = collections.defaultdict(list)
1891 for cmt in cmts.all():
1905 for cmt in cmts.all():
1892 grouped[cmt.revision].append(cmt)
1906 grouped[cmt.revision].append(cmt)
1893 return grouped
1907 return grouped
1894
1908
1895 def statuses(self, revisions=None):
1909 def statuses(self, revisions=None):
1896 """
1910 """
1897 Returns statuses for this repository
1911 Returns statuses for this repository
1898
1912
1899 :param revisions: list of revisions to get statuses for
1913 :param revisions: list of revisions to get statuses for
1900 """
1914 """
1901 statuses = ChangesetStatus.query()\
1915 statuses = ChangesetStatus.query()\
1902 .filter(ChangesetStatus.repo == self)\
1916 .filter(ChangesetStatus.repo == self)\
1903 .filter(ChangesetStatus.version == 0)
1917 .filter(ChangesetStatus.version == 0)
1904
1918
1905 if revisions:
1919 if revisions:
1906 # Try doing the filtering in chunks to avoid hitting limits
1920 # Try doing the filtering in chunks to avoid hitting limits
1907 size = 500
1921 size = 500
1908 status_results = []
1922 status_results = []
1909 for chunk in xrange(0, len(revisions), size):
1923 for chunk in xrange(0, len(revisions), size):
1910 status_results += statuses.filter(
1924 status_results += statuses.filter(
1911 ChangesetStatus.revision.in_(
1925 ChangesetStatus.revision.in_(
1912 revisions[chunk: chunk+size])
1926 revisions[chunk: chunk+size])
1913 ).all()
1927 ).all()
1914 else:
1928 else:
1915 status_results = statuses.all()
1929 status_results = statuses.all()
1916
1930
1917 grouped = {}
1931 grouped = {}
1918
1932
1919 # maybe we have open new pullrequest without a status?
1933 # maybe we have open new pullrequest without a status?
1920 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1934 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1921 status_lbl = ChangesetStatus.get_status_lbl(stat)
1935 status_lbl = ChangesetStatus.get_status_lbl(stat)
1922 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1936 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1923 for rev in pr.revisions:
1937 for rev in pr.revisions:
1924 pr_id = pr.pull_request_id
1938 pr_id = pr.pull_request_id
1925 pr_repo = pr.target_repo.repo_name
1939 pr_repo = pr.target_repo.repo_name
1926 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1940 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1927
1941
1928 for stat in status_results:
1942 for stat in status_results:
1929 pr_id = pr_repo = None
1943 pr_id = pr_repo = None
1930 if stat.pull_request:
1944 if stat.pull_request:
1931 pr_id = stat.pull_request.pull_request_id
1945 pr_id = stat.pull_request.pull_request_id
1932 pr_repo = stat.pull_request.target_repo.repo_name
1946 pr_repo = stat.pull_request.target_repo.repo_name
1933 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1947 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1934 pr_id, pr_repo]
1948 pr_id, pr_repo]
1935 return grouped
1949 return grouped
1936
1950
1937 # ==========================================================================
1951 # ==========================================================================
1938 # SCM CACHE INSTANCE
1952 # SCM CACHE INSTANCE
1939 # ==========================================================================
1953 # ==========================================================================
1940
1954
1941 def scm_instance(self, **kwargs):
1955 def scm_instance(self, **kwargs):
1942 import rhodecode
1956 import rhodecode
1943
1957
1944 # Passing a config will not hit the cache currently only used
1958 # Passing a config will not hit the cache currently only used
1945 # for repo2dbmapper
1959 # for repo2dbmapper
1946 config = kwargs.pop('config', None)
1960 config = kwargs.pop('config', None)
1947 cache = kwargs.pop('cache', None)
1961 cache = kwargs.pop('cache', None)
1948 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1962 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1949 # if cache is NOT defined use default global, else we have a full
1963 # if cache is NOT defined use default global, else we have a full
1950 # control over cache behaviour
1964 # control over cache behaviour
1951 if cache is None and full_cache and not config:
1965 if cache is None and full_cache and not config:
1952 return self._get_instance_cached()
1966 return self._get_instance_cached()
1953 return self._get_instance(cache=bool(cache), config=config)
1967 return self._get_instance(cache=bool(cache), config=config)
1954
1968
1955 def _get_instance_cached(self):
1969 def _get_instance_cached(self):
1956 @cache_region('long_term')
1970 @cache_region('long_term')
1957 def _get_repo(cache_key):
1971 def _get_repo(cache_key):
1958 return self._get_instance()
1972 return self._get_instance()
1959
1973
1960 invalidator_context = CacheKey.repo_context_cache(
1974 invalidator_context = CacheKey.repo_context_cache(
1961 _get_repo, self.repo_name, None)
1975 _get_repo, self.repo_name, None)
1962
1976
1963 with invalidator_context as context:
1977 with invalidator_context as context:
1964 context.invalidate()
1978 context.invalidate()
1965 repo = context.compute()
1979 repo = context.compute()
1966
1980
1967 return repo
1981 return repo
1968
1982
1969 def _get_instance(self, cache=True, config=None):
1983 def _get_instance(self, cache=True, config=None):
1970 repo_full_path = self.repo_full_path
1984 repo_full_path = self.repo_full_path
1971 try:
1985 try:
1972 vcs_alias = get_scm(repo_full_path)[0]
1986 vcs_alias = get_scm(repo_full_path)[0]
1973 log.debug(
1987 log.debug(
1974 'Creating instance of %s repository from %s',
1988 'Creating instance of %s repository from %s',
1975 vcs_alias, repo_full_path)
1989 vcs_alias, repo_full_path)
1976 backend = get_backend(vcs_alias)
1990 backend = get_backend(vcs_alias)
1977 except VCSError:
1991 except VCSError:
1978 log.exception(
1992 log.exception(
1979 'Perhaps this repository is in db and not in '
1993 'Perhaps this repository is in db and not in '
1980 'filesystem run rescan repositories with '
1994 'filesystem run rescan repositories with '
1981 '"destroy old data" option from admin panel')
1995 '"destroy old data" option from admin panel')
1982 return
1996 return
1983
1997
1984 config = config or self._config
1998 config = config or self._config
1985 custom_wire = {
1999 custom_wire = {
1986 'cache': cache # controls the vcs.remote cache
2000 'cache': cache # controls the vcs.remote cache
1987 }
2001 }
1988 repo = backend(
2002 repo = backend(
1989 safe_str(repo_full_path), config=config, create=False,
2003 safe_str(repo_full_path), config=config, create=False,
1990 with_wire=custom_wire)
2004 with_wire=custom_wire)
1991
2005
1992 return repo
2006 return repo
1993
2007
1994 def __json__(self):
2008 def __json__(self):
1995 return {'landing_rev': self.landing_rev}
2009 return {'landing_rev': self.landing_rev}
1996
2010
1997 def get_dict(self):
2011 def get_dict(self):
1998
2012
1999 # Since we transformed `repo_name` to a hybrid property, we need to
2013 # Since we transformed `repo_name` to a hybrid property, we need to
2000 # keep compatibility with the code which uses `repo_name` field.
2014 # keep compatibility with the code which uses `repo_name` field.
2001
2015
2002 result = super(Repository, self).get_dict()
2016 result = super(Repository, self).get_dict()
2003 result['repo_name'] = result.pop('_repo_name', None)
2017 result['repo_name'] = result.pop('_repo_name', None)
2004 return result
2018 return result
2005
2019
2006
2020
2007 class RepoGroup(Base, BaseModel):
2021 class RepoGroup(Base, BaseModel):
2008 __tablename__ = 'groups'
2022 __tablename__ = 'groups'
2009 __table_args__ = (
2023 __table_args__ = (
2010 UniqueConstraint('group_name', 'group_parent_id'),
2024 UniqueConstraint('group_name', 'group_parent_id'),
2011 CheckConstraint('group_id != group_parent_id'),
2025 CheckConstraint('group_id != group_parent_id'),
2012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2026 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2013 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2027 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2014 )
2028 )
2015 __mapper_args__ = {'order_by': 'group_name'}
2029 __mapper_args__ = {'order_by': 'group_name'}
2016
2030
2017 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2031 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2018
2032
2019 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2033 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2020 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2034 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2021 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2035 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2022 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2036 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2023 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2037 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2024 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2038 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2025 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2039 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2026
2040
2027 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2041 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2028 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2042 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2029 parent_group = relationship('RepoGroup', remote_side=group_id)
2043 parent_group = relationship('RepoGroup', remote_side=group_id)
2030 user = relationship('User')
2044 user = relationship('User')
2031
2045
2032 def __init__(self, group_name='', parent_group=None):
2046 def __init__(self, group_name='', parent_group=None):
2033 self.group_name = group_name
2047 self.group_name = group_name
2034 self.parent_group = parent_group
2048 self.parent_group = parent_group
2035
2049
2036 def __unicode__(self):
2050 def __unicode__(self):
2037 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2051 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2038 self.group_name)
2052 self.group_name)
2039
2053
2040 @classmethod
2054 @classmethod
2041 def _generate_choice(cls, repo_group):
2055 def _generate_choice(cls, repo_group):
2042 from webhelpers.html import literal as _literal
2056 from webhelpers.html import literal as _literal
2043 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2057 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2044 return repo_group.group_id, _name(repo_group.full_path_splitted)
2058 return repo_group.group_id, _name(repo_group.full_path_splitted)
2045
2059
2046 @classmethod
2060 @classmethod
2047 def groups_choices(cls, groups=None, show_empty_group=True):
2061 def groups_choices(cls, groups=None, show_empty_group=True):
2048 if not groups:
2062 if not groups:
2049 groups = cls.query().all()
2063 groups = cls.query().all()
2050
2064
2051 repo_groups = []
2065 repo_groups = []
2052 if show_empty_group:
2066 if show_empty_group:
2053 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2067 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2054
2068
2055 repo_groups.extend([cls._generate_choice(x) for x in groups])
2069 repo_groups.extend([cls._generate_choice(x) for x in groups])
2056
2070
2057 repo_groups = sorted(
2071 repo_groups = sorted(
2058 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2072 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2059 return repo_groups
2073 return repo_groups
2060
2074
2061 @classmethod
2075 @classmethod
2062 def url_sep(cls):
2076 def url_sep(cls):
2063 return URL_SEP
2077 return URL_SEP
2064
2078
2065 @classmethod
2079 @classmethod
2066 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2080 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2067 if case_insensitive:
2081 if case_insensitive:
2068 gr = cls.query().filter(func.lower(cls.group_name)
2082 gr = cls.query().filter(func.lower(cls.group_name)
2069 == func.lower(group_name))
2083 == func.lower(group_name))
2070 else:
2084 else:
2071 gr = cls.query().filter(cls.group_name == group_name)
2085 gr = cls.query().filter(cls.group_name == group_name)
2072 if cache:
2086 if cache:
2073 gr = gr.options(FromCache(
2087 gr = gr.options(FromCache(
2074 "sql_cache_short",
2088 "sql_cache_short",
2075 "get_group_%s" % _hash_key(group_name)))
2089 "get_group_%s" % _hash_key(group_name)))
2076 return gr.scalar()
2090 return gr.scalar()
2077
2091
2078 @classmethod
2092 @classmethod
2079 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2093 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2080 case_insensitive=True):
2094 case_insensitive=True):
2081 q = RepoGroup.query()
2095 q = RepoGroup.query()
2082
2096
2083 if not isinstance(user_id, Optional):
2097 if not isinstance(user_id, Optional):
2084 q = q.filter(RepoGroup.user_id == user_id)
2098 q = q.filter(RepoGroup.user_id == user_id)
2085
2099
2086 if not isinstance(group_id, Optional):
2100 if not isinstance(group_id, Optional):
2087 q = q.filter(RepoGroup.group_parent_id == group_id)
2101 q = q.filter(RepoGroup.group_parent_id == group_id)
2088
2102
2089 if case_insensitive:
2103 if case_insensitive:
2090 q = q.order_by(func.lower(RepoGroup.group_name))
2104 q = q.order_by(func.lower(RepoGroup.group_name))
2091 else:
2105 else:
2092 q = q.order_by(RepoGroup.group_name)
2106 q = q.order_by(RepoGroup.group_name)
2093 return q.all()
2107 return q.all()
2094
2108
2095 @property
2109 @property
2096 def parents(self):
2110 def parents(self):
2097 parents_recursion_limit = 10
2111 parents_recursion_limit = 10
2098 groups = []
2112 groups = []
2099 if self.parent_group is None:
2113 if self.parent_group is None:
2100 return groups
2114 return groups
2101 cur_gr = self.parent_group
2115 cur_gr = self.parent_group
2102 groups.insert(0, cur_gr)
2116 groups.insert(0, cur_gr)
2103 cnt = 0
2117 cnt = 0
2104 while 1:
2118 while 1:
2105 cnt += 1
2119 cnt += 1
2106 gr = getattr(cur_gr, 'parent_group', None)
2120 gr = getattr(cur_gr, 'parent_group', None)
2107 cur_gr = cur_gr.parent_group
2121 cur_gr = cur_gr.parent_group
2108 if gr is None:
2122 if gr is None:
2109 break
2123 break
2110 if cnt == parents_recursion_limit:
2124 if cnt == parents_recursion_limit:
2111 # this will prevent accidental infinit loops
2125 # this will prevent accidental infinit loops
2112 log.error(('more than %s parents found for group %s, stopping '
2126 log.error(('more than %s parents found for group %s, stopping '
2113 'recursive parent fetching' % (parents_recursion_limit, self)))
2127 'recursive parent fetching' % (parents_recursion_limit, self)))
2114 break
2128 break
2115
2129
2116 groups.insert(0, gr)
2130 groups.insert(0, gr)
2117 return groups
2131 return groups
2118
2132
2119 @property
2133 @property
2120 def children(self):
2134 def children(self):
2121 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2135 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2122
2136
2123 @property
2137 @property
2124 def name(self):
2138 def name(self):
2125 return self.group_name.split(RepoGroup.url_sep())[-1]
2139 return self.group_name.split(RepoGroup.url_sep())[-1]
2126
2140
2127 @property
2141 @property
2128 def full_path(self):
2142 def full_path(self):
2129 return self.group_name
2143 return self.group_name
2130
2144
2131 @property
2145 @property
2132 def full_path_splitted(self):
2146 def full_path_splitted(self):
2133 return self.group_name.split(RepoGroup.url_sep())
2147 return self.group_name.split(RepoGroup.url_sep())
2134
2148
2135 @property
2149 @property
2136 def repositories(self):
2150 def repositories(self):
2137 return Repository.query()\
2151 return Repository.query()\
2138 .filter(Repository.group == self)\
2152 .filter(Repository.group == self)\
2139 .order_by(Repository.repo_name)
2153 .order_by(Repository.repo_name)
2140
2154
2141 @property
2155 @property
2142 def repositories_recursive_count(self):
2156 def repositories_recursive_count(self):
2143 cnt = self.repositories.count()
2157 cnt = self.repositories.count()
2144
2158
2145 def children_count(group):
2159 def children_count(group):
2146 cnt = 0
2160 cnt = 0
2147 for child in group.children:
2161 for child in group.children:
2148 cnt += child.repositories.count()
2162 cnt += child.repositories.count()
2149 cnt += children_count(child)
2163 cnt += children_count(child)
2150 return cnt
2164 return cnt
2151
2165
2152 return cnt + children_count(self)
2166 return cnt + children_count(self)
2153
2167
2154 def _recursive_objects(self, include_repos=True):
2168 def _recursive_objects(self, include_repos=True):
2155 all_ = []
2169 all_ = []
2156
2170
2157 def _get_members(root_gr):
2171 def _get_members(root_gr):
2158 if include_repos:
2172 if include_repos:
2159 for r in root_gr.repositories:
2173 for r in root_gr.repositories:
2160 all_.append(r)
2174 all_.append(r)
2161 childs = root_gr.children.all()
2175 childs = root_gr.children.all()
2162 if childs:
2176 if childs:
2163 for gr in childs:
2177 for gr in childs:
2164 all_.append(gr)
2178 all_.append(gr)
2165 _get_members(gr)
2179 _get_members(gr)
2166
2180
2167 _get_members(self)
2181 _get_members(self)
2168 return [self] + all_
2182 return [self] + all_
2169
2183
2170 def recursive_groups_and_repos(self):
2184 def recursive_groups_and_repos(self):
2171 """
2185 """
2172 Recursive return all groups, with repositories in those groups
2186 Recursive return all groups, with repositories in those groups
2173 """
2187 """
2174 return self._recursive_objects()
2188 return self._recursive_objects()
2175
2189
2176 def recursive_groups(self):
2190 def recursive_groups(self):
2177 """
2191 """
2178 Returns all children groups for this group including children of children
2192 Returns all children groups for this group including children of children
2179 """
2193 """
2180 return self._recursive_objects(include_repos=False)
2194 return self._recursive_objects(include_repos=False)
2181
2195
2182 def get_new_name(self, group_name):
2196 def get_new_name(self, group_name):
2183 """
2197 """
2184 returns new full group name based on parent and new name
2198 returns new full group name based on parent and new name
2185
2199
2186 :param group_name:
2200 :param group_name:
2187 """
2201 """
2188 path_prefix = (self.parent_group.full_path_splitted if
2202 path_prefix = (self.parent_group.full_path_splitted if
2189 self.parent_group else [])
2203 self.parent_group else [])
2190 return RepoGroup.url_sep().join(path_prefix + [group_name])
2204 return RepoGroup.url_sep().join(path_prefix + [group_name])
2191
2205
2192 def permissions(self, with_admins=True, with_owner=True):
2206 def permissions(self, with_admins=True, with_owner=True):
2193 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2207 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2194 q = q.options(joinedload(UserRepoGroupToPerm.group),
2208 q = q.options(joinedload(UserRepoGroupToPerm.group),
2195 joinedload(UserRepoGroupToPerm.user),
2209 joinedload(UserRepoGroupToPerm.user),
2196 joinedload(UserRepoGroupToPerm.permission),)
2210 joinedload(UserRepoGroupToPerm.permission),)
2197
2211
2198 # get owners and admins and permissions. We do a trick of re-writing
2212 # get owners and admins and permissions. We do a trick of re-writing
2199 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2213 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2200 # has a global reference and changing one object propagates to all
2214 # has a global reference and changing one object propagates to all
2201 # others. This means if admin is also an owner admin_row that change
2215 # others. This means if admin is also an owner admin_row that change
2202 # would propagate to both objects
2216 # would propagate to both objects
2203 perm_rows = []
2217 perm_rows = []
2204 for _usr in q.all():
2218 for _usr in q.all():
2205 usr = AttributeDict(_usr.user.get_dict())
2219 usr = AttributeDict(_usr.user.get_dict())
2206 usr.permission = _usr.permission.permission_name
2220 usr.permission = _usr.permission.permission_name
2207 perm_rows.append(usr)
2221 perm_rows.append(usr)
2208
2222
2209 # filter the perm rows by 'default' first and then sort them by
2223 # filter the perm rows by 'default' first and then sort them by
2210 # admin,write,read,none permissions sorted again alphabetically in
2224 # admin,write,read,none permissions sorted again alphabetically in
2211 # each group
2225 # each group
2212 perm_rows = sorted(perm_rows, key=display_sort)
2226 perm_rows = sorted(perm_rows, key=display_sort)
2213
2227
2214 _admin_perm = 'group.admin'
2228 _admin_perm = 'group.admin'
2215 owner_row = []
2229 owner_row = []
2216 if with_owner:
2230 if with_owner:
2217 usr = AttributeDict(self.user.get_dict())
2231 usr = AttributeDict(self.user.get_dict())
2218 usr.owner_row = True
2232 usr.owner_row = True
2219 usr.permission = _admin_perm
2233 usr.permission = _admin_perm
2220 owner_row.append(usr)
2234 owner_row.append(usr)
2221
2235
2222 super_admin_rows = []
2236 super_admin_rows = []
2223 if with_admins:
2237 if with_admins:
2224 for usr in User.get_all_super_admins():
2238 for usr in User.get_all_super_admins():
2225 # if this admin is also owner, don't double the record
2239 # if this admin is also owner, don't double the record
2226 if usr.user_id == owner_row[0].user_id:
2240 if usr.user_id == owner_row[0].user_id:
2227 owner_row[0].admin_row = True
2241 owner_row[0].admin_row = True
2228 else:
2242 else:
2229 usr = AttributeDict(usr.get_dict())
2243 usr = AttributeDict(usr.get_dict())
2230 usr.admin_row = True
2244 usr.admin_row = True
2231 usr.permission = _admin_perm
2245 usr.permission = _admin_perm
2232 super_admin_rows.append(usr)
2246 super_admin_rows.append(usr)
2233
2247
2234 return super_admin_rows + owner_row + perm_rows
2248 return super_admin_rows + owner_row + perm_rows
2235
2249
2236 def permission_user_groups(self):
2250 def permission_user_groups(self):
2237 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2251 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2238 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2252 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2239 joinedload(UserGroupRepoGroupToPerm.users_group),
2253 joinedload(UserGroupRepoGroupToPerm.users_group),
2240 joinedload(UserGroupRepoGroupToPerm.permission),)
2254 joinedload(UserGroupRepoGroupToPerm.permission),)
2241
2255
2242 perm_rows = []
2256 perm_rows = []
2243 for _user_group in q.all():
2257 for _user_group in q.all():
2244 usr = AttributeDict(_user_group.users_group.get_dict())
2258 usr = AttributeDict(_user_group.users_group.get_dict())
2245 usr.permission = _user_group.permission.permission_name
2259 usr.permission = _user_group.permission.permission_name
2246 perm_rows.append(usr)
2260 perm_rows.append(usr)
2247
2261
2248 return perm_rows
2262 return perm_rows
2249
2263
2250 def get_api_data(self):
2264 def get_api_data(self):
2251 """
2265 """
2252 Common function for generating api data
2266 Common function for generating api data
2253
2267
2254 """
2268 """
2255 group = self
2269 group = self
2256 data = {
2270 data = {
2257 'group_id': group.group_id,
2271 'group_id': group.group_id,
2258 'group_name': group.group_name,
2272 'group_name': group.group_name,
2259 'group_description': group.group_description,
2273 'group_description': group.group_description,
2260 'parent_group': group.parent_group.group_name if group.parent_group else None,
2274 'parent_group': group.parent_group.group_name if group.parent_group else None,
2261 'repositories': [x.repo_name for x in group.repositories],
2275 'repositories': [x.repo_name for x in group.repositories],
2262 'owner': group.user.username,
2276 'owner': group.user.username,
2263 }
2277 }
2264 return data
2278 return data
2265
2279
2266
2280
2267 class Permission(Base, BaseModel):
2281 class Permission(Base, BaseModel):
2268 __tablename__ = 'permissions'
2282 __tablename__ = 'permissions'
2269 __table_args__ = (
2283 __table_args__ = (
2270 Index('p_perm_name_idx', 'permission_name'),
2284 Index('p_perm_name_idx', 'permission_name'),
2271 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2285 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2272 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2286 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2273 )
2287 )
2274 PERMS = [
2288 PERMS = [
2275 ('hg.admin', _('RhodeCode Super Administrator')),
2289 ('hg.admin', _('RhodeCode Super Administrator')),
2276
2290
2277 ('repository.none', _('Repository no access')),
2291 ('repository.none', _('Repository no access')),
2278 ('repository.read', _('Repository read access')),
2292 ('repository.read', _('Repository read access')),
2279 ('repository.write', _('Repository write access')),
2293 ('repository.write', _('Repository write access')),
2280 ('repository.admin', _('Repository admin access')),
2294 ('repository.admin', _('Repository admin access')),
2281
2295
2282 ('group.none', _('Repository group no access')),
2296 ('group.none', _('Repository group no access')),
2283 ('group.read', _('Repository group read access')),
2297 ('group.read', _('Repository group read access')),
2284 ('group.write', _('Repository group write access')),
2298 ('group.write', _('Repository group write access')),
2285 ('group.admin', _('Repository group admin access')),
2299 ('group.admin', _('Repository group admin access')),
2286
2300
2287 ('usergroup.none', _('User group no access')),
2301 ('usergroup.none', _('User group no access')),
2288 ('usergroup.read', _('User group read access')),
2302 ('usergroup.read', _('User group read access')),
2289 ('usergroup.write', _('User group write access')),
2303 ('usergroup.write', _('User group write access')),
2290 ('usergroup.admin', _('User group admin access')),
2304 ('usergroup.admin', _('User group admin access')),
2291
2305
2292 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2306 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2293 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2307 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2294
2308
2295 ('hg.usergroup.create.false', _('User Group creation disabled')),
2309 ('hg.usergroup.create.false', _('User Group creation disabled')),
2296 ('hg.usergroup.create.true', _('User Group creation enabled')),
2310 ('hg.usergroup.create.true', _('User Group creation enabled')),
2297
2311
2298 ('hg.create.none', _('Repository creation disabled')),
2312 ('hg.create.none', _('Repository creation disabled')),
2299 ('hg.create.repository', _('Repository creation enabled')),
2313 ('hg.create.repository', _('Repository creation enabled')),
2300 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2314 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2301 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2315 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2302
2316
2303 ('hg.fork.none', _('Repository forking disabled')),
2317 ('hg.fork.none', _('Repository forking disabled')),
2304 ('hg.fork.repository', _('Repository forking enabled')),
2318 ('hg.fork.repository', _('Repository forking enabled')),
2305
2319
2306 ('hg.register.none', _('Registration disabled')),
2320 ('hg.register.none', _('Registration disabled')),
2307 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2321 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2308 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2322 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2309
2323
2310 ('hg.extern_activate.manual', _('Manual activation of external account')),
2324 ('hg.extern_activate.manual', _('Manual activation of external account')),
2311 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2325 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2312
2326
2313 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2327 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2314 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2328 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2315 ]
2329 ]
2316
2330
2317 # definition of system default permissions for DEFAULT user
2331 # definition of system default permissions for DEFAULT user
2318 DEFAULT_USER_PERMISSIONS = [
2332 DEFAULT_USER_PERMISSIONS = [
2319 'repository.read',
2333 'repository.read',
2320 'group.read',
2334 'group.read',
2321 'usergroup.read',
2335 'usergroup.read',
2322 'hg.create.repository',
2336 'hg.create.repository',
2323 'hg.repogroup.create.false',
2337 'hg.repogroup.create.false',
2324 'hg.usergroup.create.false',
2338 'hg.usergroup.create.false',
2325 'hg.create.write_on_repogroup.true',
2339 'hg.create.write_on_repogroup.true',
2326 'hg.fork.repository',
2340 'hg.fork.repository',
2327 'hg.register.manual_activate',
2341 'hg.register.manual_activate',
2328 'hg.extern_activate.auto',
2342 'hg.extern_activate.auto',
2329 'hg.inherit_default_perms.true',
2343 'hg.inherit_default_perms.true',
2330 ]
2344 ]
2331
2345
2332 # defines which permissions are more important higher the more important
2346 # defines which permissions are more important higher the more important
2333 # Weight defines which permissions are more important.
2347 # Weight defines which permissions are more important.
2334 # The higher number the more important.
2348 # The higher number the more important.
2335 PERM_WEIGHTS = {
2349 PERM_WEIGHTS = {
2336 'repository.none': 0,
2350 'repository.none': 0,
2337 'repository.read': 1,
2351 'repository.read': 1,
2338 'repository.write': 3,
2352 'repository.write': 3,
2339 'repository.admin': 4,
2353 'repository.admin': 4,
2340
2354
2341 'group.none': 0,
2355 'group.none': 0,
2342 'group.read': 1,
2356 'group.read': 1,
2343 'group.write': 3,
2357 'group.write': 3,
2344 'group.admin': 4,
2358 'group.admin': 4,
2345
2359
2346 'usergroup.none': 0,
2360 'usergroup.none': 0,
2347 'usergroup.read': 1,
2361 'usergroup.read': 1,
2348 'usergroup.write': 3,
2362 'usergroup.write': 3,
2349 'usergroup.admin': 4,
2363 'usergroup.admin': 4,
2350
2364
2351 'hg.repogroup.create.false': 0,
2365 'hg.repogroup.create.false': 0,
2352 'hg.repogroup.create.true': 1,
2366 'hg.repogroup.create.true': 1,
2353
2367
2354 'hg.usergroup.create.false': 0,
2368 'hg.usergroup.create.false': 0,
2355 'hg.usergroup.create.true': 1,
2369 'hg.usergroup.create.true': 1,
2356
2370
2357 'hg.fork.none': 0,
2371 'hg.fork.none': 0,
2358 'hg.fork.repository': 1,
2372 'hg.fork.repository': 1,
2359 'hg.create.none': 0,
2373 'hg.create.none': 0,
2360 'hg.create.repository': 1
2374 'hg.create.repository': 1
2361 }
2375 }
2362
2376
2363 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2377 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2364 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2378 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2365 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2379 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2366
2380
2367 def __unicode__(self):
2381 def __unicode__(self):
2368 return u"<%s('%s:%s')>" % (
2382 return u"<%s('%s:%s')>" % (
2369 self.__class__.__name__, self.permission_id, self.permission_name
2383 self.__class__.__name__, self.permission_id, self.permission_name
2370 )
2384 )
2371
2385
2372 @classmethod
2386 @classmethod
2373 def get_by_key(cls, key):
2387 def get_by_key(cls, key):
2374 return cls.query().filter(cls.permission_name == key).scalar()
2388 return cls.query().filter(cls.permission_name == key).scalar()
2375
2389
2376 @classmethod
2390 @classmethod
2377 def get_default_repo_perms(cls, user_id, repo_id=None):
2391 def get_default_repo_perms(cls, user_id, repo_id=None):
2378 q = Session().query(UserRepoToPerm, Repository, Permission)\
2392 q = Session().query(UserRepoToPerm, Repository, Permission)\
2379 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2393 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2380 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2394 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2381 .filter(UserRepoToPerm.user_id == user_id)
2395 .filter(UserRepoToPerm.user_id == user_id)
2382 if repo_id:
2396 if repo_id:
2383 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2397 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2384 return q.all()
2398 return q.all()
2385
2399
2386 @classmethod
2400 @classmethod
2387 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2401 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2388 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2402 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2389 .join(
2403 .join(
2390 Permission,
2404 Permission,
2391 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2405 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2392 .join(
2406 .join(
2393 Repository,
2407 Repository,
2394 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2408 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2395 .join(
2409 .join(
2396 UserGroup,
2410 UserGroup,
2397 UserGroupRepoToPerm.users_group_id ==
2411 UserGroupRepoToPerm.users_group_id ==
2398 UserGroup.users_group_id)\
2412 UserGroup.users_group_id)\
2399 .join(
2413 .join(
2400 UserGroupMember,
2414 UserGroupMember,
2401 UserGroupRepoToPerm.users_group_id ==
2415 UserGroupRepoToPerm.users_group_id ==
2402 UserGroupMember.users_group_id)\
2416 UserGroupMember.users_group_id)\
2403 .filter(
2417 .filter(
2404 UserGroupMember.user_id == user_id,
2418 UserGroupMember.user_id == user_id,
2405 UserGroup.users_group_active == true())
2419 UserGroup.users_group_active == true())
2406 if repo_id:
2420 if repo_id:
2407 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2421 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2408 return q.all()
2422 return q.all()
2409
2423
2410 @classmethod
2424 @classmethod
2411 def get_default_group_perms(cls, user_id, repo_group_id=None):
2425 def get_default_group_perms(cls, user_id, repo_group_id=None):
2412 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2426 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2413 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2427 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2414 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2428 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2415 .filter(UserRepoGroupToPerm.user_id == user_id)
2429 .filter(UserRepoGroupToPerm.user_id == user_id)
2416 if repo_group_id:
2430 if repo_group_id:
2417 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2431 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2418 return q.all()
2432 return q.all()
2419
2433
2420 @classmethod
2434 @classmethod
2421 def get_default_group_perms_from_user_group(
2435 def get_default_group_perms_from_user_group(
2422 cls, user_id, repo_group_id=None):
2436 cls, user_id, repo_group_id=None):
2423 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2437 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2424 .join(
2438 .join(
2425 Permission,
2439 Permission,
2426 UserGroupRepoGroupToPerm.permission_id ==
2440 UserGroupRepoGroupToPerm.permission_id ==
2427 Permission.permission_id)\
2441 Permission.permission_id)\
2428 .join(
2442 .join(
2429 RepoGroup,
2443 RepoGroup,
2430 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2444 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2431 .join(
2445 .join(
2432 UserGroup,
2446 UserGroup,
2433 UserGroupRepoGroupToPerm.users_group_id ==
2447 UserGroupRepoGroupToPerm.users_group_id ==
2434 UserGroup.users_group_id)\
2448 UserGroup.users_group_id)\
2435 .join(
2449 .join(
2436 UserGroupMember,
2450 UserGroupMember,
2437 UserGroupRepoGroupToPerm.users_group_id ==
2451 UserGroupRepoGroupToPerm.users_group_id ==
2438 UserGroupMember.users_group_id)\
2452 UserGroupMember.users_group_id)\
2439 .filter(
2453 .filter(
2440 UserGroupMember.user_id == user_id,
2454 UserGroupMember.user_id == user_id,
2441 UserGroup.users_group_active == true())
2455 UserGroup.users_group_active == true())
2442 if repo_group_id:
2456 if repo_group_id:
2443 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2457 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2444 return q.all()
2458 return q.all()
2445
2459
2446 @classmethod
2460 @classmethod
2447 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2461 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2448 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2462 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2449 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2463 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2450 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2464 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2451 .filter(UserUserGroupToPerm.user_id == user_id)
2465 .filter(UserUserGroupToPerm.user_id == user_id)
2452 if user_group_id:
2466 if user_group_id:
2453 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2467 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2454 return q.all()
2468 return q.all()
2455
2469
2456 @classmethod
2470 @classmethod
2457 def get_default_user_group_perms_from_user_group(
2471 def get_default_user_group_perms_from_user_group(
2458 cls, user_id, user_group_id=None):
2472 cls, user_id, user_group_id=None):
2459 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2473 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2460 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2474 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2461 .join(
2475 .join(
2462 Permission,
2476 Permission,
2463 UserGroupUserGroupToPerm.permission_id ==
2477 UserGroupUserGroupToPerm.permission_id ==
2464 Permission.permission_id)\
2478 Permission.permission_id)\
2465 .join(
2479 .join(
2466 TargetUserGroup,
2480 TargetUserGroup,
2467 UserGroupUserGroupToPerm.target_user_group_id ==
2481 UserGroupUserGroupToPerm.target_user_group_id ==
2468 TargetUserGroup.users_group_id)\
2482 TargetUserGroup.users_group_id)\
2469 .join(
2483 .join(
2470 UserGroup,
2484 UserGroup,
2471 UserGroupUserGroupToPerm.user_group_id ==
2485 UserGroupUserGroupToPerm.user_group_id ==
2472 UserGroup.users_group_id)\
2486 UserGroup.users_group_id)\
2473 .join(
2487 .join(
2474 UserGroupMember,
2488 UserGroupMember,
2475 UserGroupUserGroupToPerm.user_group_id ==
2489 UserGroupUserGroupToPerm.user_group_id ==
2476 UserGroupMember.users_group_id)\
2490 UserGroupMember.users_group_id)\
2477 .filter(
2491 .filter(
2478 UserGroupMember.user_id == user_id,
2492 UserGroupMember.user_id == user_id,
2479 UserGroup.users_group_active == true())
2493 UserGroup.users_group_active == true())
2480 if user_group_id:
2494 if user_group_id:
2481 q = q.filter(
2495 q = q.filter(
2482 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2496 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2483
2497
2484 return q.all()
2498 return q.all()
2485
2499
2486
2500
2487 class UserRepoToPerm(Base, BaseModel):
2501 class UserRepoToPerm(Base, BaseModel):
2488 __tablename__ = 'repo_to_perm'
2502 __tablename__ = 'repo_to_perm'
2489 __table_args__ = (
2503 __table_args__ = (
2490 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2504 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2491 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2492 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2493 )
2507 )
2494 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2508 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2495 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2496 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2510 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2497 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2511 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2498
2512
2499 user = relationship('User')
2513 user = relationship('User')
2500 repository = relationship('Repository')
2514 repository = relationship('Repository')
2501 permission = relationship('Permission')
2515 permission = relationship('Permission')
2502
2516
2503 @classmethod
2517 @classmethod
2504 def create(cls, user, repository, permission):
2518 def create(cls, user, repository, permission):
2505 n = cls()
2519 n = cls()
2506 n.user = user
2520 n.user = user
2507 n.repository = repository
2521 n.repository = repository
2508 n.permission = permission
2522 n.permission = permission
2509 Session().add(n)
2523 Session().add(n)
2510 return n
2524 return n
2511
2525
2512 def __unicode__(self):
2526 def __unicode__(self):
2513 return u'<%s => %s >' % (self.user, self.repository)
2527 return u'<%s => %s >' % (self.user, self.repository)
2514
2528
2515
2529
2516 class UserUserGroupToPerm(Base, BaseModel):
2530 class UserUserGroupToPerm(Base, BaseModel):
2517 __tablename__ = 'user_user_group_to_perm'
2531 __tablename__ = 'user_user_group_to_perm'
2518 __table_args__ = (
2532 __table_args__ = (
2519 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2533 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2521 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2522 )
2536 )
2523 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2537 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2524 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2538 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2525 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2539 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2526 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2540 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2527
2541
2528 user = relationship('User')
2542 user = relationship('User')
2529 user_group = relationship('UserGroup')
2543 user_group = relationship('UserGroup')
2530 permission = relationship('Permission')
2544 permission = relationship('Permission')
2531
2545
2532 @classmethod
2546 @classmethod
2533 def create(cls, user, user_group, permission):
2547 def create(cls, user, user_group, permission):
2534 n = cls()
2548 n = cls()
2535 n.user = user
2549 n.user = user
2536 n.user_group = user_group
2550 n.user_group = user_group
2537 n.permission = permission
2551 n.permission = permission
2538 Session().add(n)
2552 Session().add(n)
2539 return n
2553 return n
2540
2554
2541 def __unicode__(self):
2555 def __unicode__(self):
2542 return u'<%s => %s >' % (self.user, self.user_group)
2556 return u'<%s => %s >' % (self.user, self.user_group)
2543
2557
2544
2558
2545 class UserToPerm(Base, BaseModel):
2559 class UserToPerm(Base, BaseModel):
2546 __tablename__ = 'user_to_perm'
2560 __tablename__ = 'user_to_perm'
2547 __table_args__ = (
2561 __table_args__ = (
2548 UniqueConstraint('user_id', 'permission_id'),
2562 UniqueConstraint('user_id', 'permission_id'),
2549 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2563 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2550 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2564 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2551 )
2565 )
2552 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2566 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2553 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2554 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2568 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2555
2569
2556 user = relationship('User')
2570 user = relationship('User')
2557 permission = relationship('Permission', lazy='joined')
2571 permission = relationship('Permission', lazy='joined')
2558
2572
2559 def __unicode__(self):
2573 def __unicode__(self):
2560 return u'<%s => %s >' % (self.user, self.permission)
2574 return u'<%s => %s >' % (self.user, self.permission)
2561
2575
2562
2576
2563 class UserGroupRepoToPerm(Base, BaseModel):
2577 class UserGroupRepoToPerm(Base, BaseModel):
2564 __tablename__ = 'users_group_repo_to_perm'
2578 __tablename__ = 'users_group_repo_to_perm'
2565 __table_args__ = (
2579 __table_args__ = (
2566 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2580 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2568 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2582 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2569 )
2583 )
2570 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2584 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2571 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2572 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2586 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2573 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2587 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2574
2588
2575 users_group = relationship('UserGroup')
2589 users_group = relationship('UserGroup')
2576 permission = relationship('Permission')
2590 permission = relationship('Permission')
2577 repository = relationship('Repository')
2591 repository = relationship('Repository')
2578
2592
2579 @classmethod
2593 @classmethod
2580 def create(cls, users_group, repository, permission):
2594 def create(cls, users_group, repository, permission):
2581 n = cls()
2595 n = cls()
2582 n.users_group = users_group
2596 n.users_group = users_group
2583 n.repository = repository
2597 n.repository = repository
2584 n.permission = permission
2598 n.permission = permission
2585 Session().add(n)
2599 Session().add(n)
2586 return n
2600 return n
2587
2601
2588 def __unicode__(self):
2602 def __unicode__(self):
2589 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2603 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2590
2604
2591
2605
2592 class UserGroupUserGroupToPerm(Base, BaseModel):
2606 class UserGroupUserGroupToPerm(Base, BaseModel):
2593 __tablename__ = 'user_group_user_group_to_perm'
2607 __tablename__ = 'user_group_user_group_to_perm'
2594 __table_args__ = (
2608 __table_args__ = (
2595 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2609 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2596 CheckConstraint('target_user_group_id != user_group_id'),
2610 CheckConstraint('target_user_group_id != user_group_id'),
2597 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2598 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2612 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2599 )
2613 )
2600 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2614 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2601 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2615 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2602 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2616 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2603 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2617 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2604
2618
2605 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2619 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2606 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2620 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2607 permission = relationship('Permission')
2621 permission = relationship('Permission')
2608
2622
2609 @classmethod
2623 @classmethod
2610 def create(cls, target_user_group, user_group, permission):
2624 def create(cls, target_user_group, user_group, permission):
2611 n = cls()
2625 n = cls()
2612 n.target_user_group = target_user_group
2626 n.target_user_group = target_user_group
2613 n.user_group = user_group
2627 n.user_group = user_group
2614 n.permission = permission
2628 n.permission = permission
2615 Session().add(n)
2629 Session().add(n)
2616 return n
2630 return n
2617
2631
2618 def __unicode__(self):
2632 def __unicode__(self):
2619 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2633 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2620
2634
2621
2635
2622 class UserGroupToPerm(Base, BaseModel):
2636 class UserGroupToPerm(Base, BaseModel):
2623 __tablename__ = 'users_group_to_perm'
2637 __tablename__ = 'users_group_to_perm'
2624 __table_args__ = (
2638 __table_args__ = (
2625 UniqueConstraint('users_group_id', 'permission_id',),
2639 UniqueConstraint('users_group_id', 'permission_id',),
2626 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2627 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2628 )
2642 )
2629 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2643 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2630 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2631 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2632
2646
2633 users_group = relationship('UserGroup')
2647 users_group = relationship('UserGroup')
2634 permission = relationship('Permission')
2648 permission = relationship('Permission')
2635
2649
2636
2650
2637 class UserRepoGroupToPerm(Base, BaseModel):
2651 class UserRepoGroupToPerm(Base, BaseModel):
2638 __tablename__ = 'user_repo_group_to_perm'
2652 __tablename__ = 'user_repo_group_to_perm'
2639 __table_args__ = (
2653 __table_args__ = (
2640 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2654 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2656 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2643 )
2657 )
2644
2658
2645 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2659 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2646 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2660 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2647 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2661 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2648 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2662 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2649
2663
2650 user = relationship('User')
2664 user = relationship('User')
2651 group = relationship('RepoGroup')
2665 group = relationship('RepoGroup')
2652 permission = relationship('Permission')
2666 permission = relationship('Permission')
2653
2667
2654 @classmethod
2668 @classmethod
2655 def create(cls, user, repository_group, permission):
2669 def create(cls, user, repository_group, permission):
2656 n = cls()
2670 n = cls()
2657 n.user = user
2671 n.user = user
2658 n.group = repository_group
2672 n.group = repository_group
2659 n.permission = permission
2673 n.permission = permission
2660 Session().add(n)
2674 Session().add(n)
2661 return n
2675 return n
2662
2676
2663
2677
2664 class UserGroupRepoGroupToPerm(Base, BaseModel):
2678 class UserGroupRepoGroupToPerm(Base, BaseModel):
2665 __tablename__ = 'users_group_repo_group_to_perm'
2679 __tablename__ = 'users_group_repo_group_to_perm'
2666 __table_args__ = (
2680 __table_args__ = (
2667 UniqueConstraint('users_group_id', 'group_id'),
2681 UniqueConstraint('users_group_id', 'group_id'),
2668 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2682 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2669 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2683 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2670 )
2684 )
2671
2685
2672 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2686 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2673 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2687 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2674 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2688 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2675 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2689 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2676
2690
2677 users_group = relationship('UserGroup')
2691 users_group = relationship('UserGroup')
2678 permission = relationship('Permission')
2692 permission = relationship('Permission')
2679 group = relationship('RepoGroup')
2693 group = relationship('RepoGroup')
2680
2694
2681 @classmethod
2695 @classmethod
2682 def create(cls, user_group, repository_group, permission):
2696 def create(cls, user_group, repository_group, permission):
2683 n = cls()
2697 n = cls()
2684 n.users_group = user_group
2698 n.users_group = user_group
2685 n.group = repository_group
2699 n.group = repository_group
2686 n.permission = permission
2700 n.permission = permission
2687 Session().add(n)
2701 Session().add(n)
2688 return n
2702 return n
2689
2703
2690 def __unicode__(self):
2704 def __unicode__(self):
2691 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2705 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2692
2706
2693
2707
2694 class Statistics(Base, BaseModel):
2708 class Statistics(Base, BaseModel):
2695 __tablename__ = 'statistics'
2709 __tablename__ = 'statistics'
2696 __table_args__ = (
2710 __table_args__ = (
2697 UniqueConstraint('repository_id'),
2711 UniqueConstraint('repository_id'),
2698 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2699 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2713 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2700 )
2714 )
2701 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2715 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2702 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2716 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2703 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2717 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2704 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2718 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2705 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2719 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2706 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2720 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2707
2721
2708 repository = relationship('Repository', single_parent=True)
2722 repository = relationship('Repository', single_parent=True)
2709
2723
2710
2724
2711 class UserFollowing(Base, BaseModel):
2725 class UserFollowing(Base, BaseModel):
2712 __tablename__ = 'user_followings'
2726 __tablename__ = 'user_followings'
2713 __table_args__ = (
2727 __table_args__ = (
2714 UniqueConstraint('user_id', 'follows_repository_id'),
2728 UniqueConstraint('user_id', 'follows_repository_id'),
2715 UniqueConstraint('user_id', 'follows_user_id'),
2729 UniqueConstraint('user_id', 'follows_user_id'),
2716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2731 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2718 )
2732 )
2719
2733
2720 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2734 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2721 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2735 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2722 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2736 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2723 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2737 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2724 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2738 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2725
2739
2726 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2740 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2727
2741
2728 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2742 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2729 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2743 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2730
2744
2731 @classmethod
2745 @classmethod
2732 def get_repo_followers(cls, repo_id):
2746 def get_repo_followers(cls, repo_id):
2733 return cls.query().filter(cls.follows_repo_id == repo_id)
2747 return cls.query().filter(cls.follows_repo_id == repo_id)
2734
2748
2735
2749
2736 class CacheKey(Base, BaseModel):
2750 class CacheKey(Base, BaseModel):
2737 __tablename__ = 'cache_invalidation'
2751 __tablename__ = 'cache_invalidation'
2738 __table_args__ = (
2752 __table_args__ = (
2739 UniqueConstraint('cache_key'),
2753 UniqueConstraint('cache_key'),
2740 Index('key_idx', 'cache_key'),
2754 Index('key_idx', 'cache_key'),
2741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2742 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2756 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2743 )
2757 )
2744 CACHE_TYPE_ATOM = 'ATOM'
2758 CACHE_TYPE_ATOM = 'ATOM'
2745 CACHE_TYPE_RSS = 'RSS'
2759 CACHE_TYPE_RSS = 'RSS'
2746 CACHE_TYPE_README = 'README'
2760 CACHE_TYPE_README = 'README'
2747
2761
2748 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2762 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2749 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2763 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2750 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2764 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2751 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2752
2766
2753 def __init__(self, cache_key, cache_args=''):
2767 def __init__(self, cache_key, cache_args=''):
2754 self.cache_key = cache_key
2768 self.cache_key = cache_key
2755 self.cache_args = cache_args
2769 self.cache_args = cache_args
2756 self.cache_active = False
2770 self.cache_active = False
2757
2771
2758 def __unicode__(self):
2772 def __unicode__(self):
2759 return u"<%s('%s:%s[%s]')>" % (
2773 return u"<%s('%s:%s[%s]')>" % (
2760 self.__class__.__name__,
2774 self.__class__.__name__,
2761 self.cache_id, self.cache_key, self.cache_active)
2775 self.cache_id, self.cache_key, self.cache_active)
2762
2776
2763 def _cache_key_partition(self):
2777 def _cache_key_partition(self):
2764 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2778 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2765 return prefix, repo_name, suffix
2779 return prefix, repo_name, suffix
2766
2780
2767 def get_prefix(self):
2781 def get_prefix(self):
2768 """
2782 """
2769 Try to extract prefix from existing cache key. The key could consist
2783 Try to extract prefix from existing cache key. The key could consist
2770 of prefix, repo_name, suffix
2784 of prefix, repo_name, suffix
2771 """
2785 """
2772 # this returns prefix, repo_name, suffix
2786 # this returns prefix, repo_name, suffix
2773 return self._cache_key_partition()[0]
2787 return self._cache_key_partition()[0]
2774
2788
2775 def get_suffix(self):
2789 def get_suffix(self):
2776 """
2790 """
2777 get suffix that might have been used in _get_cache_key to
2791 get suffix that might have been used in _get_cache_key to
2778 generate self.cache_key. Only used for informational purposes
2792 generate self.cache_key. Only used for informational purposes
2779 in repo_edit.html.
2793 in repo_edit.html.
2780 """
2794 """
2781 # prefix, repo_name, suffix
2795 # prefix, repo_name, suffix
2782 return self._cache_key_partition()[2]
2796 return self._cache_key_partition()[2]
2783
2797
2784 @classmethod
2798 @classmethod
2785 def delete_all_cache(cls):
2799 def delete_all_cache(cls):
2786 """
2800 """
2787 Delete all cache keys from database.
2801 Delete all cache keys from database.
2788 Should only be run when all instances are down and all entries
2802 Should only be run when all instances are down and all entries
2789 thus stale.
2803 thus stale.
2790 """
2804 """
2791 cls.query().delete()
2805 cls.query().delete()
2792 Session().commit()
2806 Session().commit()
2793
2807
2794 @classmethod
2808 @classmethod
2795 def get_cache_key(cls, repo_name, cache_type):
2809 def get_cache_key(cls, repo_name, cache_type):
2796 """
2810 """
2797
2811
2798 Generate a cache key for this process of RhodeCode instance.
2812 Generate a cache key for this process of RhodeCode instance.
2799 Prefix most likely will be process id or maybe explicitly set
2813 Prefix most likely will be process id or maybe explicitly set
2800 instance_id from .ini file.
2814 instance_id from .ini file.
2801 """
2815 """
2802 import rhodecode
2816 import rhodecode
2803 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2817 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2804
2818
2805 repo_as_unicode = safe_unicode(repo_name)
2819 repo_as_unicode = safe_unicode(repo_name)
2806 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2820 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2807 if cache_type else repo_as_unicode
2821 if cache_type else repo_as_unicode
2808
2822
2809 return u'{}{}'.format(prefix, key)
2823 return u'{}{}'.format(prefix, key)
2810
2824
2811 @classmethod
2825 @classmethod
2812 def set_invalidate(cls, repo_name, delete=False):
2826 def set_invalidate(cls, repo_name, delete=False):
2813 """
2827 """
2814 Mark all caches of a repo as invalid in the database.
2828 Mark all caches of a repo as invalid in the database.
2815 """
2829 """
2816
2830
2817 try:
2831 try:
2818 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2832 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2819 if delete:
2833 if delete:
2820 log.debug('cache objects deleted for repo %s',
2834 log.debug('cache objects deleted for repo %s',
2821 safe_str(repo_name))
2835 safe_str(repo_name))
2822 qry.delete()
2836 qry.delete()
2823 else:
2837 else:
2824 log.debug('cache objects marked as invalid for repo %s',
2838 log.debug('cache objects marked as invalid for repo %s',
2825 safe_str(repo_name))
2839 safe_str(repo_name))
2826 qry.update({"cache_active": False})
2840 qry.update({"cache_active": False})
2827
2841
2828 Session().commit()
2842 Session().commit()
2829 except Exception:
2843 except Exception:
2830 log.exception(
2844 log.exception(
2831 'Cache key invalidation failed for repository %s',
2845 'Cache key invalidation failed for repository %s',
2832 safe_str(repo_name))
2846 safe_str(repo_name))
2833 Session().rollback()
2847 Session().rollback()
2834
2848
2835 @classmethod
2849 @classmethod
2836 def get_active_cache(cls, cache_key):
2850 def get_active_cache(cls, cache_key):
2837 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2851 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2838 if inv_obj:
2852 if inv_obj:
2839 return inv_obj
2853 return inv_obj
2840 return None
2854 return None
2841
2855
2842 @classmethod
2856 @classmethod
2843 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2857 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2844 """
2858 """
2845 @cache_region('long_term')
2859 @cache_region('long_term')
2846 def _heavy_calculation(cache_key):
2860 def _heavy_calculation(cache_key):
2847 return 'result'
2861 return 'result'
2848
2862
2849 cache_context = CacheKey.repo_context_cache(
2863 cache_context = CacheKey.repo_context_cache(
2850 _heavy_calculation, repo_name, cache_type)
2864 _heavy_calculation, repo_name, cache_type)
2851
2865
2852 with cache_context as context:
2866 with cache_context as context:
2853 context.invalidate()
2867 context.invalidate()
2854 computed = context.compute()
2868 computed = context.compute()
2855
2869
2856 assert computed == 'result'
2870 assert computed == 'result'
2857 """
2871 """
2858 from rhodecode.lib import caches
2872 from rhodecode.lib import caches
2859 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2873 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2860
2874
2861
2875
2862 class ChangesetComment(Base, BaseModel):
2876 class ChangesetComment(Base, BaseModel):
2863 __tablename__ = 'changeset_comments'
2877 __tablename__ = 'changeset_comments'
2864 __table_args__ = (
2878 __table_args__ = (
2865 Index('cc_revision_idx', 'revision'),
2879 Index('cc_revision_idx', 'revision'),
2866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2867 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2868 )
2882 )
2869
2883
2870 COMMENT_OUTDATED = u'comment_outdated'
2884 COMMENT_OUTDATED = u'comment_outdated'
2871
2885
2872 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2873 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2874 revision = Column('revision', String(40), nullable=True)
2888 revision = Column('revision', String(40), nullable=True)
2875 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2876 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2877 line_no = Column('line_no', Unicode(10), nullable=True)
2891 line_no = Column('line_no', Unicode(10), nullable=True)
2878 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2879 f_path = Column('f_path', Unicode(1000), nullable=True)
2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2880 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2881 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2882 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2883 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2884 renderer = Column('renderer', Unicode(64), nullable=True)
2898 renderer = Column('renderer', Unicode(64), nullable=True)
2885 display_state = Column('display_state', Unicode(128), nullable=True)
2899 display_state = Column('display_state', Unicode(128), nullable=True)
2886
2900
2887 author = relationship('User', lazy='joined')
2901 author = relationship('User', lazy='joined')
2888 repo = relationship('Repository')
2902 repo = relationship('Repository')
2889 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2890 pull_request = relationship('PullRequest', lazy='joined')
2904 pull_request = relationship('PullRequest', lazy='joined')
2891 pull_request_version = relationship('PullRequestVersion')
2905 pull_request_version = relationship('PullRequestVersion')
2892
2906
2893 @classmethod
2907 @classmethod
2894 def get_users(cls, revision=None, pull_request_id=None):
2908 def get_users(cls, revision=None, pull_request_id=None):
2895 """
2909 """
2896 Returns user associated with this ChangesetComment. ie those
2910 Returns user associated with this ChangesetComment. ie those
2897 who actually commented
2911 who actually commented
2898
2912
2899 :param cls:
2913 :param cls:
2900 :param revision:
2914 :param revision:
2901 """
2915 """
2902 q = Session().query(User)\
2916 q = Session().query(User)\
2903 .join(ChangesetComment.author)
2917 .join(ChangesetComment.author)
2904 if revision:
2918 if revision:
2905 q = q.filter(cls.revision == revision)
2919 q = q.filter(cls.revision == revision)
2906 elif pull_request_id:
2920 elif pull_request_id:
2907 q = q.filter(cls.pull_request_id == pull_request_id)
2921 q = q.filter(cls.pull_request_id == pull_request_id)
2908 return q.all()
2922 return q.all()
2909
2923
2910 def render(self, mentions=False):
2924 def render(self, mentions=False):
2911 from rhodecode.lib import helpers as h
2925 from rhodecode.lib import helpers as h
2912 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2913
2927
2914 def __repr__(self):
2928 def __repr__(self):
2915 if self.comment_id:
2929 if self.comment_id:
2916 return '<DB:ChangesetComment #%s>' % self.comment_id
2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2917 else:
2931 else:
2918 return '<DB:ChangesetComment at %#x>' % id(self)
2932 return '<DB:ChangesetComment at %#x>' % id(self)
2919
2933
2920
2934
2921 class ChangesetStatus(Base, BaseModel):
2935 class ChangesetStatus(Base, BaseModel):
2922 __tablename__ = 'changeset_statuses'
2936 __tablename__ = 'changeset_statuses'
2923 __table_args__ = (
2937 __table_args__ = (
2924 Index('cs_revision_idx', 'revision'),
2938 Index('cs_revision_idx', 'revision'),
2925 Index('cs_version_idx', 'version'),
2939 Index('cs_version_idx', 'version'),
2926 UniqueConstraint('repo_id', 'revision', 'version'),
2940 UniqueConstraint('repo_id', 'revision', 'version'),
2927 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2928 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2929 )
2943 )
2930 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2931 STATUS_APPROVED = 'approved'
2945 STATUS_APPROVED = 'approved'
2932 STATUS_REJECTED = 'rejected'
2946 STATUS_REJECTED = 'rejected'
2933 STATUS_UNDER_REVIEW = 'under_review'
2947 STATUS_UNDER_REVIEW = 'under_review'
2934
2948
2935 STATUSES = [
2949 STATUSES = [
2936 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2937 (STATUS_APPROVED, _("Approved")),
2951 (STATUS_APPROVED, _("Approved")),
2938 (STATUS_REJECTED, _("Rejected")),
2952 (STATUS_REJECTED, _("Rejected")),
2939 (STATUS_UNDER_REVIEW, _("Under Review")),
2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2940 ]
2954 ]
2941
2955
2942 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2943 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2944 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2945 revision = Column('revision', String(40), nullable=False)
2959 revision = Column('revision', String(40), nullable=False)
2946 status = Column('status', String(128), nullable=False, default=DEFAULT)
2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2947 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2948 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2949 version = Column('version', Integer(), nullable=False, default=0)
2963 version = Column('version', Integer(), nullable=False, default=0)
2950 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2951
2965
2952 author = relationship('User', lazy='joined')
2966 author = relationship('User', lazy='joined')
2953 repo = relationship('Repository')
2967 repo = relationship('Repository')
2954 comment = relationship('ChangesetComment', lazy='joined')
2968 comment = relationship('ChangesetComment', lazy='joined')
2955 pull_request = relationship('PullRequest', lazy='joined')
2969 pull_request = relationship('PullRequest', lazy='joined')
2956
2970
2957 def __unicode__(self):
2971 def __unicode__(self):
2958 return u"<%s('%s[%s]:%s')>" % (
2972 return u"<%s('%s[%s]:%s')>" % (
2959 self.__class__.__name__,
2973 self.__class__.__name__,
2960 self.status, self.version, self.author
2974 self.status, self.version, self.author
2961 )
2975 )
2962
2976
2963 @classmethod
2977 @classmethod
2964 def get_status_lbl(cls, value):
2978 def get_status_lbl(cls, value):
2965 return dict(cls.STATUSES).get(value)
2979 return dict(cls.STATUSES).get(value)
2966
2980
2967 @property
2981 @property
2968 def status_lbl(self):
2982 def status_lbl(self):
2969 return ChangesetStatus.get_status_lbl(self.status)
2983 return ChangesetStatus.get_status_lbl(self.status)
2970
2984
2971
2985
2972 class _PullRequestBase(BaseModel):
2986 class _PullRequestBase(BaseModel):
2973 """
2987 """
2974 Common attributes of pull request and version entries.
2988 Common attributes of pull request and version entries.
2975 """
2989 """
2976
2990
2977 # .status values
2991 # .status values
2978 STATUS_NEW = u'new'
2992 STATUS_NEW = u'new'
2979 STATUS_OPEN = u'open'
2993 STATUS_OPEN = u'open'
2980 STATUS_CLOSED = u'closed'
2994 STATUS_CLOSED = u'closed'
2981
2995
2982 title = Column('title', Unicode(255), nullable=True)
2996 title = Column('title', Unicode(255), nullable=True)
2983 description = Column(
2997 description = Column(
2984 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2985 nullable=True)
2999 nullable=True)
2986 # new/open/closed status of pull request (not approve/reject/etc)
3000 # new/open/closed status of pull request (not approve/reject/etc)
2987 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2988 created_on = Column(
3002 created_on = Column(
2989 'created_on', DateTime(timezone=False), nullable=False,
3003 'created_on', DateTime(timezone=False), nullable=False,
2990 default=datetime.datetime.now)
3004 default=datetime.datetime.now)
2991 updated_on = Column(
3005 updated_on = Column(
2992 'updated_on', DateTime(timezone=False), nullable=False,
3006 'updated_on', DateTime(timezone=False), nullable=False,
2993 default=datetime.datetime.now)
3007 default=datetime.datetime.now)
2994
3008
2995 @declared_attr
3009 @declared_attr
2996 def user_id(cls):
3010 def user_id(cls):
2997 return Column(
3011 return Column(
2998 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
2999 unique=None)
3013 unique=None)
3000
3014
3001 # 500 revisions max
3015 # 500 revisions max
3002 _revisions = Column(
3016 _revisions = Column(
3003 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3004
3018
3005 @declared_attr
3019 @declared_attr
3006 def source_repo_id(cls):
3020 def source_repo_id(cls):
3007 # TODO: dan: rename column to source_repo_id
3021 # TODO: dan: rename column to source_repo_id
3008 return Column(
3022 return Column(
3009 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3010 nullable=False)
3024 nullable=False)
3011
3025
3012 source_ref = Column('org_ref', Unicode(255), nullable=False)
3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3013
3027
3014 @declared_attr
3028 @declared_attr
3015 def target_repo_id(cls):
3029 def target_repo_id(cls):
3016 # TODO: dan: rename column to target_repo_id
3030 # TODO: dan: rename column to target_repo_id
3017 return Column(
3031 return Column(
3018 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3019 nullable=False)
3033 nullable=False)
3020
3034
3021 target_ref = Column('other_ref', Unicode(255), nullable=False)
3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3022
3036
3023 # TODO: dan: rename column to last_merge_source_rev
3037 # TODO: dan: rename column to last_merge_source_rev
3024 _last_merge_source_rev = Column(
3038 _last_merge_source_rev = Column(
3025 'last_merge_org_rev', String(40), nullable=True)
3039 'last_merge_org_rev', String(40), nullable=True)
3026 # TODO: dan: rename column to last_merge_target_rev
3040 # TODO: dan: rename column to last_merge_target_rev
3027 _last_merge_target_rev = Column(
3041 _last_merge_target_rev = Column(
3028 'last_merge_other_rev', String(40), nullable=True)
3042 'last_merge_other_rev', String(40), nullable=True)
3029 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3030 merge_rev = Column('merge_rev', String(40), nullable=True)
3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3031
3045
3032 @hybrid_property
3046 @hybrid_property
3033 def revisions(self):
3047 def revisions(self):
3034 return self._revisions.split(':') if self._revisions else []
3048 return self._revisions.split(':') if self._revisions else []
3035
3049
3036 @revisions.setter
3050 @revisions.setter
3037 def revisions(self, val):
3051 def revisions(self, val):
3038 self._revisions = ':'.join(val)
3052 self._revisions = ':'.join(val)
3039
3053
3040 @declared_attr
3054 @declared_attr
3041 def author(cls):
3055 def author(cls):
3042 return relationship('User', lazy='joined')
3056 return relationship('User', lazy='joined')
3043
3057
3044 @declared_attr
3058 @declared_attr
3045 def source_repo(cls):
3059 def source_repo(cls):
3046 return relationship(
3060 return relationship(
3047 'Repository',
3061 'Repository',
3048 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3049
3063
3050 @property
3064 @property
3051 def source_ref_parts(self):
3065 def source_ref_parts(self):
3052 refs = self.source_ref.split(':')
3066 refs = self.source_ref.split(':')
3053 return Reference(refs[0], refs[1], refs[2])
3067 return Reference(refs[0], refs[1], refs[2])
3054
3068
3055 @declared_attr
3069 @declared_attr
3056 def target_repo(cls):
3070 def target_repo(cls):
3057 return relationship(
3071 return relationship(
3058 'Repository',
3072 'Repository',
3059 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3060
3074
3061 @property
3075 @property
3062 def target_ref_parts(self):
3076 def target_ref_parts(self):
3063 refs = self.target_ref.split(':')
3077 refs = self.target_ref.split(':')
3064 return Reference(refs[0], refs[1], refs[2])
3078 return Reference(refs[0], refs[1], refs[2])
3065
3079
3066
3080
3067 class PullRequest(Base, _PullRequestBase):
3081 class PullRequest(Base, _PullRequestBase):
3068 __tablename__ = 'pull_requests'
3082 __tablename__ = 'pull_requests'
3069 __table_args__ = (
3083 __table_args__ = (
3070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3072 )
3086 )
3073
3087
3074 pull_request_id = Column(
3088 pull_request_id = Column(
3075 'pull_request_id', Integer(), nullable=False, primary_key=True)
3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3076
3090
3077 def __repr__(self):
3091 def __repr__(self):
3078 if self.pull_request_id:
3092 if self.pull_request_id:
3079 return '<DB:PullRequest #%s>' % self.pull_request_id
3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3080 else:
3094 else:
3081 return '<DB:PullRequest at %#x>' % id(self)
3095 return '<DB:PullRequest at %#x>' % id(self)
3082
3096
3083 reviewers = relationship('PullRequestReviewers',
3097 reviewers = relationship('PullRequestReviewers',
3084 cascade="all, delete, delete-orphan")
3098 cascade="all, delete, delete-orphan")
3085 statuses = relationship('ChangesetStatus')
3099 statuses = relationship('ChangesetStatus')
3086 comments = relationship('ChangesetComment',
3100 comments = relationship('ChangesetComment',
3087 cascade="all, delete, delete-orphan")
3101 cascade="all, delete, delete-orphan")
3088 versions = relationship('PullRequestVersion',
3102 versions = relationship('PullRequestVersion',
3089 cascade="all, delete, delete-orphan")
3103 cascade="all, delete, delete-orphan")
3090
3104
3091 def is_closed(self):
3105 def is_closed(self):
3092 return self.status == self.STATUS_CLOSED
3106 return self.status == self.STATUS_CLOSED
3093
3107
3094 def get_api_data(self):
3108 def get_api_data(self):
3095 from rhodecode.model.pull_request import PullRequestModel
3109 from rhodecode.model.pull_request import PullRequestModel
3096 pull_request = self
3110 pull_request = self
3097 merge_status = PullRequestModel().merge_status(pull_request)
3111 merge_status = PullRequestModel().merge_status(pull_request)
3098 data = {
3112 data = {
3099 'pull_request_id': pull_request.pull_request_id,
3113 'pull_request_id': pull_request.pull_request_id,
3100 'url': url('pullrequest_show',
3114 'url': url('pullrequest_show',
3101 repo_name=pull_request.target_repo.repo_name,
3115 repo_name=pull_request.target_repo.repo_name,
3102 pull_request_id=pull_request.pull_request_id,
3116 pull_request_id=pull_request.pull_request_id,
3103 qualified=True),
3117 qualified=True),
3104 'title': pull_request.title,
3118 'title': pull_request.title,
3105 'description': pull_request.description,
3119 'description': pull_request.description,
3106 'status': pull_request.status,
3120 'status': pull_request.status,
3107 'created_on': pull_request.created_on,
3121 'created_on': pull_request.created_on,
3108 'updated_on': pull_request.updated_on,
3122 'updated_on': pull_request.updated_on,
3109 'commit_ids': pull_request.revisions,
3123 'commit_ids': pull_request.revisions,
3110 'review_status': pull_request.calculated_review_status(),
3124 'review_status': pull_request.calculated_review_status(),
3111 'mergeable': {
3125 'mergeable': {
3112 'status': merge_status[0],
3126 'status': merge_status[0],
3113 'message': unicode(merge_status[1]),
3127 'message': unicode(merge_status[1]),
3114 },
3128 },
3115 'source': {
3129 'source': {
3116 'clone_url': pull_request.source_repo.clone_url(),
3130 'clone_url': pull_request.source_repo.clone_url(),
3117 'repository': pull_request.source_repo.repo_name,
3131 'repository': pull_request.source_repo.repo_name,
3118 'reference': {
3132 'reference': {
3119 'name': pull_request.source_ref_parts.name,
3133 'name': pull_request.source_ref_parts.name,
3120 'type': pull_request.source_ref_parts.type,
3134 'type': pull_request.source_ref_parts.type,
3121 'commit_id': pull_request.source_ref_parts.commit_id,
3135 'commit_id': pull_request.source_ref_parts.commit_id,
3122 },
3136 },
3123 },
3137 },
3124 'target': {
3138 'target': {
3125 'clone_url': pull_request.target_repo.clone_url(),
3139 'clone_url': pull_request.target_repo.clone_url(),
3126 'repository': pull_request.target_repo.repo_name,
3140 'repository': pull_request.target_repo.repo_name,
3127 'reference': {
3141 'reference': {
3128 'name': pull_request.target_ref_parts.name,
3142 'name': pull_request.target_ref_parts.name,
3129 'type': pull_request.target_ref_parts.type,
3143 'type': pull_request.target_ref_parts.type,
3130 'commit_id': pull_request.target_ref_parts.commit_id,
3144 'commit_id': pull_request.target_ref_parts.commit_id,
3131 },
3145 },
3132 },
3146 },
3133 'author': pull_request.author.get_api_data(include_secrets=False,
3147 'author': pull_request.author.get_api_data(include_secrets=False,
3134 details='basic'),
3148 details='basic'),
3135 'reviewers': [
3149 'reviewers': [
3136 {
3150 {
3137 'user': reviewer.get_api_data(include_secrets=False,
3151 'user': reviewer.get_api_data(include_secrets=False,
3138 details='basic'),
3152 details='basic'),
3139 'review_status': st[0][1].status if st else 'not_reviewed',
3153 'review_status': st[0][1].status if st else 'not_reviewed',
3140 }
3154 }
3141 for reviewer, st in pull_request.reviewers_statuses()
3155 for reviewer, st in pull_request.reviewers_statuses()
3142 ]
3156 ]
3143 }
3157 }
3144
3158
3145 return data
3159 return data
3146
3160
3147 def __json__(self):
3161 def __json__(self):
3148 return {
3162 return {
3149 'revisions': self.revisions,
3163 'revisions': self.revisions,
3150 }
3164 }
3151
3165
3152 def calculated_review_status(self):
3166 def calculated_review_status(self):
3153 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3167 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3154 # because it's tricky on how to use ChangesetStatusModel from there
3168 # because it's tricky on how to use ChangesetStatusModel from there
3155 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3169 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3156 from rhodecode.model.changeset_status import ChangesetStatusModel
3170 from rhodecode.model.changeset_status import ChangesetStatusModel
3157 return ChangesetStatusModel().calculated_review_status(self)
3171 return ChangesetStatusModel().calculated_review_status(self)
3158
3172
3159 def reviewers_statuses(self):
3173 def reviewers_statuses(self):
3160 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3174 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3161 from rhodecode.model.changeset_status import ChangesetStatusModel
3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3162 return ChangesetStatusModel().reviewers_statuses(self)
3176 return ChangesetStatusModel().reviewers_statuses(self)
3163
3177
3164
3178
3165 class PullRequestVersion(Base, _PullRequestBase):
3179 class PullRequestVersion(Base, _PullRequestBase):
3166 __tablename__ = 'pull_request_versions'
3180 __tablename__ = 'pull_request_versions'
3167 __table_args__ = (
3181 __table_args__ = (
3168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3169 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3170 )
3184 )
3171
3185
3172 pull_request_version_id = Column(
3186 pull_request_version_id = Column(
3173 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3187 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3174 pull_request_id = Column(
3188 pull_request_id = Column(
3175 'pull_request_id', Integer(),
3189 'pull_request_id', Integer(),
3176 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3190 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3177 pull_request = relationship('PullRequest')
3191 pull_request = relationship('PullRequest')
3178
3192
3179 def __repr__(self):
3193 def __repr__(self):
3180 if self.pull_request_version_id:
3194 if self.pull_request_version_id:
3181 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3195 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3182 else:
3196 else:
3183 return '<DB:PullRequestVersion at %#x>' % id(self)
3197 return '<DB:PullRequestVersion at %#x>' % id(self)
3184
3198
3185
3199
3186 class PullRequestReviewers(Base, BaseModel):
3200 class PullRequestReviewers(Base, BaseModel):
3187 __tablename__ = 'pull_request_reviewers'
3201 __tablename__ = 'pull_request_reviewers'
3188 __table_args__ = (
3202 __table_args__ = (
3189 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3190 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3191 )
3205 )
3192
3206
3193 def __init__(self, user=None, pull_request=None):
3207 def __init__(self, user=None, pull_request=None):
3194 self.user = user
3208 self.user = user
3195 self.pull_request = pull_request
3209 self.pull_request = pull_request
3196
3210
3197 pull_requests_reviewers_id = Column(
3211 pull_requests_reviewers_id = Column(
3198 'pull_requests_reviewers_id', Integer(), nullable=False,
3212 'pull_requests_reviewers_id', Integer(), nullable=False,
3199 primary_key=True)
3213 primary_key=True)
3200 pull_request_id = Column(
3214 pull_request_id = Column(
3201 "pull_request_id", Integer(),
3215 "pull_request_id", Integer(),
3202 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3216 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3203 user_id = Column(
3217 user_id = Column(
3204 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3218 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3205
3219
3206 user = relationship('User')
3220 user = relationship('User')
3207 pull_request = relationship('PullRequest')
3221 pull_request = relationship('PullRequest')
3208
3222
3209
3223
3210 class Notification(Base, BaseModel):
3224 class Notification(Base, BaseModel):
3211 __tablename__ = 'notifications'
3225 __tablename__ = 'notifications'
3212 __table_args__ = (
3226 __table_args__ = (
3213 Index('notification_type_idx', 'type'),
3227 Index('notification_type_idx', 'type'),
3214 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3228 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3215 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3229 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3216 )
3230 )
3217
3231
3218 TYPE_CHANGESET_COMMENT = u'cs_comment'
3232 TYPE_CHANGESET_COMMENT = u'cs_comment'
3219 TYPE_MESSAGE = u'message'
3233 TYPE_MESSAGE = u'message'
3220 TYPE_MENTION = u'mention'
3234 TYPE_MENTION = u'mention'
3221 TYPE_REGISTRATION = u'registration'
3235 TYPE_REGISTRATION = u'registration'
3222 TYPE_PULL_REQUEST = u'pull_request'
3236 TYPE_PULL_REQUEST = u'pull_request'
3223 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3237 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3224
3238
3225 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3239 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3226 subject = Column('subject', Unicode(512), nullable=True)
3240 subject = Column('subject', Unicode(512), nullable=True)
3227 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3241 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3228 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3242 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3229 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3243 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3230 type_ = Column('type', Unicode(255))
3244 type_ = Column('type', Unicode(255))
3231
3245
3232 created_by_user = relationship('User')
3246 created_by_user = relationship('User')
3233 notifications_to_users = relationship('UserNotification', lazy='joined',
3247 notifications_to_users = relationship('UserNotification', lazy='joined',
3234 cascade="all, delete, delete-orphan")
3248 cascade="all, delete, delete-orphan")
3235
3249
3236 @property
3250 @property
3237 def recipients(self):
3251 def recipients(self):
3238 return [x.user for x in UserNotification.query()\
3252 return [x.user for x in UserNotification.query()\
3239 .filter(UserNotification.notification == self)\
3253 .filter(UserNotification.notification == self)\
3240 .order_by(UserNotification.user_id.asc()).all()]
3254 .order_by(UserNotification.user_id.asc()).all()]
3241
3255
3242 @classmethod
3256 @classmethod
3243 def create(cls, created_by, subject, body, recipients, type_=None):
3257 def create(cls, created_by, subject, body, recipients, type_=None):
3244 if type_ is None:
3258 if type_ is None:
3245 type_ = Notification.TYPE_MESSAGE
3259 type_ = Notification.TYPE_MESSAGE
3246
3260
3247 notification = cls()
3261 notification = cls()
3248 notification.created_by_user = created_by
3262 notification.created_by_user = created_by
3249 notification.subject = subject
3263 notification.subject = subject
3250 notification.body = body
3264 notification.body = body
3251 notification.type_ = type_
3265 notification.type_ = type_
3252 notification.created_on = datetime.datetime.now()
3266 notification.created_on = datetime.datetime.now()
3253
3267
3254 for u in recipients:
3268 for u in recipients:
3255 assoc = UserNotification()
3269 assoc = UserNotification()
3256 assoc.notification = notification
3270 assoc.notification = notification
3257
3271
3258 # if created_by is inside recipients mark his notification
3272 # if created_by is inside recipients mark his notification
3259 # as read
3273 # as read
3260 if u.user_id == created_by.user_id:
3274 if u.user_id == created_by.user_id:
3261 assoc.read = True
3275 assoc.read = True
3262
3276
3263 u.notifications.append(assoc)
3277 u.notifications.append(assoc)
3264 Session().add(notification)
3278 Session().add(notification)
3265
3279
3266 return notification
3280 return notification
3267
3281
3268 @property
3282 @property
3269 def description(self):
3283 def description(self):
3270 from rhodecode.model.notification import NotificationModel
3284 from rhodecode.model.notification import NotificationModel
3271 return NotificationModel().make_description(self)
3285 return NotificationModel().make_description(self)
3272
3286
3273
3287
3274 class UserNotification(Base, BaseModel):
3288 class UserNotification(Base, BaseModel):
3275 __tablename__ = 'user_to_notification'
3289 __tablename__ = 'user_to_notification'
3276 __table_args__ = (
3290 __table_args__ = (
3277 UniqueConstraint('user_id', 'notification_id'),
3291 UniqueConstraint('user_id', 'notification_id'),
3278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3292 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3279 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3293 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3280 )
3294 )
3281 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3295 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3282 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3296 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3283 read = Column('read', Boolean, default=False)
3297 read = Column('read', Boolean, default=False)
3284 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3298 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3285
3299
3286 user = relationship('User', lazy="joined")
3300 user = relationship('User', lazy="joined")
3287 notification = relationship('Notification', lazy="joined",
3301 notification = relationship('Notification', lazy="joined",
3288 order_by=lambda: Notification.created_on.desc(),)
3302 order_by=lambda: Notification.created_on.desc(),)
3289
3303
3290 def mark_as_read(self):
3304 def mark_as_read(self):
3291 self.read = True
3305 self.read = True
3292 Session().add(self)
3306 Session().add(self)
3293
3307
3294
3308
3295 class Gist(Base, BaseModel):
3309 class Gist(Base, BaseModel):
3296 __tablename__ = 'gists'
3310 __tablename__ = 'gists'
3297 __table_args__ = (
3311 __table_args__ = (
3298 Index('g_gist_access_id_idx', 'gist_access_id'),
3312 Index('g_gist_access_id_idx', 'gist_access_id'),
3299 Index('g_created_on_idx', 'created_on'),
3313 Index('g_created_on_idx', 'created_on'),
3300 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3301 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3315 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3302 )
3316 )
3303 GIST_PUBLIC = u'public'
3317 GIST_PUBLIC = u'public'
3304 GIST_PRIVATE = u'private'
3318 GIST_PRIVATE = u'private'
3305 DEFAULT_FILENAME = u'gistfile1.txt'
3319 DEFAULT_FILENAME = u'gistfile1.txt'
3306
3320
3307 ACL_LEVEL_PUBLIC = u'acl_public'
3321 ACL_LEVEL_PUBLIC = u'acl_public'
3308 ACL_LEVEL_PRIVATE = u'acl_private'
3322 ACL_LEVEL_PRIVATE = u'acl_private'
3309
3323
3310 gist_id = Column('gist_id', Integer(), primary_key=True)
3324 gist_id = Column('gist_id', Integer(), primary_key=True)
3311 gist_access_id = Column('gist_access_id', Unicode(250))
3325 gist_access_id = Column('gist_access_id', Unicode(250))
3312 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3326 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3313 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3327 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3314 gist_expires = Column('gist_expires', Float(53), nullable=False)
3328 gist_expires = Column('gist_expires', Float(53), nullable=False)
3315 gist_type = Column('gist_type', Unicode(128), nullable=False)
3329 gist_type = Column('gist_type', Unicode(128), nullable=False)
3316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3330 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3317 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3331 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3318 acl_level = Column('acl_level', Unicode(128), nullable=True)
3332 acl_level = Column('acl_level', Unicode(128), nullable=True)
3319
3333
3320 owner = relationship('User')
3334 owner = relationship('User')
3321
3335
3322 def __repr__(self):
3336 def __repr__(self):
3323 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3337 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3324
3338
3325 @classmethod
3339 @classmethod
3326 def get_or_404(cls, id_):
3340 def get_or_404(cls, id_):
3327 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3341 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3328 if not res:
3342 if not res:
3329 raise HTTPNotFound
3343 raise HTTPNotFound
3330 return res
3344 return res
3331
3345
3332 @classmethod
3346 @classmethod
3333 def get_by_access_id(cls, gist_access_id):
3347 def get_by_access_id(cls, gist_access_id):
3334 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3348 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3335
3349
3336 def gist_url(self):
3350 def gist_url(self):
3337 import rhodecode
3351 import rhodecode
3338 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3352 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3339 if alias_url:
3353 if alias_url:
3340 return alias_url.replace('{gistid}', self.gist_access_id)
3354 return alias_url.replace('{gistid}', self.gist_access_id)
3341
3355
3342 return url('gist', gist_id=self.gist_access_id, qualified=True)
3356 return url('gist', gist_id=self.gist_access_id, qualified=True)
3343
3357
3344 @classmethod
3358 @classmethod
3345 def base_path(cls):
3359 def base_path(cls):
3346 """
3360 """
3347 Returns base path when all gists are stored
3361 Returns base path when all gists are stored
3348
3362
3349 :param cls:
3363 :param cls:
3350 """
3364 """
3351 from rhodecode.model.gist import GIST_STORE_LOC
3365 from rhodecode.model.gist import GIST_STORE_LOC
3352 q = Session().query(RhodeCodeUi)\
3366 q = Session().query(RhodeCodeUi)\
3353 .filter(RhodeCodeUi.ui_key == URL_SEP)
3367 .filter(RhodeCodeUi.ui_key == URL_SEP)
3354 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3368 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3355 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3369 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3356
3370
3357 def get_api_data(self):
3371 def get_api_data(self):
3358 """
3372 """
3359 Common function for generating gist related data for API
3373 Common function for generating gist related data for API
3360 """
3374 """
3361 gist = self
3375 gist = self
3362 data = {
3376 data = {
3363 'gist_id': gist.gist_id,
3377 'gist_id': gist.gist_id,
3364 'type': gist.gist_type,
3378 'type': gist.gist_type,
3365 'access_id': gist.gist_access_id,
3379 'access_id': gist.gist_access_id,
3366 'description': gist.gist_description,
3380 'description': gist.gist_description,
3367 'url': gist.gist_url(),
3381 'url': gist.gist_url(),
3368 'expires': gist.gist_expires,
3382 'expires': gist.gist_expires,
3369 'created_on': gist.created_on,
3383 'created_on': gist.created_on,
3370 'modified_at': gist.modified_at,
3384 'modified_at': gist.modified_at,
3371 'content': None,
3385 'content': None,
3372 'acl_level': gist.acl_level,
3386 'acl_level': gist.acl_level,
3373 }
3387 }
3374 return data
3388 return data
3375
3389
3376 def __json__(self):
3390 def __json__(self):
3377 data = dict(
3391 data = dict(
3378 )
3392 )
3379 data.update(self.get_api_data())
3393 data.update(self.get_api_data())
3380 return data
3394 return data
3381 # SCM functions
3395 # SCM functions
3382
3396
3383 def scm_instance(self, **kwargs):
3397 def scm_instance(self, **kwargs):
3384 from rhodecode.lib.vcs import get_repo
3398 from rhodecode.lib.vcs import get_repo
3385 base_path = self.base_path()
3399 base_path = self.base_path()
3386 return get_repo(os.path.join(*map(safe_str,
3400 return get_repo(os.path.join(*map(safe_str,
3387 [base_path, self.gist_access_id])))
3401 [base_path, self.gist_access_id])))
3388
3402
3389
3403
3390 class DbMigrateVersion(Base, BaseModel):
3404 class DbMigrateVersion(Base, BaseModel):
3391 __tablename__ = 'db_migrate_version'
3405 __tablename__ = 'db_migrate_version'
3392 __table_args__ = (
3406 __table_args__ = (
3393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3408 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3395 )
3409 )
3396 repository_id = Column('repository_id', String(250), primary_key=True)
3410 repository_id = Column('repository_id', String(250), primary_key=True)
3397 repository_path = Column('repository_path', Text)
3411 repository_path = Column('repository_path', Text)
3398 version = Column('version', Integer)
3412 version = Column('version', Integer)
3399
3413
3400
3414
3401 class ExternalIdentity(Base, BaseModel):
3415 class ExternalIdentity(Base, BaseModel):
3402 __tablename__ = 'external_identities'
3416 __tablename__ = 'external_identities'
3403 __table_args__ = (
3417 __table_args__ = (
3404 Index('local_user_id_idx', 'local_user_id'),
3418 Index('local_user_id_idx', 'local_user_id'),
3405 Index('external_id_idx', 'external_id'),
3419 Index('external_id_idx', 'external_id'),
3406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3420 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3407 'mysql_charset': 'utf8'})
3421 'mysql_charset': 'utf8'})
3408
3422
3409 external_id = Column('external_id', Unicode(255), default=u'',
3423 external_id = Column('external_id', Unicode(255), default=u'',
3410 primary_key=True)
3424 primary_key=True)
3411 external_username = Column('external_username', Unicode(1024), default=u'')
3425 external_username = Column('external_username', Unicode(1024), default=u'')
3412 local_user_id = Column('local_user_id', Integer(),
3426 local_user_id = Column('local_user_id', Integer(),
3413 ForeignKey('users.user_id'), primary_key=True)
3427 ForeignKey('users.user_id'), primary_key=True)
3414 provider_name = Column('provider_name', Unicode(255), default=u'',
3428 provider_name = Column('provider_name', Unicode(255), default=u'',
3415 primary_key=True)
3429 primary_key=True)
3416 access_token = Column('access_token', String(1024), default=u'')
3430 access_token = Column('access_token', String(1024), default=u'')
3417 alt_token = Column('alt_token', String(1024), default=u'')
3431 alt_token = Column('alt_token', String(1024), default=u'')
3418 token_secret = Column('token_secret', String(1024), default=u'')
3432 token_secret = Column('token_secret', String(1024), default=u'')
3419
3433
3420 @classmethod
3434 @classmethod
3421 def by_external_id_and_provider(cls, external_id, provider_name,
3435 def by_external_id_and_provider(cls, external_id, provider_name,
3422 local_user_id=None):
3436 local_user_id=None):
3423 """
3437 """
3424 Returns ExternalIdentity instance based on search params
3438 Returns ExternalIdentity instance based on search params
3425
3439
3426 :param external_id:
3440 :param external_id:
3427 :param provider_name:
3441 :param provider_name:
3428 :return: ExternalIdentity
3442 :return: ExternalIdentity
3429 """
3443 """
3430 query = cls.query()
3444 query = cls.query()
3431 query = query.filter(cls.external_id == external_id)
3445 query = query.filter(cls.external_id == external_id)
3432 query = query.filter(cls.provider_name == provider_name)
3446 query = query.filter(cls.provider_name == provider_name)
3433 if local_user_id:
3447 if local_user_id:
3434 query = query.filter(cls.local_user_id == local_user_id)
3448 query = query.filter(cls.local_user_id == local_user_id)
3435 return query.first()
3449 return query.first()
3436
3450
3437 @classmethod
3451 @classmethod
3438 def user_by_external_id_and_provider(cls, external_id, provider_name):
3452 def user_by_external_id_and_provider(cls, external_id, provider_name):
3439 """
3453 """
3440 Returns User instance based on search params
3454 Returns User instance based on search params
3441
3455
3442 :param external_id:
3456 :param external_id:
3443 :param provider_name:
3457 :param provider_name:
3444 :return: User
3458 :return: User
3445 """
3459 """
3446 query = User.query()
3460 query = User.query()
3447 query = query.filter(cls.external_id == external_id)
3461 query = query.filter(cls.external_id == external_id)
3448 query = query.filter(cls.provider_name == provider_name)
3462 query = query.filter(cls.provider_name == provider_name)
3449 query = query.filter(User.user_id == cls.local_user_id)
3463 query = query.filter(User.user_id == cls.local_user_id)
3450 return query.first()
3464 return query.first()
3451
3465
3452 @classmethod
3466 @classmethod
3453 def by_local_user_id(cls, local_user_id):
3467 def by_local_user_id(cls, local_user_id):
3454 """
3468 """
3455 Returns all tokens for user
3469 Returns all tokens for user
3456
3470
3457 :param local_user_id:
3471 :param local_user_id:
3458 :return: ExternalIdentity
3472 :return: ExternalIdentity
3459 """
3473 """
3460 query = cls.query()
3474 query = cls.query()
3461 query = query.filter(cls.local_user_id == local_user_id)
3475 query = query.filter(cls.local_user_id == local_user_id)
3462 return query
3476 return query
@@ -1,655 +1,655 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 ${self.body()}
19 ${self.body()}
20 </div>
20 </div>
21 </div>
21 </div>
22 ${self.menu_bar_subnav()}
22 ${self.menu_bar_subnav()}
23 <!-- END HEADER -->
23 <!-- END HEADER -->
24
24
25 <!-- CONTENT -->
25 <!-- CONTENT -->
26 <div id="content" class="wrapper">
26 <div id="content" class="wrapper">
27 ${self.flash_msg()}
27 ${self.flash_msg()}
28 <div class="main">
28 <div class="main">
29 ${next.main()}
29 ${next.main()}
30 </div>
30 </div>
31 </div>
31 </div>
32 <!-- END CONTENT -->
32 <!-- END CONTENT -->
33
33
34 </div>
34 </div>
35 <!-- FOOTER -->
35 <!-- FOOTER -->
36 <div id="footer">
36 <div id="footer">
37 <div id="footer-inner" class="title wrapper">
37 <div id="footer-inner" class="title wrapper">
38 <div>
38 <div>
39 <p class="footer-link-right">
39 <p class="footer-link-right">
40 % if c.visual.show_version:
40 % if c.visual.show_version:
41 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
41 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 % endif
42 % endif
43 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
43 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 % if c.visual.rhodecode_support_url:
44 % if c.visual.rhodecode_support_url:
45 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
45 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 % endif
46 % endif
47 </p>
47 </p>
48 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
48 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <p class="server-instance" style="display:${sid}">
49 <p class="server-instance" style="display:${sid}">
50 ## display hidden instance ID if specially defined
50 ## display hidden instance ID if specially defined
51 % if c.rhodecode_instanceid:
51 % if c.rhodecode_instanceid:
52 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
52 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 % endif
53 % endif
54 </p>
54 </p>
55 </div>
55 </div>
56 </div>
56 </div>
57 </div>
57 </div>
58
58
59 <!-- END FOOTER -->
59 <!-- END FOOTER -->
60
60
61 ### MAKO DEFS ###
61 ### MAKO DEFS ###
62
62
63 <%def name="menu_bar_subnav()">
63 <%def name="menu_bar_subnav()">
64 </%def>
64 </%def>
65
65
66 <%def name="flash_msg()">
66 <%def name="flash_msg()">
67 <%include file="/base/flash_msg.html"/>
67 <%include file="/base/flash_msg.html"/>
68 </%def>
68 </%def>
69
69
70 <%def name="breadcrumbs(class_='breadcrumbs')">
70 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <div class="${class_}">
71 <div class="${class_}">
72 ${self.breadcrumbs_links()}
72 ${self.breadcrumbs_links()}
73 </div>
73 </div>
74 </%def>
74 </%def>
75
75
76 <%def name="admin_menu()">
76 <%def name="admin_menu()">
77 <ul class="admin_menu submenu">
77 <ul class="admin_menu submenu">
78 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
78 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
79 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
79 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
80 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
80 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
81 <li><a href="${h.url('users')}">${_('Users')}</a></li>
81 <li><a href="${h.url('users')}">${_('Users')}</a></li>
82 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
82 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
83 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
83 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
84 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
84 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
87 </ul>
87 </ul>
88 </%def>
88 </%def>
89
89
90
90
91 <%def name="dt_info_panel(elements)">
91 <%def name="dt_info_panel(elements)">
92 <dl class="dl-horizontal">
92 <dl class="dl-horizontal">
93 %for dt, dd, title, show_items in elements:
93 %for dt, dd, title, show_items in elements:
94 <dt>${dt}:</dt>
94 <dt>${dt}:</dt>
95 <dd title="${title}">
95 <dd title="${title}">
96 %if callable(dd):
96 %if callable(dd):
97 ## allow lazy evaluation of elements
97 ## allow lazy evaluation of elements
98 ${dd()}
98 ${dd()}
99 %else:
99 %else:
100 ${dd}
100 ${dd}
101 %endif
101 %endif
102 %if show_items:
102 %if show_items:
103 <span class="btn-collapse" data-toggle="item-${h.md5(dt)[:6]}-details">${_('Show More')} </span>
103 <span class="btn-collapse" data-toggle="item-${h.md5(dt)[:6]}-details">${_('Show More')} </span>
104 %endif
104 %endif
105 </dd>
105 </dd>
106
106
107 %if show_items:
107 %if show_items:
108 <div class="collapsable-content" data-toggle="item-${h.md5(dt)[:6]}-details" style="display: none">
108 <div class="collapsable-content" data-toggle="item-${h.md5(dt)[:6]}-details" style="display: none">
109 %for item in show_items:
109 %for item in show_items:
110 <dt></dt>
110 <dt></dt>
111 <dd>${item}</dd>
111 <dd>${item}</dd>
112 %endfor
112 %endfor
113 </div>
113 </div>
114 %endif
114 %endif
115
115
116 %endfor
116 %endfor
117 </dl>
117 </dl>
118 </%def>
118 </%def>
119
119
120
120
121 <%def name="gravatar(email, size=16)">
121 <%def name="gravatar(email, size=16)">
122 <%
122 <%
123 if (size > 16):
123 if (size > 16):
124 gravatar_class = 'gravatar gravatar-large'
124 gravatar_class = 'gravatar gravatar-large'
125 else:
125 else:
126 gravatar_class = 'gravatar'
126 gravatar_class = 'gravatar'
127 %>
127 %>
128 <%doc>
128 <%doc>
129 TODO: johbo: For now we serve double size images to make it smooth
129 TODO: johbo: For now we serve double size images to make it smooth
130 for retina. This is how it worked until now. Should be replaced
130 for retina. This is how it worked until now. Should be replaced
131 with a better solution at some point.
131 with a better solution at some point.
132 </%doc>
132 </%doc>
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 </%def>
134 </%def>
135
135
136
136
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 <div class="rc-user tooltip" title="${contact}">
138 <div class="rc-user tooltip" title="${contact}">
139 ${self.gravatar(h.email_or_none(contact), size)}
139 ${self.gravatar(h.email_or_none(contact), size)}
140 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 </div>
141 </div>
142 </%def>
142 </%def>
143
143
144
144
145 ## admin menu used for people that have some admin resources
145 ## admin menu used for people that have some admin resources
146 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 <ul class="submenu">
147 <ul class="submenu">
148 %if repositories:
148 %if repositories:
149 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
150 %endif
150 %endif
151 %if repository_groups:
151 %if repository_groups:
152 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
153 %endif
153 %endif
154 %if user_groups:
154 %if user_groups:
155 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
156 %endif
156 %endif
157 </ul>
157 </ul>
158 </%def>
158 </%def>
159
159
160 <%def name="repo_page_title(repo_instance)">
160 <%def name="repo_page_title(repo_instance)">
161 <div class="title-content">
161 <div class="title-content">
162 <div class="title-main">
162 <div class="title-main">
163 ## SVN/HG/GIT icons
163 ## SVN/HG/GIT icons
164 %if h.is_hg(repo_instance):
164 %if h.is_hg(repo_instance):
165 <i class="icon-hg"></i>
165 <i class="icon-hg"></i>
166 %endif
166 %endif
167 %if h.is_git(repo_instance):
167 %if h.is_git(repo_instance):
168 <i class="icon-git"></i>
168 <i class="icon-git"></i>
169 %endif
169 %endif
170 %if h.is_svn(repo_instance):
170 %if h.is_svn(repo_instance):
171 <i class="icon-svn"></i>
171 <i class="icon-svn"></i>
172 %endif
172 %endif
173
173
174 ## public/private
174 ## public/private
175 %if repo_instance.private:
175 %if repo_instance.private:
176 <i class="icon-repo-private"></i>
176 <i class="icon-repo-private"></i>
177 %else:
177 %else:
178 <i class="icon-repo-public"></i>
178 <i class="icon-repo-public"></i>
179 %endif
179 %endif
180
180
181 ## repo name with group name
181 ## repo name with group name
182 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183
183
184 </div>
184 </div>
185
185
186 ## FORKED
186 ## FORKED
187 %if repo_instance.fork:
187 %if repo_instance.fork:
188 <p>
188 <p>
189 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <i class="icon-code-fork"></i> ${_('Fork of')}
190 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
191 </p>
191 </p>
192 %endif
192 %endif
193
193
194 ## IMPORTED FROM REMOTE
194 ## IMPORTED FROM REMOTE
195 %if repo_instance.clone_uri:
195 %if repo_instance.clone_uri:
196 <p>
196 <p>
197 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <i class="icon-code-fork"></i> ${_('Clone from')}
198 <a href="${h.url(str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 </p>
199 </p>
200 %endif
200 %endif
201
201
202 ## LOCKING STATUS
202 ## LOCKING STATUS
203 %if repo_instance.locked[0]:
203 %if repo_instance.locked[0]:
204 <p class="locking_locked">
204 <p class="locking_locked">
205 <i class="icon-repo-lock"></i>
205 <i class="icon-repo-lock"></i>
206 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 </p>
207 </p>
208 %elif repo_instance.enable_locking:
208 %elif repo_instance.enable_locking:
209 <p class="locking_unlocked">
209 <p class="locking_unlocked">
210 <i class="icon-repo-unlock"></i>
210 <i class="icon-repo-unlock"></i>
211 ${_('Repository not locked. Pull repository to lock it.')}
211 ${_('Repository not locked. Pull repository to lock it.')}
212 </p>
212 </p>
213 %endif
213 %endif
214
214
215 </div>
215 </div>
216 </%def>
216 </%def>
217
217
218 <%def name="repo_menu(active=None)">
218 <%def name="repo_menu(active=None)">
219 <%
219 <%
220 def is_active(selected):
220 def is_active(selected):
221 if selected == active:
221 if selected == active:
222 return "active"
222 return "active"
223 %>
223 %>
224
224
225 <!--- CONTEXT BAR -->
225 <!--- CONTEXT BAR -->
226 <div id="context-bar">
226 <div id="context-bar">
227 <div class="wrapper">
227 <div class="wrapper">
228 <ul id="context-pages" class="horizontal-list navigation">
228 <ul id="context-pages" class="horizontal-list navigation">
229 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
232 <li class="${is_active('compare')}">
232 <li class="${is_active('compare')}">
233 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
234 </li>
234 </li>
235 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
236 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
237 <li class="${is_active('showpullrequest')}">
237 <li class="${is_active('showpullrequest')}">
238 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
238 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
239 %if c.repository_pull_requests:
239 %if c.repository_pull_requests:
240 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 <span class="pr_notifications">${c.repository_pull_requests}</span>
241 %endif
241 %endif
242 <div class="menulabel">${_('Pull Requests')}</div>
242 <div class="menulabel">${_('Pull Requests')}</div>
243 </a>
243 </a>
244 </li>
244 </li>
245 %endif
245 %endif
246 <li class="${is_active('options')}">
246 <li class="${is_active('options')}">
247 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
247 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
248 <ul class="submenu">
248 <ul class="submenu">
249 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 %endif
251 %endif
252 %if c.rhodecode_db_repo.fork:
252 %if c.rhodecode_db_repo.fork:
253 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
254 ${_('Compare fork')}</a></li>
254 ${_('Compare fork')}</a></li>
255 %endif
255 %endif
256
256
257 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
257 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
258
258
259 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 %if c.rhodecode_db_repo.locked[0]:
260 %if c.rhodecode_db_repo.locked[0]:
261 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
261 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
262 %else:
262 %else:
263 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
263 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
264 %endif
264 %endif
265 %endif
265 %endif
266 %if c.rhodecode_user.username != h.DEFAULT_USER:
266 %if c.rhodecode_user.username != h.DEFAULT_USER:
267 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
267 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
268 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
268 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
269 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
269 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
270 %endif
270 %endif
271 %endif
271 %endif
272 </ul>
272 </ul>
273 </li>
273 </li>
274 </ul>
274 </ul>
275 </div>
275 </div>
276 <div class="clear"></div>
276 <div class="clear"></div>
277 </div>
277 </div>
278 <!--- END CONTEXT BAR -->
278 <!--- END CONTEXT BAR -->
279
279
280 </%def>
280 </%def>
281
281
282 <%def name="usermenu()">
282 <%def name="usermenu()">
283 ## USER MENU
283 ## USER MENU
284 <li id="quick_login_li">
284 <li id="quick_login_li">
285 <a id="quick_login_link" class="menulink childs">
285 <a id="quick_login_link" class="menulink childs">
286 ${gravatar(c.rhodecode_user.email, 20)}
286 ${gravatar(c.rhodecode_user.email, 20)}
287 <span class="user">
287 <span class="user">
288 %if c.rhodecode_user.username != h.DEFAULT_USER:
288 %if c.rhodecode_user.username != h.DEFAULT_USER:
289 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
289 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
290 %else:
290 %else:
291 <span>${_('Sign in')}</span>
291 <span>${_('Sign in')}</span>
292 %endif
292 %endif
293 </span>
293 </span>
294 </a>
294 </a>
295
295
296 <div class="user-menu submenu">
296 <div class="user-menu submenu">
297 <div id="quick_login">
297 <div id="quick_login">
298 %if c.rhodecode_user.username == h.DEFAULT_USER:
298 %if c.rhodecode_user.username == h.DEFAULT_USER:
299 <h4>${_('Sign in to your account')}</h4>
299 <h4>${_('Sign in to your account')}</h4>
300 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
300 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
301 <div class="form form-vertical">
301 <div class="form form-vertical">
302 <div class="fields">
302 <div class="fields">
303 <div class="field">
303 <div class="field">
304 <div class="label">
304 <div class="label">
305 <label for="username">${_('Username')}:</label>
305 <label for="username">${_('Username')}:</label>
306 </div>
306 </div>
307 <div class="input">
307 <div class="input">
308 ${h.text('username',class_='focus',tabindex=1)}
308 ${h.text('username',class_='focus',tabindex=1)}
309 </div>
309 </div>
310
310
311 </div>
311 </div>
312 <div class="field">
312 <div class="field">
313 <div class="label">
313 <div class="label">
314 <label for="password">${_('Password')}:</label>
314 <label for="password">${_('Password')}:</label>
315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
316 </div>
316 </div>
317 <div class="input">
317 <div class="input">
318 ${h.password('password',class_='focus',tabindex=2)}
318 ${h.password('password',class_='focus',tabindex=2)}
319 </div>
319 </div>
320 </div>
320 </div>
321 <div class="buttons">
321 <div class="buttons">
322 <div class="register">
322 <div class="register">
323 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
323 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
324 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
324 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
325 %endif
325 %endif
326 </div>
326 </div>
327 <div class="submit">
327 <div class="submit">
328 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
328 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
329 </div>
329 </div>
330 </div>
330 </div>
331 </div>
331 </div>
332 </div>
332 </div>
333 ${h.end_form()}
333 ${h.end_form()}
334 %else:
334 %else:
335 <div class="">
335 <div class="">
336 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
336 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
337 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
337 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
338 <div class="email">${c.rhodecode_user.email}</div>
338 <div class="email">${c.rhodecode_user.email}</div>
339 </div>
339 </div>
340 <div class="">
340 <div class="">
341 <ol class="links">
341 <ol class="links">
342 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
342 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
343 <li class="logout">
343 <li class="logout">
344 ${h.secure_form(h.route_path('logout'))}
344 ${h.secure_form(h.route_path('logout'))}
345 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
345 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
346 ${h.end_form()}
346 ${h.end_form()}
347 </li>
347 </li>
348 </ol>
348 </ol>
349 </div>
349 </div>
350 %endif
350 %endif
351 </div>
351 </div>
352 </div>
352 </div>
353 %if c.rhodecode_user.username != h.DEFAULT_USER:
353 %if c.rhodecode_user.username != h.DEFAULT_USER:
354 <div class="pill_container">
354 <div class="pill_container">
355 % if c.unread_notifications == 0:
355 % if c.unread_notifications == 0:
356 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
356 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
357 % else:
357 % else:
358 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
358 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
359 % endif
359 % endif
360 </div>
360 </div>
361 % endif
361 % endif
362 </li>
362 </li>
363 </%def>
363 </%def>
364
364
365 <%def name="menu_items(active=None)">
365 <%def name="menu_items(active=None)">
366 <%
366 <%
367 def is_active(selected):
367 def is_active(selected):
368 if selected == active:
368 if selected == active:
369 return "active"
369 return "active"
370 return ""
370 return ""
371 %>
371 %>
372 <ul id="quick" class="main_nav navigation horizontal-list">
372 <ul id="quick" class="main_nav navigation horizontal-list">
373 <!-- repo switcher -->
373 <!-- repo switcher -->
374 <li class="${is_active('repositories')} repo_switcher_li has_select2">
374 <li class="${is_active('repositories')} repo_switcher_li has_select2">
375 <input id="repo_switcher" name="repo_switcher" type="hidden">
375 <input id="repo_switcher" name="repo_switcher" type="hidden">
376 </li>
376 </li>
377
377
378 ## ROOT MENU
378 ## ROOT MENU
379 %if c.rhodecode_user.username != h.DEFAULT_USER:
379 %if c.rhodecode_user.username != h.DEFAULT_USER:
380 <li class="${is_active('journal')}">
380 <li class="${is_active('journal')}">
381 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
381 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
382 <div class="menulabel">${_('Journal')}</div>
382 <div class="menulabel">${_('Journal')}</div>
383 </a>
383 </a>
384 </li>
384 </li>
385 %else:
385 %else:
386 <li class="${is_active('journal')}">
386 <li class="${is_active('journal')}">
387 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
387 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
388 <div class="menulabel">${_('Public journal')}</div>
388 <div class="menulabel">${_('Public journal')}</div>
389 </a>
389 </a>
390 </li>
390 </li>
391 %endif
391 %endif
392 <li class="${is_active('gists')}">
392 <li class="${is_active('gists')}">
393 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
393 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
394 <div class="menulabel">${_('Gists')}</div>
394 <div class="menulabel">${_('Gists')}</div>
395 </a>
395 </a>
396 </li>
396 </li>
397 <li class="${is_active('search')}">
397 <li class="${is_active('search')}">
398 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
398 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
399 <div class="menulabel">${_('Search')}</div>
399 <div class="menulabel">${_('Search')}</div>
400 </a>
400 </a>
401 </li>
401 </li>
402 % if h.HasPermissionAll('hg.admin')('access admin main page'):
402 % if h.HasPermissionAll('hg.admin')('access admin main page'):
403 <li class="${is_active('admin')}">
403 <li class="${is_active('admin')}">
404 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
404 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
405 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
405 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
406 </a>
406 </a>
407 ${admin_menu()}
407 ${admin_menu()}
408 </li>
408 </li>
409 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
409 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
410 <li class="${is_active('admin')}">
410 <li class="${is_active('admin')}">
411 <a class="menulink childs" title="${_('Delegated Admin settings')}">
411 <a class="menulink childs" title="${_('Delegated Admin settings')}">
412 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
412 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
413 </a>
413 </a>
414 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
414 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
415 c.rhodecode_user.repository_groups_admin,
415 c.rhodecode_user.repository_groups_admin,
416 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
416 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
417 </li>
417 </li>
418 % endif
418 % endif
419 % if c.debug_style:
419 % if c.debug_style:
420 <li class="${is_active('debug_style')}">
420 <li class="${is_active('debug_style')}">
421 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
421 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
422 <div class="menulabel">${_('Style')}</div>
422 <div class="menulabel">${_('Style')}</div>
423 </a>
423 </a>
424 </li>
424 </li>
425 % endif
425 % endif
426 ## render extra user menu
426 ## render extra user menu
427 ${usermenu()}
427 ${usermenu()}
428 </ul>
428 </ul>
429
429
430 <script type="text/javascript">
430 <script type="text/javascript">
431 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
431 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
432
432
433 /*format the look of items in the list*/
433 /*format the look of items in the list*/
434 var format = function(state, escapeMarkup){
434 var format = function(state, escapeMarkup){
435 if (!state.id){
435 if (!state.id){
436 return state.text; // optgroup
436 return state.text; // optgroup
437 }
437 }
438 var obj_dict = state.obj;
438 var obj_dict = state.obj;
439 var tmpl = '';
439 var tmpl = '';
440
440
441 if(obj_dict && state.type == 'repo'){
441 if(obj_dict && state.type == 'repo'){
442 if(obj_dict['repo_type'] === 'hg'){
442 if(obj_dict['repo_type'] === 'hg'){
443 tmpl += '<i class="icon-hg"></i> ';
443 tmpl += '<i class="icon-hg"></i> ';
444 }
444 }
445 else if(obj_dict['repo_type'] === 'git'){
445 else if(obj_dict['repo_type'] === 'git'){
446 tmpl += '<i class="icon-git"></i> ';
446 tmpl += '<i class="icon-git"></i> ';
447 }
447 }
448 else if(obj_dict['repo_type'] === 'svn'){
448 else if(obj_dict['repo_type'] === 'svn'){
449 tmpl += '<i class="icon-svn"></i> ';
449 tmpl += '<i class="icon-svn"></i> ';
450 }
450 }
451 if(obj_dict['private']){
451 if(obj_dict['private']){
452 tmpl += '<i class="icon-lock" ></i> ';
452 tmpl += '<i class="icon-lock" ></i> ';
453 }
453 }
454 else if(visual_show_public_icon){
454 else if(visual_show_public_icon){
455 tmpl += '<i class="icon-unlock-alt"></i> ';
455 tmpl += '<i class="icon-unlock-alt"></i> ';
456 }
456 }
457 }
457 }
458 if(obj_dict && state.type == 'commit') {
458 if(obj_dict && state.type == 'commit') {
459 tmpl += '<i class="icon-tag"></i>';
459 tmpl += '<i class="icon-tag"></i>';
460 }
460 }
461 if(obj_dict && state.type == 'group'){
461 if(obj_dict && state.type == 'group'){
462 tmpl += '<i class="icon-folder-close"></i> ';
462 tmpl += '<i class="icon-folder-close"></i> ';
463 }
463 }
464 tmpl += escapeMarkup(state.text);
464 tmpl += escapeMarkup(state.text);
465 return tmpl;
465 return tmpl;
466 };
466 };
467
467
468 var formatResult = function(result, container, query, escapeMarkup) {
468 var formatResult = function(result, container, query, escapeMarkup) {
469 return format(result, escapeMarkup);
469 return format(result, escapeMarkup);
470 };
470 };
471
471
472 var formatSelection = function(data, container, escapeMarkup) {
472 var formatSelection = function(data, container, escapeMarkup) {
473 return format(data, escapeMarkup);
473 return format(data, escapeMarkup);
474 };
474 };
475
475
476 $("#repo_switcher").select2({
476 $("#repo_switcher").select2({
477 cachedDataSource: {},
477 cachedDataSource: {},
478 minimumInputLength: 2,
478 minimumInputLength: 2,
479 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
479 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
480 dropdownAutoWidth: true,
480 dropdownAutoWidth: true,
481 formatResult: formatResult,
481 formatResult: formatResult,
482 formatSelection: formatSelection,
482 formatSelection: formatSelection,
483 containerCssClass: "repo-switcher",
483 containerCssClass: "repo-switcher",
484 dropdownCssClass: "repo-switcher-dropdown",
484 dropdownCssClass: "repo-switcher-dropdown",
485 escapeMarkup: function(m){
485 escapeMarkup: function(m){
486 // don't escape our custom placeholder
486 // don't escape our custom placeholder
487 if(m.substr(0,23) == '<div class="menulabel">'){
487 if(m.substr(0,23) == '<div class="menulabel">'){
488 return m;
488 return m;
489 }
489 }
490
490
491 return Select2.util.escapeMarkup(m);
491 return Select2.util.escapeMarkup(m);
492 },
492 },
493 query: $.debounce(250, function(query){
493 query: $.debounce(250, function(query){
494 self = this;
494 self = this;
495 var cacheKey = query.term;
495 var cacheKey = query.term;
496 var cachedData = self.cachedDataSource[cacheKey];
496 var cachedData = self.cachedDataSource[cacheKey];
497
497
498 if (cachedData) {
498 if (cachedData) {
499 query.callback({results: cachedData.results});
499 query.callback({results: cachedData.results});
500 } else {
500 } else {
501 $.ajax({
501 $.ajax({
502 url: "${h.url('goto_switcher_data')}",
502 url: "${h.url('goto_switcher_data')}",
503 data: {'query': query.term},
503 data: {'query': query.term},
504 dataType: 'json',
504 dataType: 'json',
505 type: 'GET',
505 type: 'GET',
506 success: function(data) {
506 success: function(data) {
507 self.cachedDataSource[cacheKey] = data;
507 self.cachedDataSource[cacheKey] = data;
508 query.callback({results: data.results});
508 query.callback({results: data.results});
509 },
509 },
510 error: function(data, textStatus, errorThrown) {
510 error: function(data, textStatus, errorThrown) {
511 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
511 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
512 }
512 }
513 })
513 })
514 }
514 }
515 })
515 })
516 });
516 });
517
517
518 $("#repo_switcher").on('select2-selecting', function(e){
518 $("#repo_switcher").on('select2-selecting', function(e){
519 e.preventDefault();
519 e.preventDefault();
520 window.location = e.choice.url;
520 window.location = e.choice.url;
521 });
521 });
522
522
523 ## Global mouse bindings ##
523 ## Global mouse bindings ##
524
524
525 // general help "?"
525 // general help "?"
526 Mousetrap.bind(['?'], function(e) {
526 Mousetrap.bind(['?'], function(e) {
527 $('#help_kb').modal({})
527 $('#help_kb').modal({})
528 });
528 });
529
529
530 // / open the quick filter
530 // / open the quick filter
531 Mousetrap.bind(['/'], function(e) {
531 Mousetrap.bind(['/'], function(e) {
532 $("#repo_switcher").select2("open");
532 $("#repo_switcher").select2("open");
533
533
534 // return false to prevent default browser behavior
534 // return false to prevent default browser behavior
535 // and stop event from bubbling
535 // and stop event from bubbling
536 return false;
536 return false;
537 });
537 });
538
538
539 // general nav g + action
539 // general nav g + action
540 Mousetrap.bind(['g h'], function(e) {
540 Mousetrap.bind(['g h'], function(e) {
541 window.location = pyroutes.url('home');
541 window.location = pyroutes.url('home');
542 });
542 });
543 Mousetrap.bind(['g g'], function(e) {
543 Mousetrap.bind(['g g'], function(e) {
544 window.location = pyroutes.url('gists', {'private':1});
544 window.location = pyroutes.url('gists', {'private':1});
545 });
545 });
546 Mousetrap.bind(['g G'], function(e) {
546 Mousetrap.bind(['g G'], function(e) {
547 window.location = pyroutes.url('gists', {'public':1});
547 window.location = pyroutes.url('gists', {'public':1});
548 });
548 });
549 Mousetrap.bind(['n g'], function(e) {
549 Mousetrap.bind(['n g'], function(e) {
550 window.location = pyroutes.url('new_gist');
550 window.location = pyroutes.url('new_gist');
551 });
551 });
552 Mousetrap.bind(['n r'], function(e) {
552 Mousetrap.bind(['n r'], function(e) {
553 window.location = pyroutes.url('new_repo');
553 window.location = pyroutes.url('new_repo');
554 });
554 });
555
555
556 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
556 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
557 // nav in repo context
557 // nav in repo context
558 Mousetrap.bind(['g s'], function(e) {
558 Mousetrap.bind(['g s'], function(e) {
559 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
559 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
560 });
560 });
561 Mousetrap.bind(['g c'], function(e) {
561 Mousetrap.bind(['g c'], function(e) {
562 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
562 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
563 });
563 });
564 Mousetrap.bind(['g F'], function(e) {
564 Mousetrap.bind(['g F'], function(e) {
565 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
565 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
566 });
566 });
567 Mousetrap.bind(['g f'], function(e) {
567 Mousetrap.bind(['g f'], function(e) {
568 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
568 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
569 });
569 });
570 Mousetrap.bind(['g p'], function(e) {
570 Mousetrap.bind(['g p'], function(e) {
571 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
571 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
572 });
572 });
573 Mousetrap.bind(['g o'], function(e) {
573 Mousetrap.bind(['g o'], function(e) {
574 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
574 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
575 });
575 });
576 Mousetrap.bind(['g O'], function(e) {
576 Mousetrap.bind(['g O'], function(e) {
577 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
577 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
578 });
578 });
579 % endif
579 % endif
580
580
581 </script>
581 </script>
582 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
582 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
583 </%def>
583 </%def>
584
584
585 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
585 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
586 <div class="modal-dialog">
586 <div class="modal-dialog">
587 <div class="modal-content">
587 <div class="modal-content">
588 <div class="modal-header">
588 <div class="modal-header">
589 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
589 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
590 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
590 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
591 </div>
591 </div>
592 <div class="modal-body">
592 <div class="modal-body">
593 <div class="block-left">
593 <div class="block-left">
594 <table class="keyboard-mappings">
594 <table class="keyboard-mappings">
595 <tbody>
595 <tbody>
596 <tr>
596 <tr>
597 <th></th>
597 <th></th>
598 <th>${_('Site-wide shortcuts')}</th>
598 <th>${_('Site-wide shortcuts')}</th>
599 </tr>
599 </tr>
600 <%
600 <%
601 elems = [
601 elems = [
602 ('/', 'Open quick search box'),
602 ('/', 'Open quick search box'),
603 ('g h', 'Goto home page'),
603 ('g h', 'Goto home page'),
604 ('g g', 'Goto my private gists page'),
604 ('g g', 'Goto my private gists page'),
605 ('g G', 'Goto my public gists page'),
605 ('g G', 'Goto my public gists page'),
606 ('n r', 'New repository page'),
606 ('n r', 'New repository page'),
607 ('n g', 'New gist page'),
607 ('n g', 'New gist page'),
608 ]
608 ]
609 %>
609 %>
610 %for key, desc in elems:
610 %for key, desc in elems:
611 <tr>
611 <tr>
612 <td class="keys">
612 <td class="keys">
613 <span class="key tag">${key}</span>
613 <span class="key tag">${key}</span>
614 </td>
614 </td>
615 <td>${desc}</td>
615 <td>${desc}</td>
616 </tr>
616 </tr>
617 %endfor
617 %endfor
618 </tbody>
618 </tbody>
619 </table>
619 </table>
620 </div>
620 </div>
621 <div class="block-left">
621 <div class="block-left">
622 <table class="keyboard-mappings">
622 <table class="keyboard-mappings">
623 <tbody>
623 <tbody>
624 <tr>
624 <tr>
625 <th></th>
625 <th></th>
626 <th>${_('Repositories')}</th>
626 <th>${_('Repositories')}</th>
627 </tr>
627 </tr>
628 <%
628 <%
629 elems = [
629 elems = [
630 ('g s', 'Goto summary page'),
630 ('g s', 'Goto summary page'),
631 ('g c', 'Goto changelog page'),
631 ('g c', 'Goto changelog page'),
632 ('g f', 'Goto files page'),
632 ('g f', 'Goto files page'),
633 ('g F', 'Goto files page with file search activated'),
633 ('g F', 'Goto files page with file search activated'),
634 ('g p', 'Goto pull requests page'),
634 ('g p', 'Goto pull requests page'),
635 ('g o', 'Goto repository settings'),
635 ('g o', 'Goto repository settings'),
636 ('g O', 'Goto repository permissions settings'),
636 ('g O', 'Goto repository permissions settings'),
637 ]
637 ]
638 %>
638 %>
639 %for key, desc in elems:
639 %for key, desc in elems:
640 <tr>
640 <tr>
641 <td class="keys">
641 <td class="keys">
642 <span class="key tag">${key}</span>
642 <span class="key tag">${key}</span>
643 </td>
643 </td>
644 <td>${desc}</td>
644 <td>${desc}</td>
645 </tr>
645 </tr>
646 %endfor
646 %endfor
647 </tbody>
647 </tbody>
648 </table>
648 </table>
649 </div>
649 </div>
650 </div>
650 </div>
651 <div class="modal-footer">
651 <div class="modal-footer">
652 </div>
652 </div>
653 </div><!-- /.modal-content -->
653 </div><!-- /.modal-content -->
654 </div><!-- /.modal-dialog -->
654 </div><!-- /.modal-dialog -->
655 </div><!-- /.modal -->
655 </div><!-- /.modal -->
@@ -1,40 +1,76 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 pytest
21 import pytest
22
22
23 from rhodecode.lib.encrypt import AESCipher
23 from rhodecode.lib.encrypt import (
24 AESCipher, SignatureVerificationError, InvalidDecryptedValue)
24
25
25
26
26 class TestEncryptModule(object):
27 class TestEncryptModule(object):
27
28
28 @pytest.mark.parametrize(
29 @pytest.mark.parametrize(
29 "key, text",
30 "key, text",
30 [
31 [
31 ('a', 'short'),
32 ('a', 'short'),
32 ('a'*64, 'too long(trimmed to 32)'),
33 ('a'*64, 'too long(trimmed to 32)'),
33 ('a'*32, 'just enough'),
34 ('a'*32, 'just enough'),
34 ('Δ…Δ‡Δ™Δ‡Δ™', 'non asci'),
35 ('Δ…Δ‡Δ™Δ‡Δ™', 'non asci'),
35 ('$asa$asa', 'special $ used'),
36 ('$asa$asa', 'special $ used'),
36 ]
37 ]
37 )
38 )
38 def test_encryption(self, key, text):
39 def test_encryption(self, key, text):
39 enc = AESCipher(key).encrypt(text)
40 enc = AESCipher(key).encrypt(text)
40 assert AESCipher(key).decrypt(enc) == text
41 assert AESCipher(key).decrypt(enc) == text
42
43 def test_encryption_with_hmac(self):
44 key = 'secret'
45 text = 'ihatemysql'
46 enc = AESCipher(key, hmac=True).encrypt(text)
47 assert AESCipher(key, hmac=True).decrypt(enc) == text
48
49 def test_encryption_with_hmac_with_bad_key(self):
50 key = 'secretstring'
51 text = 'ihatemysql'
52 enc = AESCipher(key, hmac=True).encrypt(text)
53
54 with pytest.raises(SignatureVerificationError) as e:
55 assert AESCipher('differentsecret', hmac=True).decrypt(enc) == ''
56
57 assert 'Encryption signature verification failed' in str(e)
58
59 def test_encryption_with_hmac_with_bad_data(self):
60 key = 'secret'
61 text = 'ihatemysql'
62 enc = AESCipher(key, hmac=True).encrypt(text)
63 enc = 'xyz' + enc[3:]
64 with pytest.raises(SignatureVerificationError) as e:
65 assert AESCipher(key, hmac=True).decrypt(enc) == text
66
67 assert 'Encryption signature verification failed' in str(e)
68
69 def test_encryption_with_hmac_with_bad_key_not_strict(self):
70 key = 'secretstring'
71 text = 'ihatemysql'
72 enc = AESCipher(key, hmac=True).encrypt(text)
73
74 assert isinstance(AESCipher(
75 'differentsecret', hmac=True, strict_verification=False
76 ).decrypt(enc), InvalidDecryptedValue)
General Comments 0
You need to be logged in to leave comments. Login now