##// END OF EJS Templates
caches: new cache context managers....
marcink -
r2932:9bfe4e0a default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,738 +1,722 b''
1
1
2
2
3 ################################################################################
3 ################################################################################
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10
10
11 ################################################################################
11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 ## EMAIL CONFIGURATION ##
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
19 #email_prefix = [RhodeCode]
20
20
21 ## email FROM address all mails will be sent
21 ## email FROM address all mails will be sent
22 #app_email_from = rhodecode-noreply@localhost
22 #app_email_from = rhodecode-noreply@localhost
23
23
24 ## Uncomment and replace with the address which should receive any error report
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
26 #email_to = admin@localhost
27
27
28 ## in case of Application errors, sent an error email form
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
29 #error_email_from = rhodecode_error@localhost
30
30
31 ## additional error message to be send in case of server crash
31 ## additional error message to be send in case of server crash
32 #error_message =
32 #error_message =
33
33
34
34
35 #smtp_server = mail.server.com
35 #smtp_server = mail.server.com
36 #smtp_username =
36 #smtp_username =
37 #smtp_password =
37 #smtp_password =
38 #smtp_port =
38 #smtp_port =
39 #smtp_use_tls = false
39 #smtp_use_tls = false
40 #smtp_use_ssl = true
40 #smtp_use_ssl = true
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 #smtp_auth =
42 #smtp_auth =
43
43
44 [server:main]
44 [server:main]
45 ## COMMON ##
45 ## COMMON ##
46 host = 127.0.0.1
46 host = 127.0.0.1
47 port = 5000
47 port = 5000
48
48
49 ##################################
49 ##################################
50 ## WAITRESS WSGI SERVER ##
50 ## WAITRESS WSGI SERVER ##
51 ## Recommended for Development ##
51 ## Recommended for Development ##
52 ##################################
52 ##################################
53
53
54 use = egg:waitress#main
54 use = egg:waitress#main
55 ## number of worker threads
55 ## number of worker threads
56 threads = 5
56 threads = 5
57 ## MAX BODY SIZE 100GB
57 ## MAX BODY SIZE 100GB
58 max_request_body_size = 107374182400
58 max_request_body_size = 107374182400
59 ## Use poll instead of select, fixes file descriptors limits problems.
59 ## Use poll instead of select, fixes file descriptors limits problems.
60 ## May not work on old windows systems.
60 ## May not work on old windows systems.
61 asyncore_use_poll = true
61 asyncore_use_poll = true
62
62
63
63
64 ##########################
64 ##########################
65 ## GUNICORN WSGI SERVER ##
65 ## GUNICORN WSGI SERVER ##
66 ##########################
66 ##########################
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
68
68
69 #use = egg:gunicorn#main
69 #use = egg:gunicorn#main
70 ## Sets the number of process workers. You must set `instance_id = *`
70 ## Sets the number of process workers. You must set `instance_id = *`
71 ## when this option is set to more than one worker, recommended
71 ## when this option is set to more than one worker, recommended
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
73 ## The `instance_id = *` must be set in the [app:main] section below
73 ## The `instance_id = *` must be set in the [app:main] section below
74 #workers = 2
74 #workers = 2
75 ## number of threads for each of the worker, must be set to 1 for gevent
75 ## number of threads for each of the worker, must be set to 1 for gevent
76 ## generally recommended to be at 1
76 ## generally recommended to be at 1
77 #threads = 1
77 #threads = 1
78 ## process name
78 ## process name
79 #proc_name = rhodecode
79 #proc_name = rhodecode
80 ## type of worker class, one of sync, gevent
80 ## type of worker class, one of sync, gevent
81 ## recommended for bigger setup is using of of other than sync one
81 ## recommended for bigger setup is using of of other than sync one
82 #worker_class = gevent
82 #worker_class = gevent
83 ## The maximum number of simultaneous clients. Valid only for Gevent
83 ## The maximum number of simultaneous clients. Valid only for Gevent
84 #worker_connections = 10
84 #worker_connections = 10
85 ## max number of requests that worker will handle before being gracefully
85 ## max number of requests that worker will handle before being gracefully
86 ## restarted, could prevent memory leaks
86 ## restarted, could prevent memory leaks
87 #max_requests = 1000
87 #max_requests = 1000
88 #max_requests_jitter = 30
88 #max_requests_jitter = 30
89 ## amount of time a worker can spend with handling a request before it
89 ## amount of time a worker can spend with handling a request before it
90 ## gets killed and restarted. Set to 6hrs
90 ## gets killed and restarted. Set to 6hrs
91 #timeout = 21600
91 #timeout = 21600
92
92
93
93
94 ## prefix middleware for RhodeCode.
94 ## prefix middleware for RhodeCode.
95 ## recommended when using proxy setup.
95 ## recommended when using proxy setup.
96 ## allows to set RhodeCode under a prefix in server.
96 ## allows to set RhodeCode under a prefix in server.
97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
98 ## And set your prefix like: `prefix = /custom_prefix`
98 ## And set your prefix like: `prefix = /custom_prefix`
99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
100 ## to make your cookies only work on prefix url
100 ## to make your cookies only work on prefix url
101 [filter:proxy-prefix]
101 [filter:proxy-prefix]
102 use = egg:PasteDeploy#prefix
102 use = egg:PasteDeploy#prefix
103 prefix = /
103 prefix = /
104
104
105 [app:main]
105 [app:main]
106 use = egg:rhodecode-enterprise-ce
106 use = egg:rhodecode-enterprise-ce
107
107
108 ## enable proxy prefix middleware, defined above
108 ## enable proxy prefix middleware, defined above
109 #filter-with = proxy-prefix
109 #filter-with = proxy-prefix
110
110
111 # During development the we want to have the debug toolbar enabled
111 # During development the we want to have the debug toolbar enabled
112 pyramid.includes =
112 pyramid.includes =
113 pyramid_debugtoolbar
113 pyramid_debugtoolbar
114 rhodecode.lib.middleware.request_wrapper
114 rhodecode.lib.middleware.request_wrapper
115
115
116 pyramid.reload_templates = true
116 pyramid.reload_templates = true
117
117
118 debugtoolbar.hosts = 0.0.0.0/0
118 debugtoolbar.hosts = 0.0.0.0/0
119 debugtoolbar.exclude_prefixes =
119 debugtoolbar.exclude_prefixes =
120 /css
120 /css
121 /fonts
121 /fonts
122 /images
122 /images
123 /js
123 /js
124
124
125 ## RHODECODE PLUGINS ##
125 ## RHODECODE PLUGINS ##
126 rhodecode.includes =
126 rhodecode.includes =
127 rhodecode.api
127 rhodecode.api
128
128
129
129
130 # api prefix url
130 # api prefix url
131 rhodecode.api.url = /_admin/api
131 rhodecode.api.url = /_admin/api
132
132
133
133
134 ## END RHODECODE PLUGINS ##
134 ## END RHODECODE PLUGINS ##
135
135
136 ## encryption key used to encrypt social plugin tokens,
136 ## encryption key used to encrypt social plugin tokens,
137 ## remote_urls with credentials etc, if not set it defaults to
137 ## remote_urls with credentials etc, if not set it defaults to
138 ## `beaker.session.secret`
138 ## `beaker.session.secret`
139 #rhodecode.encrypted_values.secret =
139 #rhodecode.encrypted_values.secret =
140
140
141 ## decryption strict mode (enabled by default). It controls if decryption raises
141 ## decryption strict mode (enabled by default). It controls if decryption raises
142 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
142 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
143 #rhodecode.encrypted_values.strict = false
143 #rhodecode.encrypted_values.strict = false
144
144
145 ## return gzipped responses from Rhodecode (static files/application)
145 ## return gzipped responses from Rhodecode (static files/application)
146 gzip_responses = false
146 gzip_responses = false
147
147
148 ## autogenerate javascript routes file on startup
148 ## autogenerate javascript routes file on startup
149 generate_js_files = false
149 generate_js_files = false
150
150
151 ## Optional Languages
151 ## Optional Languages
152 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
152 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
153 lang = en
153 lang = en
154
154
155 ## perform a full repository scan on each server start, this should be
155 ## perform a full repository scan on each server start, this should be
156 ## set to false after first startup, to allow faster server restarts.
156 ## set to false after first startup, to allow faster server restarts.
157 startup.import_repos = false
157 startup.import_repos = false
158
158
159 ## Uncomment and set this path to use archive download cache.
159 ## Uncomment and set this path to use archive download cache.
160 ## Once enabled, generated archives will be cached at this location
160 ## Once enabled, generated archives will be cached at this location
161 ## and served from the cache during subsequent requests for the same archive of
161 ## and served from the cache during subsequent requests for the same archive of
162 ## the repository.
162 ## the repository.
163 #archive_cache_dir = /tmp/tarballcache
163 #archive_cache_dir = /tmp/tarballcache
164
164
165 ## URL at which the application is running. This is used for bootstraping
165 ## URL at which the application is running. This is used for bootstraping
166 ## requests in context when no web request is available. Used in ishell, or
166 ## requests in context when no web request is available. Used in ishell, or
167 ## SSH calls. Set this for events to receive proper url for SSH calls.
167 ## SSH calls. Set this for events to receive proper url for SSH calls.
168 app.base_url = http://rhodecode.local
168 app.base_url = http://rhodecode.local
169
169
170 ## change this to unique ID for security
170 ## change this to unique ID for security
171 app_instance_uuid = rc-production
171 app_instance_uuid = rc-production
172
172
173 ## cut off limit for large diffs (size in bytes). If overall diff size on
173 ## cut off limit for large diffs (size in bytes). If overall diff size on
174 ## commit, or pull request exceeds this limit this diff will be displayed
174 ## commit, or pull request exceeds this limit this diff will be displayed
175 ## partially. E.g 512000 == 512Kb
175 ## partially. E.g 512000 == 512Kb
176 cut_off_limit_diff = 512000
176 cut_off_limit_diff = 512000
177
177
178 ## cut off limit for large files inside diffs (size in bytes). Each individual
178 ## cut off limit for large files inside diffs (size in bytes). Each individual
179 ## file inside diff which exceeds this limit will be displayed partially.
179 ## file inside diff which exceeds this limit will be displayed partially.
180 ## E.g 128000 == 128Kb
180 ## E.g 128000 == 128Kb
181 cut_off_limit_file = 128000
181 cut_off_limit_file = 128000
182
182
183 ## use cache version of scm repo everywhere
183 ## use cache version of scm repo everywhere
184 vcs_full_cache = true
184 vcs_full_cache = true
185
185
186 ## force https in RhodeCode, fixes https redirects, assumes it's always https
186 ## force https in RhodeCode, fixes https redirects, assumes it's always https
187 ## Normally this is controlled by proper http flags sent from http server
187 ## Normally this is controlled by proper http flags sent from http server
188 force_https = false
188 force_https = false
189
189
190 ## use Strict-Transport-Security headers
190 ## use Strict-Transport-Security headers
191 use_htsts = false
191 use_htsts = false
192
192
193 ## git rev filter option, --all is the default filter, if you need to
193 ## git rev filter option, --all is the default filter, if you need to
194 ## hide all refs in changelog switch this to --branches --tags
194 ## hide all refs in changelog switch this to --branches --tags
195 git_rev_filter = --branches --tags
195 git_rev_filter = --branches --tags
196
196
197 # Set to true if your repos are exposed using the dumb protocol
197 # Set to true if your repos are exposed using the dumb protocol
198 git_update_server_info = false
198 git_update_server_info = false
199
199
200 ## RSS/ATOM feed options
200 ## RSS/ATOM feed options
201 rss_cut_off_limit = 256000
201 rss_cut_off_limit = 256000
202 rss_items_per_page = 10
202 rss_items_per_page = 10
203 rss_include_diff = false
203 rss_include_diff = false
204
204
205 ## gist URL alias, used to create nicer urls for gist. This should be an
205 ## gist URL alias, used to create nicer urls for gist. This should be an
206 ## url that does rewrites to _admin/gists/{gistid}.
206 ## url that does rewrites to _admin/gists/{gistid}.
207 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
207 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
208 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
208 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
209 gist_alias_url =
209 gist_alias_url =
210
210
211 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
211 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
212 ## used for access.
212 ## used for access.
213 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
213 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
214 ## came from the the logged in user who own this authentication token.
214 ## came from the the logged in user who own this authentication token.
215 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
215 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
216 ## authentication token. Such view would be only accessible when used together
216 ## authentication token. Such view would be only accessible when used together
217 ## with this authentication token
217 ## with this authentication token
218 ##
218 ##
219 ## list of all views can be found under `/_admin/permissions/auth_token_access`
219 ## list of all views can be found under `/_admin/permissions/auth_token_access`
220 ## The list should be "," separated and on a single line.
220 ## The list should be "," separated and on a single line.
221 ##
221 ##
222 ## Most common views to enable:
222 ## Most common views to enable:
223 # RepoCommitsView:repo_commit_download
223 # RepoCommitsView:repo_commit_download
224 # RepoCommitsView:repo_commit_patch
224 # RepoCommitsView:repo_commit_patch
225 # RepoCommitsView:repo_commit_raw
225 # RepoCommitsView:repo_commit_raw
226 # RepoCommitsView:repo_commit_raw@TOKEN
226 # RepoCommitsView:repo_commit_raw@TOKEN
227 # RepoFilesView:repo_files_diff
227 # RepoFilesView:repo_files_diff
228 # RepoFilesView:repo_archivefile
228 # RepoFilesView:repo_archivefile
229 # RepoFilesView:repo_file_raw
229 # RepoFilesView:repo_file_raw
230 # GistView:*
230 # GistView:*
231 api_access_controllers_whitelist =
231 api_access_controllers_whitelist =
232
232
233 ## default encoding used to convert from and to unicode
233 ## default encoding used to convert from and to unicode
234 ## can be also a comma separated list of encoding in case of mixed encodings
234 ## can be also a comma separated list of encoding in case of mixed encodings
235 default_encoding = UTF-8
235 default_encoding = UTF-8
236
236
237 ## instance-id prefix
237 ## instance-id prefix
238 ## a prefix key for this instance used for cache invalidation when running
238 ## a prefix key for this instance used for cache invalidation when running
239 ## multiple instances of rhodecode, make sure it's globally unique for
239 ## multiple instances of rhodecode, make sure it's globally unique for
240 ## all running rhodecode instances. Leave empty if you don't use it
240 ## all running rhodecode instances. Leave empty if you don't use it
241 instance_id =
241 instance_id =
242
242
243 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
243 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
244 ## of an authentication plugin also if it is disabled by it's settings.
244 ## of an authentication plugin also if it is disabled by it's settings.
245 ## This could be useful if you are unable to log in to the system due to broken
245 ## This could be useful if you are unable to log in to the system due to broken
246 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
246 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
247 ## module to log in again and fix the settings.
247 ## module to log in again and fix the settings.
248 ##
248 ##
249 ## Available builtin plugin IDs (hash is part of the ID):
249 ## Available builtin plugin IDs (hash is part of the ID):
250 ## egg:rhodecode-enterprise-ce#rhodecode
250 ## egg:rhodecode-enterprise-ce#rhodecode
251 ## egg:rhodecode-enterprise-ce#pam
251 ## egg:rhodecode-enterprise-ce#pam
252 ## egg:rhodecode-enterprise-ce#ldap
252 ## egg:rhodecode-enterprise-ce#ldap
253 ## egg:rhodecode-enterprise-ce#jasig_cas
253 ## egg:rhodecode-enterprise-ce#jasig_cas
254 ## egg:rhodecode-enterprise-ce#headers
254 ## egg:rhodecode-enterprise-ce#headers
255 ## egg:rhodecode-enterprise-ce#crowd
255 ## egg:rhodecode-enterprise-ce#crowd
256 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
256 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
257
257
258 ## alternative return HTTP header for failed authentication. Default HTTP
258 ## alternative return HTTP header for failed authentication. Default HTTP
259 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
259 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
260 ## handling that causing a series of failed authentication calls.
260 ## handling that causing a series of failed authentication calls.
261 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
261 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
262 ## This will be served instead of default 401 on bad authnetication
262 ## This will be served instead of default 401 on bad authnetication
263 auth_ret_code =
263 auth_ret_code =
264
264
265 ## use special detection method when serving auth_ret_code, instead of serving
265 ## use special detection method when serving auth_ret_code, instead of serving
266 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
266 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
267 ## and then serve auth_ret_code to clients
267 ## and then serve auth_ret_code to clients
268 auth_ret_code_detection = false
268 auth_ret_code_detection = false
269
269
270 ## locking return code. When repository is locked return this HTTP code. 2XX
270 ## locking return code. When repository is locked return this HTTP code. 2XX
271 ## codes don't break the transactions while 4XX codes do
271 ## codes don't break the transactions while 4XX codes do
272 lock_ret_code = 423
272 lock_ret_code = 423
273
273
274 ## allows to change the repository location in settings page
274 ## allows to change the repository location in settings page
275 allow_repo_location_change = true
275 allow_repo_location_change = true
276
276
277 ## allows to setup custom hooks in settings page
277 ## allows to setup custom hooks in settings page
278 allow_custom_hooks_settings = true
278 allow_custom_hooks_settings = true
279
279
280 ## generated license token, goto license page in RhodeCode settings to obtain
280 ## generated license token, goto license page in RhodeCode settings to obtain
281 ## new token
281 ## new token
282 license_token =
282 license_token =
283
283
284 ## supervisor connection uri, for managing supervisor and logs.
284 ## supervisor connection uri, for managing supervisor and logs.
285 supervisor.uri =
285 supervisor.uri =
286 ## supervisord group name/id we only want this RC instance to handle
286 ## supervisord group name/id we only want this RC instance to handle
287 supervisor.group_id = dev
287 supervisor.group_id = dev
288
288
289 ## Display extended labs settings
289 ## Display extended labs settings
290 labs_settings_active = true
290 labs_settings_active = true
291
291
292 ####################################
292 ####################################
293 ### CELERY CONFIG ####
293 ### CELERY CONFIG ####
294 ####################################
294 ####################################
295 ## run: /path/to/celery worker \
295 ## run: /path/to/celery worker \
296 ## -E --beat --app rhodecode.lib.celerylib.loader \
296 ## -E --beat --app rhodecode.lib.celerylib.loader \
297 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
297 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
298 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
298 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
299
299
300 use_celery = false
300 use_celery = false
301
301
302 ## connection url to the message broker (default rabbitmq)
302 ## connection url to the message broker (default rabbitmq)
303 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
303 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
304
304
305 ## maximum tasks to execute before worker restart
305 ## maximum tasks to execute before worker restart
306 celery.max_tasks_per_child = 100
306 celery.max_tasks_per_child = 100
307
307
308 ## tasks will never be sent to the queue, but executed locally instead.
308 ## tasks will never be sent to the queue, but executed locally instead.
309 celery.task_always_eager = false
309 celery.task_always_eager = false
310
310
311 #####################################
311 #####################################
312 ### DOGPILE CACHE ####
312 ### DOGPILE CACHE ####
313 #####################################
313 #####################################
314 ## Default cache dir for caches. Putting this into a ramdisk
314 ## Default cache dir for caches. Putting this into a ramdisk
315 ## can boost performance, eg. /tmpfs/data_ramdisk, however this might require lots
315 ## can boost performance, eg. /tmpfs/data_ramdisk, however this might require lots
316 ## of space
316 ## of space
317 cache_dir = /tmp/rcdev/data
317 cache_dir = /tmp/rcdev/data
318
318
319 ## cache settings for permission tree, auth TTL.
319 ## cache settings for permission tree, auth TTL.
320 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
320 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
321 rc_cache.cache_perms.expiration_time = 300
321 rc_cache.cache_perms.expiration_time = 300
322 rc_cache.cache_perms.arguments.filename = /tmp/rc_cache_1
322 rc_cache.cache_perms.arguments.filename = /tmp/rc_cache_1
323
323
324 ## redis backend with distributed locks
324 ## redis backend with distributed locks
325 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
325 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
326 #rc_cache.cache_perms.expiration_time = 300
326 #rc_cache.cache_perms.expiration_time = 300
327 #rc_cache.cache_perms.arguments.host = localhost
327 #rc_cache.cache_perms.arguments.host = localhost
328 #rc_cache.cache_perms.arguments.port = 6379
328 #rc_cache.cache_perms.arguments.port = 6379
329 #rc_cache.cache_perms.arguments.db = 0
329 #rc_cache.cache_perms.arguments.db = 0
330 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
330 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
331 #rc_cache.cache_perms.arguments.distributed_lock = true
331 #rc_cache.cache_perms.arguments.distributed_lock = true
332
332
333
333
334 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
334 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
335 rc_cache.cache_repo.expiration_time = 2592000
335 rc_cache.cache_repo.expiration_time = 2592000
336 rc_cache.cache_repo.arguments.filename = /tmp/rc_cache_2
336 rc_cache.cache_repo.arguments.filename = /tmp/rc_cache_2
337
337
338 ## redis backend with distributed locks
338 ## redis backend with distributed locks
339 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
339 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
340 #rc_cache.cache_repo.expiration_time = 2592000
340 #rc_cache.cache_repo.expiration_time = 2592000
341 ## this needs to be greater then expiration_time
341 ## this needs to be greater then expiration_time
342 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
342 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
343 #rc_cache.cache_repo.arguments.host = localhost
343 #rc_cache.cache_repo.arguments.host = localhost
344 #rc_cache.cache_repo.arguments.port = 6379
344 #rc_cache.cache_repo.arguments.port = 6379
345 #rc_cache.cache_repo.arguments.db = 1
345 #rc_cache.cache_repo.arguments.db = 1
346 #rc_cache.cache_repo.arguments.distributed_lock = true
346 #rc_cache.cache_repo.arguments.distributed_lock = true
347
347
348 ## cache settings for SQL queries
348 ## cache settings for SQL queries
349 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
349 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
350 rc_cache.sql_cache_short.expiration_time = 30
350 rc_cache.sql_cache_short.expiration_time = 30
351
351
352
352
353 ####################################
353 ####################################
354 ### BEAKER CACHE ####
355 ####################################
356
357 ## locking and default file storage for Beaker. Putting this into a ramdisk
358 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
359 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
360 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
361
362 beaker.cache.regions = long_term
363
364 beaker.cache.long_term.type = memorylru_base
365 beaker.cache.long_term.expire = 172800
366 beaker.cache.long_term.key_length = 256
367
368
369 ####################################
370 ### BEAKER SESSION ####
354 ### BEAKER SESSION ####
371 ####################################
355 ####################################
372
356
373 ## .session.type is type of storage options for the session, current allowed
357 ## .session.type is type of storage options for the session, current allowed
374 ## types are file, ext:memcached, ext:redis, ext:database, and memory (default).
358 ## types are file, ext:memcached, ext:redis, ext:database, and memory (default).
375 beaker.session.type = file
359 beaker.session.type = file
376 beaker.session.data_dir = %(here)s/data/sessions
360 beaker.session.data_dir = %(here)s/data/sessions
377
361
378 ## db based session, fast, and allows easy management over logged in users
362 ## db based session, fast, and allows easy management over logged in users
379 #beaker.session.type = ext:database
363 #beaker.session.type = ext:database
380 #beaker.session.table_name = db_session
364 #beaker.session.table_name = db_session
381 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
365 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
382 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
366 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
383 #beaker.session.sa.pool_recycle = 3600
367 #beaker.session.sa.pool_recycle = 3600
384 #beaker.session.sa.echo = false
368 #beaker.session.sa.echo = false
385
369
386 beaker.session.key = rhodecode
370 beaker.session.key = rhodecode
387 beaker.session.secret = develop-rc-uytcxaz
371 beaker.session.secret = develop-rc-uytcxaz
388 beaker.session.lock_dir = %(here)s/data/sessions/lock
372 beaker.session.lock_dir = %(here)s/data/sessions/lock
389
373
390 ## Secure encrypted cookie. Requires AES and AES python libraries
374 ## Secure encrypted cookie. Requires AES and AES python libraries
391 ## you must disable beaker.session.secret to use this
375 ## you must disable beaker.session.secret to use this
392 #beaker.session.encrypt_key = key_for_encryption
376 #beaker.session.encrypt_key = key_for_encryption
393 #beaker.session.validate_key = validation_key
377 #beaker.session.validate_key = validation_key
394
378
395 ## sets session as invalid(also logging out user) if it haven not been
379 ## sets session as invalid(also logging out user) if it haven not been
396 ## accessed for given amount of time in seconds
380 ## accessed for given amount of time in seconds
397 beaker.session.timeout = 2592000
381 beaker.session.timeout = 2592000
398 beaker.session.httponly = true
382 beaker.session.httponly = true
399 ## Path to use for the cookie. Set to prefix if you use prefix middleware
383 ## Path to use for the cookie. Set to prefix if you use prefix middleware
400 #beaker.session.cookie_path = /custom_prefix
384 #beaker.session.cookie_path = /custom_prefix
401
385
402 ## uncomment for https secure cookie
386 ## uncomment for https secure cookie
403 beaker.session.secure = false
387 beaker.session.secure = false
404
388
405 ## auto save the session to not to use .save()
389 ## auto save the session to not to use .save()
406 beaker.session.auto = false
390 beaker.session.auto = false
407
391
408 ## default cookie expiration time in seconds, set to `true` to set expire
392 ## default cookie expiration time in seconds, set to `true` to set expire
409 ## at browser close
393 ## at browser close
410 #beaker.session.cookie_expires = 3600
394 #beaker.session.cookie_expires = 3600
411
395
412 ###################################
396 ###################################
413 ## SEARCH INDEXING CONFIGURATION ##
397 ## SEARCH INDEXING CONFIGURATION ##
414 ###################################
398 ###################################
415 ## Full text search indexer is available in rhodecode-tools under
399 ## Full text search indexer is available in rhodecode-tools under
416 ## `rhodecode-tools index` command
400 ## `rhodecode-tools index` command
417
401
418 ## WHOOSH Backend, doesn't require additional services to run
402 ## WHOOSH Backend, doesn't require additional services to run
419 ## it works good with few dozen repos
403 ## it works good with few dozen repos
420 search.module = rhodecode.lib.index.whoosh
404 search.module = rhodecode.lib.index.whoosh
421 search.location = %(here)s/data/index
405 search.location = %(here)s/data/index
422
406
423 ########################################
407 ########################################
424 ### CHANNELSTREAM CONFIG ####
408 ### CHANNELSTREAM CONFIG ####
425 ########################################
409 ########################################
426 ## channelstream enables persistent connections and live notification
410 ## channelstream enables persistent connections and live notification
427 ## in the system. It's also used by the chat system
411 ## in the system. It's also used by the chat system
428 channelstream.enabled = false
412 channelstream.enabled = false
429
413
430 ## server address for channelstream server on the backend
414 ## server address for channelstream server on the backend
431 channelstream.server = 127.0.0.1:9800
415 channelstream.server = 127.0.0.1:9800
432
416
433 ## location of the channelstream server from outside world
417 ## location of the channelstream server from outside world
434 ## use ws:// for http or wss:// for https. This address needs to be handled
418 ## use ws:// for http or wss:// for https. This address needs to be handled
435 ## by external HTTP server such as Nginx or Apache
419 ## by external HTTP server such as Nginx or Apache
436 ## see nginx/apache configuration examples in our docs
420 ## see nginx/apache configuration examples in our docs
437 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
421 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
438 channelstream.secret = secret
422 channelstream.secret = secret
439 channelstream.history.location = %(here)s/channelstream_history
423 channelstream.history.location = %(here)s/channelstream_history
440
424
441 ## Internal application path that Javascript uses to connect into.
425 ## Internal application path that Javascript uses to connect into.
442 ## If you use proxy-prefix the prefix should be added before /_channelstream
426 ## If you use proxy-prefix the prefix should be added before /_channelstream
443 channelstream.proxy_path = /_channelstream
427 channelstream.proxy_path = /_channelstream
444
428
445
429
446 ###################################
430 ###################################
447 ## APPENLIGHT CONFIG ##
431 ## APPENLIGHT CONFIG ##
448 ###################################
432 ###################################
449
433
450 ## Appenlight is tailored to work with RhodeCode, see
434 ## Appenlight is tailored to work with RhodeCode, see
451 ## http://appenlight.com for details how to obtain an account
435 ## http://appenlight.com for details how to obtain an account
452
436
453 ## appenlight integration enabled
437 ## appenlight integration enabled
454 appenlight = false
438 appenlight = false
455
439
456 appenlight.server_url = https://api.appenlight.com
440 appenlight.server_url = https://api.appenlight.com
457 appenlight.api_key = YOUR_API_KEY
441 appenlight.api_key = YOUR_API_KEY
458 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
442 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
459
443
460 # used for JS client
444 # used for JS client
461 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
445 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
462
446
463 ## TWEAK AMOUNT OF INFO SENT HERE
447 ## TWEAK AMOUNT OF INFO SENT HERE
464
448
465 ## enables 404 error logging (default False)
449 ## enables 404 error logging (default False)
466 appenlight.report_404 = false
450 appenlight.report_404 = false
467
451
468 ## time in seconds after request is considered being slow (default 1)
452 ## time in seconds after request is considered being slow (default 1)
469 appenlight.slow_request_time = 1
453 appenlight.slow_request_time = 1
470
454
471 ## record slow requests in application
455 ## record slow requests in application
472 ## (needs to be enabled for slow datastore recording and time tracking)
456 ## (needs to be enabled for slow datastore recording and time tracking)
473 appenlight.slow_requests = true
457 appenlight.slow_requests = true
474
458
475 ## enable hooking to application loggers
459 ## enable hooking to application loggers
476 appenlight.logging = true
460 appenlight.logging = true
477
461
478 ## minimum log level for log capture
462 ## minimum log level for log capture
479 appenlight.logging.level = WARNING
463 appenlight.logging.level = WARNING
480
464
481 ## send logs only from erroneous/slow requests
465 ## send logs only from erroneous/slow requests
482 ## (saves API quota for intensive logging)
466 ## (saves API quota for intensive logging)
483 appenlight.logging_on_error = false
467 appenlight.logging_on_error = false
484
468
485 ## list of additonal keywords that should be grabbed from environ object
469 ## list of additonal keywords that should be grabbed from environ object
486 ## can be string with comma separated list of words in lowercase
470 ## can be string with comma separated list of words in lowercase
487 ## (by default client will always send following info:
471 ## (by default client will always send following info:
488 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
472 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
489 ## start with HTTP* this list be extended with additional keywords here
473 ## start with HTTP* this list be extended with additional keywords here
490 appenlight.environ_keys_whitelist =
474 appenlight.environ_keys_whitelist =
491
475
492 ## list of keywords that should be blanked from request object
476 ## list of keywords that should be blanked from request object
493 ## can be string with comma separated list of words in lowercase
477 ## can be string with comma separated list of words in lowercase
494 ## (by default client will always blank keys that contain following words
478 ## (by default client will always blank keys that contain following words
495 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
479 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
496 ## this list be extended with additional keywords set here
480 ## this list be extended with additional keywords set here
497 appenlight.request_keys_blacklist =
481 appenlight.request_keys_blacklist =
498
482
499 ## list of namespaces that should be ignores when gathering log entries
483 ## list of namespaces that should be ignores when gathering log entries
500 ## can be string with comma separated list of namespaces
484 ## can be string with comma separated list of namespaces
501 ## (by default the client ignores own entries: appenlight_client.client)
485 ## (by default the client ignores own entries: appenlight_client.client)
502 appenlight.log_namespace_blacklist =
486 appenlight.log_namespace_blacklist =
503
487
504
488
505 ################################################################################
489 ################################################################################
506 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
490 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
507 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
491 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
508 ## execute malicious code after an exception is raised. ##
492 ## execute malicious code after an exception is raised. ##
509 ################################################################################
493 ################################################################################
510 #set debug = false
494 #set debug = false
511
495
512
496
513 ##############
497 ##############
514 ## STYLING ##
498 ## STYLING ##
515 ##############
499 ##############
516 debug_style = true
500 debug_style = true
517
501
518 ###########################################
502 ###########################################
519 ### MAIN RHODECODE DATABASE CONFIG ###
503 ### MAIN RHODECODE DATABASE CONFIG ###
520 ###########################################
504 ###########################################
521 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
505 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
522 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
506 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
523 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
507 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
524 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
508 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
525
509
526 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
510 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
527
511
528 # see sqlalchemy docs for other advanced settings
512 # see sqlalchemy docs for other advanced settings
529
513
530 ## print the sql statements to output
514 ## print the sql statements to output
531 sqlalchemy.db1.echo = false
515 sqlalchemy.db1.echo = false
532 ## recycle the connections after this amount of seconds
516 ## recycle the connections after this amount of seconds
533 sqlalchemy.db1.pool_recycle = 3600
517 sqlalchemy.db1.pool_recycle = 3600
534 sqlalchemy.db1.convert_unicode = true
518 sqlalchemy.db1.convert_unicode = true
535
519
536 ## the number of connections to keep open inside the connection pool.
520 ## the number of connections to keep open inside the connection pool.
537 ## 0 indicates no limit
521 ## 0 indicates no limit
538 #sqlalchemy.db1.pool_size = 5
522 #sqlalchemy.db1.pool_size = 5
539
523
540 ## the number of connections to allow in connection pool "overflow", that is
524 ## the number of connections to allow in connection pool "overflow", that is
541 ## connections that can be opened above and beyond the pool_size setting,
525 ## connections that can be opened above and beyond the pool_size setting,
542 ## which defaults to five.
526 ## which defaults to five.
543 #sqlalchemy.db1.max_overflow = 10
527 #sqlalchemy.db1.max_overflow = 10
544
528
545 ## Connection check ping, used to detect broken database connections
529 ## Connection check ping, used to detect broken database connections
546 ## could be enabled to better handle cases if MySQL has gone away errors
530 ## could be enabled to better handle cases if MySQL has gone away errors
547 #sqlalchemy.db1.ping_connection = true
531 #sqlalchemy.db1.ping_connection = true
548
532
549 ##################
533 ##################
550 ### VCS CONFIG ###
534 ### VCS CONFIG ###
551 ##################
535 ##################
552 vcs.server.enable = true
536 vcs.server.enable = true
553 vcs.server = localhost:9900
537 vcs.server = localhost:9900
554
538
555 ## Web server connectivity protocol, responsible for web based VCS operatations
539 ## Web server connectivity protocol, responsible for web based VCS operatations
556 ## Available protocols are:
540 ## Available protocols are:
557 ## `http` - use http-rpc backend (default)
541 ## `http` - use http-rpc backend (default)
558 vcs.server.protocol = http
542 vcs.server.protocol = http
559
543
560 ## Push/Pull operations protocol, available options are:
544 ## Push/Pull operations protocol, available options are:
561 ## `http` - use http-rpc backend (default)
545 ## `http` - use http-rpc backend (default)
562 ##
546 ##
563 vcs.scm_app_implementation = http
547 vcs.scm_app_implementation = http
564
548
565 ## Push/Pull operations hooks protocol, available options are:
549 ## Push/Pull operations hooks protocol, available options are:
566 ## `http` - use http-rpc backend (default)
550 ## `http` - use http-rpc backend (default)
567 vcs.hooks.protocol = http
551 vcs.hooks.protocol = http
568
552
569 ## Host on which this instance is listening for hooks. If vcsserver is in other location
553 ## Host on which this instance is listening for hooks. If vcsserver is in other location
570 ## this should be adjusted.
554 ## this should be adjusted.
571 vcs.hooks.host = 127.0.0.1
555 vcs.hooks.host = 127.0.0.1
572
556
573 vcs.server.log_level = debug
557 vcs.server.log_level = debug
574 ## Start VCSServer with this instance as a subprocess, usefull for development
558 ## Start VCSServer with this instance as a subprocess, usefull for development
575 vcs.start_server = false
559 vcs.start_server = false
576
560
577 ## List of enabled VCS backends, available options are:
561 ## List of enabled VCS backends, available options are:
578 ## `hg` - mercurial
562 ## `hg` - mercurial
579 ## `git` - git
563 ## `git` - git
580 ## `svn` - subversion
564 ## `svn` - subversion
581 vcs.backends = hg, git, svn
565 vcs.backends = hg, git, svn
582
566
583 vcs.connection_timeout = 3600
567 vcs.connection_timeout = 3600
584 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
568 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
585 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
569 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
586 #vcs.svn.compatible_version = pre-1.8-compatible
570 #vcs.svn.compatible_version = pre-1.8-compatible
587
571
588
572
589 ############################################################
573 ############################################################
590 ### Subversion proxy support (mod_dav_svn) ###
574 ### Subversion proxy support (mod_dav_svn) ###
591 ### Maps RhodeCode repo groups into SVN paths for Apache ###
575 ### Maps RhodeCode repo groups into SVN paths for Apache ###
592 ############################################################
576 ############################################################
593 ## Enable or disable the config file generation.
577 ## Enable or disable the config file generation.
594 svn.proxy.generate_config = false
578 svn.proxy.generate_config = false
595 ## Generate config file with `SVNListParentPath` set to `On`.
579 ## Generate config file with `SVNListParentPath` set to `On`.
596 svn.proxy.list_parent_path = true
580 svn.proxy.list_parent_path = true
597 ## Set location and file name of generated config file.
581 ## Set location and file name of generated config file.
598 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
582 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
599 ## alternative mod_dav config template. This needs to be a mako template
583 ## alternative mod_dav config template. This needs to be a mako template
600 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
584 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
601 ## Used as a prefix to the `Location` block in the generated config file.
585 ## Used as a prefix to the `Location` block in the generated config file.
602 ## In most cases it should be set to `/`.
586 ## In most cases it should be set to `/`.
603 svn.proxy.location_root = /
587 svn.proxy.location_root = /
604 ## Command to reload the mod dav svn configuration on change.
588 ## Command to reload the mod dav svn configuration on change.
605 ## Example: `/etc/init.d/apache2 reload`
589 ## Example: `/etc/init.d/apache2 reload`
606 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
590 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
607 ## If the timeout expires before the reload command finishes, the command will
591 ## If the timeout expires before the reload command finishes, the command will
608 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
592 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
609 #svn.proxy.reload_timeout = 10
593 #svn.proxy.reload_timeout = 10
610
594
611 ############################################################
595 ############################################################
612 ### SSH Support Settings ###
596 ### SSH Support Settings ###
613 ############################################################
597 ############################################################
614
598
615 ## Defines if a custom authorized_keys file should be created and written on
599 ## Defines if a custom authorized_keys file should be created and written on
616 ## any change user ssh keys. Setting this to false also disables posibility
600 ## any change user ssh keys. Setting this to false also disables posibility
617 ## of adding SSH keys by users from web interface. Super admins can still
601 ## of adding SSH keys by users from web interface. Super admins can still
618 ## manage SSH Keys.
602 ## manage SSH Keys.
619 ssh.generate_authorized_keyfile = false
603 ssh.generate_authorized_keyfile = false
620
604
621 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
605 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
622 # ssh.authorized_keys_ssh_opts =
606 # ssh.authorized_keys_ssh_opts =
623
607
624 ## Path to the authrozied_keys file where the generate entries are placed.
608 ## Path to the authrozied_keys file where the generate entries are placed.
625 ## It is possible to have multiple key files specified in `sshd_config` e.g.
609 ## It is possible to have multiple key files specified in `sshd_config` e.g.
626 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
610 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
627 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
611 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
628
612
629 ## Command to execute the SSH wrapper. The binary is available in the
613 ## Command to execute the SSH wrapper. The binary is available in the
630 ## rhodecode installation directory.
614 ## rhodecode installation directory.
631 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
615 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
632 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
616 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
633
617
634 ## Allow shell when executing the ssh-wrapper command
618 ## Allow shell when executing the ssh-wrapper command
635 ssh.wrapper_cmd_allow_shell = false
619 ssh.wrapper_cmd_allow_shell = false
636
620
637 ## Enables logging, and detailed output send back to the client during SSH
621 ## Enables logging, and detailed output send back to the client during SSH
638 ## operations. Usefull for debugging, shouldn't be used in production.
622 ## operations. Usefull for debugging, shouldn't be used in production.
639 ssh.enable_debug_logging = true
623 ssh.enable_debug_logging = true
640
624
641 ## Paths to binary executable, by default they are the names, but we can
625 ## Paths to binary executable, by default they are the names, but we can
642 ## override them if we want to use a custom one
626 ## override them if we want to use a custom one
643 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
627 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
644 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
628 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
645 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
629 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
646
630
647
631
648 ## Dummy marker to add new entries after.
632 ## Dummy marker to add new entries after.
649 ## Add any custom entries below. Please don't remove.
633 ## Add any custom entries below. Please don't remove.
650 custom.conf = 1
634 custom.conf = 1
651
635
652
636
653 ################################
637 ################################
654 ### LOGGING CONFIGURATION ####
638 ### LOGGING CONFIGURATION ####
655 ################################
639 ################################
656 [loggers]
640 [loggers]
657 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
641 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
658
642
659 [handlers]
643 [handlers]
660 keys = console, console_sql
644 keys = console, console_sql
661
645
662 [formatters]
646 [formatters]
663 keys = generic, color_formatter, color_formatter_sql
647 keys = generic, color_formatter, color_formatter_sql
664
648
665 #############
649 #############
666 ## LOGGERS ##
650 ## LOGGERS ##
667 #############
651 #############
668 [logger_root]
652 [logger_root]
669 level = NOTSET
653 level = NOTSET
670 handlers = console
654 handlers = console
671
655
672 [logger_sqlalchemy]
656 [logger_sqlalchemy]
673 level = INFO
657 level = INFO
674 handlers = console_sql
658 handlers = console_sql
675 qualname = sqlalchemy.engine
659 qualname = sqlalchemy.engine
676 propagate = 0
660 propagate = 0
677
661
678 [logger_beaker]
662 [logger_beaker]
679 level = DEBUG
663 level = DEBUG
680 handlers =
664 handlers =
681 qualname = beaker.container
665 qualname = beaker.container
682 propagate = 1
666 propagate = 1
683
667
684 [logger_rhodecode]
668 [logger_rhodecode]
685 level = DEBUG
669 level = DEBUG
686 handlers =
670 handlers =
687 qualname = rhodecode
671 qualname = rhodecode
688 propagate = 1
672 propagate = 1
689
673
690 [logger_ssh_wrapper]
674 [logger_ssh_wrapper]
691 level = DEBUG
675 level = DEBUG
692 handlers =
676 handlers =
693 qualname = ssh_wrapper
677 qualname = ssh_wrapper
694 propagate = 1
678 propagate = 1
695
679
696 [logger_celery]
680 [logger_celery]
697 level = DEBUG
681 level = DEBUG
698 handlers =
682 handlers =
699 qualname = celery
683 qualname = celery
700
684
701
685
702 ##############
686 ##############
703 ## HANDLERS ##
687 ## HANDLERS ##
704 ##############
688 ##############
705
689
706 [handler_console]
690 [handler_console]
707 class = StreamHandler
691 class = StreamHandler
708 args = (sys.stderr, )
692 args = (sys.stderr, )
709 level = DEBUG
693 level = DEBUG
710 formatter = color_formatter
694 formatter = color_formatter
711
695
712 [handler_console_sql]
696 [handler_console_sql]
713 # "level = DEBUG" logs SQL queries and results.
697 # "level = DEBUG" logs SQL queries and results.
714 # "level = INFO" logs SQL queries.
698 # "level = INFO" logs SQL queries.
715 # "level = WARN" logs neither. (Recommended for production systems.)
699 # "level = WARN" logs neither. (Recommended for production systems.)
716 class = StreamHandler
700 class = StreamHandler
717 args = (sys.stderr, )
701 args = (sys.stderr, )
718 level = WARN
702 level = WARN
719 formatter = color_formatter_sql
703 formatter = color_formatter_sql
720
704
721 ################
705 ################
722 ## FORMATTERS ##
706 ## FORMATTERS ##
723 ################
707 ################
724
708
725 [formatter_generic]
709 [formatter_generic]
726 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
710 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
727 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
711 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
728 datefmt = %Y-%m-%d %H:%M:%S
712 datefmt = %Y-%m-%d %H:%M:%S
729
713
730 [formatter_color_formatter]
714 [formatter_color_formatter]
731 class = rhodecode.lib.logging_formatter.ColorFormatter
715 class = rhodecode.lib.logging_formatter.ColorFormatter
732 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
716 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
733 datefmt = %Y-%m-%d %H:%M:%S
717 datefmt = %Y-%m-%d %H:%M:%S
734
718
735 [formatter_color_formatter_sql]
719 [formatter_color_formatter_sql]
736 class = rhodecode.lib.logging_formatter.ColorFormatterSql
720 class = rhodecode.lib.logging_formatter.ColorFormatterSql
737 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
721 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
738 datefmt = %Y-%m-%d %H:%M:%S
722 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,707 +1,691 b''
1
1
2
2
3 ################################################################################
3 ################################################################################
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10
10
11 ################################################################################
11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 ## EMAIL CONFIGURATION ##
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
19 #email_prefix = [RhodeCode]
20
20
21 ## email FROM address all mails will be sent
21 ## email FROM address all mails will be sent
22 #app_email_from = rhodecode-noreply@localhost
22 #app_email_from = rhodecode-noreply@localhost
23
23
24 ## Uncomment and replace with the address which should receive any error report
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
26 #email_to = admin@localhost
27
27
28 ## in case of Application errors, sent an error email form
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
29 #error_email_from = rhodecode_error@localhost
30
30
31 ## additional error message to be send in case of server crash
31 ## additional error message to be send in case of server crash
32 #error_message =
32 #error_message =
33
33
34
34
35 #smtp_server = mail.server.com
35 #smtp_server = mail.server.com
36 #smtp_username =
36 #smtp_username =
37 #smtp_password =
37 #smtp_password =
38 #smtp_port =
38 #smtp_port =
39 #smtp_use_tls = false
39 #smtp_use_tls = false
40 #smtp_use_ssl = true
40 #smtp_use_ssl = true
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 #smtp_auth =
42 #smtp_auth =
43
43
44 [server:main]
44 [server:main]
45 ## COMMON ##
45 ## COMMON ##
46 host = 127.0.0.1
46 host = 127.0.0.1
47 port = 5000
47 port = 5000
48
48
49 ##################################
49 ##################################
50 ## WAITRESS WSGI SERVER ##
50 ## WAITRESS WSGI SERVER ##
51 ## Recommended for Development ##
51 ## Recommended for Development ##
52 ##################################
52 ##################################
53
53
54 #use = egg:waitress#main
54 #use = egg:waitress#main
55 ## number of worker threads
55 ## number of worker threads
56 #threads = 5
56 #threads = 5
57 ## MAX BODY SIZE 100GB
57 ## MAX BODY SIZE 100GB
58 #max_request_body_size = 107374182400
58 #max_request_body_size = 107374182400
59 ## Use poll instead of select, fixes file descriptors limits problems.
59 ## Use poll instead of select, fixes file descriptors limits problems.
60 ## May not work on old windows systems.
60 ## May not work on old windows systems.
61 #asyncore_use_poll = true
61 #asyncore_use_poll = true
62
62
63
63
64 ##########################
64 ##########################
65 ## GUNICORN WSGI SERVER ##
65 ## GUNICORN WSGI SERVER ##
66 ##########################
66 ##########################
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
68
68
69 use = egg:gunicorn#main
69 use = egg:gunicorn#main
70 ## Sets the number of process workers. You must set `instance_id = *`
70 ## Sets the number of process workers. You must set `instance_id = *`
71 ## when this option is set to more than one worker, recommended
71 ## when this option is set to more than one worker, recommended
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
73 ## The `instance_id = *` must be set in the [app:main] section below
73 ## The `instance_id = *` must be set in the [app:main] section below
74 workers = 2
74 workers = 2
75 ## number of threads for each of the worker, must be set to 1 for gevent
75 ## number of threads for each of the worker, must be set to 1 for gevent
76 ## generally recommended to be at 1
76 ## generally recommended to be at 1
77 #threads = 1
77 #threads = 1
78 ## process name
78 ## process name
79 proc_name = rhodecode
79 proc_name = rhodecode
80 ## type of worker class, one of sync, gevent
80 ## type of worker class, one of sync, gevent
81 ## recommended for bigger setup is using of of other than sync one
81 ## recommended for bigger setup is using of of other than sync one
82 worker_class = gevent
82 worker_class = gevent
83 ## The maximum number of simultaneous clients. Valid only for Gevent
83 ## The maximum number of simultaneous clients. Valid only for Gevent
84 #worker_connections = 10
84 #worker_connections = 10
85 ## max number of requests that worker will handle before being gracefully
85 ## max number of requests that worker will handle before being gracefully
86 ## restarted, could prevent memory leaks
86 ## restarted, could prevent memory leaks
87 max_requests = 1000
87 max_requests = 1000
88 max_requests_jitter = 30
88 max_requests_jitter = 30
89 ## amount of time a worker can spend with handling a request before it
89 ## amount of time a worker can spend with handling a request before it
90 ## gets killed and restarted. Set to 6hrs
90 ## gets killed and restarted. Set to 6hrs
91 timeout = 21600
91 timeout = 21600
92
92
93
93
94 ## prefix middleware for RhodeCode.
94 ## prefix middleware for RhodeCode.
95 ## recommended when using proxy setup.
95 ## recommended when using proxy setup.
96 ## allows to set RhodeCode under a prefix in server.
96 ## allows to set RhodeCode under a prefix in server.
97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
97 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
98 ## And set your prefix like: `prefix = /custom_prefix`
98 ## And set your prefix like: `prefix = /custom_prefix`
99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
99 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
100 ## to make your cookies only work on prefix url
100 ## to make your cookies only work on prefix url
101 [filter:proxy-prefix]
101 [filter:proxy-prefix]
102 use = egg:PasteDeploy#prefix
102 use = egg:PasteDeploy#prefix
103 prefix = /
103 prefix = /
104
104
105 [app:main]
105 [app:main]
106 use = egg:rhodecode-enterprise-ce
106 use = egg:rhodecode-enterprise-ce
107
107
108 ## enable proxy prefix middleware, defined above
108 ## enable proxy prefix middleware, defined above
109 #filter-with = proxy-prefix
109 #filter-with = proxy-prefix
110
110
111 ## encryption key used to encrypt social plugin tokens,
111 ## encryption key used to encrypt social plugin tokens,
112 ## remote_urls with credentials etc, if not set it defaults to
112 ## remote_urls with credentials etc, if not set it defaults to
113 ## `beaker.session.secret`
113 ## `beaker.session.secret`
114 #rhodecode.encrypted_values.secret =
114 #rhodecode.encrypted_values.secret =
115
115
116 ## decryption strict mode (enabled by default). It controls if decryption raises
116 ## decryption strict mode (enabled by default). It controls if decryption raises
117 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
117 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
118 #rhodecode.encrypted_values.strict = false
118 #rhodecode.encrypted_values.strict = false
119
119
120 ## return gzipped responses from Rhodecode (static files/application)
120 ## return gzipped responses from Rhodecode (static files/application)
121 gzip_responses = false
121 gzip_responses = false
122
122
123 ## autogenerate javascript routes file on startup
123 ## autogenerate javascript routes file on startup
124 generate_js_files = false
124 generate_js_files = false
125
125
126 ## Optional Languages
126 ## Optional Languages
127 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
127 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
128 lang = en
128 lang = en
129
129
130 ## perform a full repository scan on each server start, this should be
130 ## perform a full repository scan on each server start, this should be
131 ## set to false after first startup, to allow faster server restarts.
131 ## set to false after first startup, to allow faster server restarts.
132 startup.import_repos = false
132 startup.import_repos = false
133
133
134 ## Uncomment and set this path to use archive download cache.
134 ## Uncomment and set this path to use archive download cache.
135 ## Once enabled, generated archives will be cached at this location
135 ## Once enabled, generated archives will be cached at this location
136 ## and served from the cache during subsequent requests for the same archive of
136 ## and served from the cache during subsequent requests for the same archive of
137 ## the repository.
137 ## the repository.
138 #archive_cache_dir = /tmp/tarballcache
138 #archive_cache_dir = /tmp/tarballcache
139
139
140 ## URL at which the application is running. This is used for bootstraping
140 ## URL at which the application is running. This is used for bootstraping
141 ## requests in context when no web request is available. Used in ishell, or
141 ## requests in context when no web request is available. Used in ishell, or
142 ## SSH calls. Set this for events to receive proper url for SSH calls.
142 ## SSH calls. Set this for events to receive proper url for SSH calls.
143 app.base_url = http://rhodecode.local
143 app.base_url = http://rhodecode.local
144
144
145 ## change this to unique ID for security
145 ## change this to unique ID for security
146 app_instance_uuid = rc-production
146 app_instance_uuid = rc-production
147
147
148 ## cut off limit for large diffs (size in bytes). If overall diff size on
148 ## cut off limit for large diffs (size in bytes). If overall diff size on
149 ## commit, or pull request exceeds this limit this diff will be displayed
149 ## commit, or pull request exceeds this limit this diff will be displayed
150 ## partially. E.g 512000 == 512Kb
150 ## partially. E.g 512000 == 512Kb
151 cut_off_limit_diff = 512000
151 cut_off_limit_diff = 512000
152
152
153 ## cut off limit for large files inside diffs (size in bytes). Each individual
153 ## cut off limit for large files inside diffs (size in bytes). Each individual
154 ## file inside diff which exceeds this limit will be displayed partially.
154 ## file inside diff which exceeds this limit will be displayed partially.
155 ## E.g 128000 == 128Kb
155 ## E.g 128000 == 128Kb
156 cut_off_limit_file = 128000
156 cut_off_limit_file = 128000
157
157
158 ## use cache version of scm repo everywhere
158 ## use cache version of scm repo everywhere
159 vcs_full_cache = true
159 vcs_full_cache = true
160
160
161 ## force https in RhodeCode, fixes https redirects, assumes it's always https
161 ## force https in RhodeCode, fixes https redirects, assumes it's always https
162 ## Normally this is controlled by proper http flags sent from http server
162 ## Normally this is controlled by proper http flags sent from http server
163 force_https = false
163 force_https = false
164
164
165 ## use Strict-Transport-Security headers
165 ## use Strict-Transport-Security headers
166 use_htsts = false
166 use_htsts = false
167
167
168 ## git rev filter option, --all is the default filter, if you need to
168 ## git rev filter option, --all is the default filter, if you need to
169 ## hide all refs in changelog switch this to --branches --tags
169 ## hide all refs in changelog switch this to --branches --tags
170 git_rev_filter = --branches --tags
170 git_rev_filter = --branches --tags
171
171
172 # Set to true if your repos are exposed using the dumb protocol
172 # Set to true if your repos are exposed using the dumb protocol
173 git_update_server_info = false
173 git_update_server_info = false
174
174
175 ## RSS/ATOM feed options
175 ## RSS/ATOM feed options
176 rss_cut_off_limit = 256000
176 rss_cut_off_limit = 256000
177 rss_items_per_page = 10
177 rss_items_per_page = 10
178 rss_include_diff = false
178 rss_include_diff = false
179
179
180 ## gist URL alias, used to create nicer urls for gist. This should be an
180 ## gist URL alias, used to create nicer urls for gist. This should be an
181 ## url that does rewrites to _admin/gists/{gistid}.
181 ## url that does rewrites to _admin/gists/{gistid}.
182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
184 gist_alias_url =
184 gist_alias_url =
185
185
186 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
186 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
187 ## used for access.
187 ## used for access.
188 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
188 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
189 ## came from the the logged in user who own this authentication token.
189 ## came from the the logged in user who own this authentication token.
190 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
190 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
191 ## authentication token. Such view would be only accessible when used together
191 ## authentication token. Such view would be only accessible when used together
192 ## with this authentication token
192 ## with this authentication token
193 ##
193 ##
194 ## list of all views can be found under `/_admin/permissions/auth_token_access`
194 ## list of all views can be found under `/_admin/permissions/auth_token_access`
195 ## The list should be "," separated and on a single line.
195 ## The list should be "," separated and on a single line.
196 ##
196 ##
197 ## Most common views to enable:
197 ## Most common views to enable:
198 # RepoCommitsView:repo_commit_download
198 # RepoCommitsView:repo_commit_download
199 # RepoCommitsView:repo_commit_patch
199 # RepoCommitsView:repo_commit_patch
200 # RepoCommitsView:repo_commit_raw
200 # RepoCommitsView:repo_commit_raw
201 # RepoCommitsView:repo_commit_raw@TOKEN
201 # RepoCommitsView:repo_commit_raw@TOKEN
202 # RepoFilesView:repo_files_diff
202 # RepoFilesView:repo_files_diff
203 # RepoFilesView:repo_archivefile
203 # RepoFilesView:repo_archivefile
204 # RepoFilesView:repo_file_raw
204 # RepoFilesView:repo_file_raw
205 # GistView:*
205 # GistView:*
206 api_access_controllers_whitelist =
206 api_access_controllers_whitelist =
207
207
208 ## default encoding used to convert from and to unicode
208 ## default encoding used to convert from and to unicode
209 ## can be also a comma separated list of encoding in case of mixed encodings
209 ## can be also a comma separated list of encoding in case of mixed encodings
210 default_encoding = UTF-8
210 default_encoding = UTF-8
211
211
212 ## instance-id prefix
212 ## instance-id prefix
213 ## a prefix key for this instance used for cache invalidation when running
213 ## a prefix key for this instance used for cache invalidation when running
214 ## multiple instances of rhodecode, make sure it's globally unique for
214 ## multiple instances of rhodecode, make sure it's globally unique for
215 ## all running rhodecode instances. Leave empty if you don't use it
215 ## all running rhodecode instances. Leave empty if you don't use it
216 instance_id =
216 instance_id =
217
217
218 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
218 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
219 ## of an authentication plugin also if it is disabled by it's settings.
219 ## of an authentication plugin also if it is disabled by it's settings.
220 ## This could be useful if you are unable to log in to the system due to broken
220 ## This could be useful if you are unable to log in to the system due to broken
221 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
221 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
222 ## module to log in again and fix the settings.
222 ## module to log in again and fix the settings.
223 ##
223 ##
224 ## Available builtin plugin IDs (hash is part of the ID):
224 ## Available builtin plugin IDs (hash is part of the ID):
225 ## egg:rhodecode-enterprise-ce#rhodecode
225 ## egg:rhodecode-enterprise-ce#rhodecode
226 ## egg:rhodecode-enterprise-ce#pam
226 ## egg:rhodecode-enterprise-ce#pam
227 ## egg:rhodecode-enterprise-ce#ldap
227 ## egg:rhodecode-enterprise-ce#ldap
228 ## egg:rhodecode-enterprise-ce#jasig_cas
228 ## egg:rhodecode-enterprise-ce#jasig_cas
229 ## egg:rhodecode-enterprise-ce#headers
229 ## egg:rhodecode-enterprise-ce#headers
230 ## egg:rhodecode-enterprise-ce#crowd
230 ## egg:rhodecode-enterprise-ce#crowd
231 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
231 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
232
232
233 ## alternative return HTTP header for failed authentication. Default HTTP
233 ## alternative return HTTP header for failed authentication. Default HTTP
234 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
234 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
235 ## handling that causing a series of failed authentication calls.
235 ## handling that causing a series of failed authentication calls.
236 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
236 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
237 ## This will be served instead of default 401 on bad authnetication
237 ## This will be served instead of default 401 on bad authnetication
238 auth_ret_code =
238 auth_ret_code =
239
239
240 ## use special detection method when serving auth_ret_code, instead of serving
240 ## use special detection method when serving auth_ret_code, instead of serving
241 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
241 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
242 ## and then serve auth_ret_code to clients
242 ## and then serve auth_ret_code to clients
243 auth_ret_code_detection = false
243 auth_ret_code_detection = false
244
244
245 ## locking return code. When repository is locked return this HTTP code. 2XX
245 ## locking return code. When repository is locked return this HTTP code. 2XX
246 ## codes don't break the transactions while 4XX codes do
246 ## codes don't break the transactions while 4XX codes do
247 lock_ret_code = 423
247 lock_ret_code = 423
248
248
249 ## allows to change the repository location in settings page
249 ## allows to change the repository location in settings page
250 allow_repo_location_change = true
250 allow_repo_location_change = true
251
251
252 ## allows to setup custom hooks in settings page
252 ## allows to setup custom hooks in settings page
253 allow_custom_hooks_settings = true
253 allow_custom_hooks_settings = true
254
254
255 ## generated license token, goto license page in RhodeCode settings to obtain
255 ## generated license token, goto license page in RhodeCode settings to obtain
256 ## new token
256 ## new token
257 license_token =
257 license_token =
258
258
259 ## supervisor connection uri, for managing supervisor and logs.
259 ## supervisor connection uri, for managing supervisor and logs.
260 supervisor.uri =
260 supervisor.uri =
261 ## supervisord group name/id we only want this RC instance to handle
261 ## supervisord group name/id we only want this RC instance to handle
262 supervisor.group_id = prod
262 supervisor.group_id = prod
263
263
264 ## Display extended labs settings
264 ## Display extended labs settings
265 labs_settings_active = true
265 labs_settings_active = true
266
266
267 ####################################
267 ####################################
268 ### CELERY CONFIG ####
268 ### CELERY CONFIG ####
269 ####################################
269 ####################################
270 ## run: /path/to/celery worker \
270 ## run: /path/to/celery worker \
271 ## -E --beat --app rhodecode.lib.celerylib.loader \
271 ## -E --beat --app rhodecode.lib.celerylib.loader \
272 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
272 ## --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
273 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
273 ## --loglevel DEBUG --ini /path/to/rhodecode.ini
274
274
275 use_celery = false
275 use_celery = false
276
276
277 ## connection url to the message broker (default rabbitmq)
277 ## connection url to the message broker (default rabbitmq)
278 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
278 celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
279
279
280 ## maximum tasks to execute before worker restart
280 ## maximum tasks to execute before worker restart
281 celery.max_tasks_per_child = 100
281 celery.max_tasks_per_child = 100
282
282
283 ## tasks will never be sent to the queue, but executed locally instead.
283 ## tasks will never be sent to the queue, but executed locally instead.
284 celery.task_always_eager = false
284 celery.task_always_eager = false
285
285
286 #####################################
286 #####################################
287 ### DOGPILE CACHE ####
287 ### DOGPILE CACHE ####
288 #####################################
288 #####################################
289 ## Default cache dir for caches. Putting this into a ramdisk
289 ## Default cache dir for caches. Putting this into a ramdisk
290 ## can boost performance, eg. /tmpfs/data_ramdisk, however this might require lots
290 ## can boost performance, eg. /tmpfs/data_ramdisk, however this might require lots
291 ## of space
291 ## of space
292 cache_dir = /tmp/rcdev/data
292 cache_dir = /tmp/rcdev/data
293
293
294 ## cache settings for permission tree, auth TTL.
294 ## cache settings for permission tree, auth TTL.
295 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
295 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
296 rc_cache.cache_perms.expiration_time = 300
296 rc_cache.cache_perms.expiration_time = 300
297 rc_cache.cache_perms.arguments.filename = /tmp/rc_cache_1
297 rc_cache.cache_perms.arguments.filename = /tmp/rc_cache_1
298
298
299 ## redis backend with distributed locks
299 ## redis backend with distributed locks
300 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
300 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
301 #rc_cache.cache_perms.expiration_time = 300
301 #rc_cache.cache_perms.expiration_time = 300
302 #rc_cache.cache_perms.arguments.host = localhost
302 #rc_cache.cache_perms.arguments.host = localhost
303 #rc_cache.cache_perms.arguments.port = 6379
303 #rc_cache.cache_perms.arguments.port = 6379
304 #rc_cache.cache_perms.arguments.db = 0
304 #rc_cache.cache_perms.arguments.db = 0
305 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
305 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
306 #rc_cache.cache_perms.arguments.distributed_lock = true
306 #rc_cache.cache_perms.arguments.distributed_lock = true
307
307
308
308
309 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
309 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
310 rc_cache.cache_repo.expiration_time = 2592000
310 rc_cache.cache_repo.expiration_time = 2592000
311 rc_cache.cache_repo.arguments.filename = /tmp/rc_cache_2
311 rc_cache.cache_repo.arguments.filename = /tmp/rc_cache_2
312
312
313 ## redis backend with distributed locks
313 ## redis backend with distributed locks
314 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
314 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
315 #rc_cache.cache_repo.expiration_time = 2592000
315 #rc_cache.cache_repo.expiration_time = 2592000
316 ## this needs to be greater then expiration_time
316 ## this needs to be greater then expiration_time
317 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
317 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
318 #rc_cache.cache_repo.arguments.host = localhost
318 #rc_cache.cache_repo.arguments.host = localhost
319 #rc_cache.cache_repo.arguments.port = 6379
319 #rc_cache.cache_repo.arguments.port = 6379
320 #rc_cache.cache_repo.arguments.db = 1
320 #rc_cache.cache_repo.arguments.db = 1
321 #rc_cache.cache_repo.arguments.distributed_lock = true
321 #rc_cache.cache_repo.arguments.distributed_lock = true
322
322
323 ## cache settings for SQL queries
323 ## cache settings for SQL queries
324 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
324 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
325 rc_cache.sql_cache_short.expiration_time = 30
325 rc_cache.sql_cache_short.expiration_time = 30
326
326
327
327
328 ####################################
328 ####################################
329 ### BEAKER CACHE ####
330 ####################################
331
332 ## locking and default file storage for Beaker. Putting this into a ramdisk
333 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
334 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
335 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
336
337 beaker.cache.regions = long_term
338
339 beaker.cache.long_term.type = memory
340 beaker.cache.long_term.expire = 172800
341 beaker.cache.long_term.key_length = 256
342
343
344 ####################################
345 ### BEAKER SESSION ####
329 ### BEAKER SESSION ####
346 ####################################
330 ####################################
347
331
348 ## .session.type is type of storage options for the session, current allowed
332 ## .session.type is type of storage options for the session, current allowed
349 ## types are file, ext:memcached, ext:redis, ext:database, and memory (default).
333 ## types are file, ext:memcached, ext:redis, ext:database, and memory (default).
350 beaker.session.type = file
334 beaker.session.type = file
351 beaker.session.data_dir = %(here)s/data/sessions
335 beaker.session.data_dir = %(here)s/data/sessions
352
336
353 ## db based session, fast, and allows easy management over logged in users
337 ## db based session, fast, and allows easy management over logged in users
354 #beaker.session.type = ext:database
338 #beaker.session.type = ext:database
355 #beaker.session.table_name = db_session
339 #beaker.session.table_name = db_session
356 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
340 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
357 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
341 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
358 #beaker.session.sa.pool_recycle = 3600
342 #beaker.session.sa.pool_recycle = 3600
359 #beaker.session.sa.echo = false
343 #beaker.session.sa.echo = false
360
344
361 beaker.session.key = rhodecode
345 beaker.session.key = rhodecode
362 beaker.session.secret = production-rc-uytcxaz
346 beaker.session.secret = production-rc-uytcxaz
363 beaker.session.lock_dir = %(here)s/data/sessions/lock
347 beaker.session.lock_dir = %(here)s/data/sessions/lock
364
348
365 ## Secure encrypted cookie. Requires AES and AES python libraries
349 ## Secure encrypted cookie. Requires AES and AES python libraries
366 ## you must disable beaker.session.secret to use this
350 ## you must disable beaker.session.secret to use this
367 #beaker.session.encrypt_key = key_for_encryption
351 #beaker.session.encrypt_key = key_for_encryption
368 #beaker.session.validate_key = validation_key
352 #beaker.session.validate_key = validation_key
369
353
370 ## sets session as invalid(also logging out user) if it haven not been
354 ## sets session as invalid(also logging out user) if it haven not been
371 ## accessed for given amount of time in seconds
355 ## accessed for given amount of time in seconds
372 beaker.session.timeout = 2592000
356 beaker.session.timeout = 2592000
373 beaker.session.httponly = true
357 beaker.session.httponly = true
374 ## Path to use for the cookie. Set to prefix if you use prefix middleware
358 ## Path to use for the cookie. Set to prefix if you use prefix middleware
375 #beaker.session.cookie_path = /custom_prefix
359 #beaker.session.cookie_path = /custom_prefix
376
360
377 ## uncomment for https secure cookie
361 ## uncomment for https secure cookie
378 beaker.session.secure = false
362 beaker.session.secure = false
379
363
380 ## auto save the session to not to use .save()
364 ## auto save the session to not to use .save()
381 beaker.session.auto = false
365 beaker.session.auto = false
382
366
383 ## default cookie expiration time in seconds, set to `true` to set expire
367 ## default cookie expiration time in seconds, set to `true` to set expire
384 ## at browser close
368 ## at browser close
385 #beaker.session.cookie_expires = 3600
369 #beaker.session.cookie_expires = 3600
386
370
387 ###################################
371 ###################################
388 ## SEARCH INDEXING CONFIGURATION ##
372 ## SEARCH INDEXING CONFIGURATION ##
389 ###################################
373 ###################################
390 ## Full text search indexer is available in rhodecode-tools under
374 ## Full text search indexer is available in rhodecode-tools under
391 ## `rhodecode-tools index` command
375 ## `rhodecode-tools index` command
392
376
393 ## WHOOSH Backend, doesn't require additional services to run
377 ## WHOOSH Backend, doesn't require additional services to run
394 ## it works good with few dozen repos
378 ## it works good with few dozen repos
395 search.module = rhodecode.lib.index.whoosh
379 search.module = rhodecode.lib.index.whoosh
396 search.location = %(here)s/data/index
380 search.location = %(here)s/data/index
397
381
398 ########################################
382 ########################################
399 ### CHANNELSTREAM CONFIG ####
383 ### CHANNELSTREAM CONFIG ####
400 ########################################
384 ########################################
401 ## channelstream enables persistent connections and live notification
385 ## channelstream enables persistent connections and live notification
402 ## in the system. It's also used by the chat system
386 ## in the system. It's also used by the chat system
403 channelstream.enabled = false
387 channelstream.enabled = false
404
388
405 ## server address for channelstream server on the backend
389 ## server address for channelstream server on the backend
406 channelstream.server = 127.0.0.1:9800
390 channelstream.server = 127.0.0.1:9800
407
391
408 ## location of the channelstream server from outside world
392 ## location of the channelstream server from outside world
409 ## use ws:// for http or wss:// for https. This address needs to be handled
393 ## use ws:// for http or wss:// for https. This address needs to be handled
410 ## by external HTTP server such as Nginx or Apache
394 ## by external HTTP server such as Nginx or Apache
411 ## see nginx/apache configuration examples in our docs
395 ## see nginx/apache configuration examples in our docs
412 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
396 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
413 channelstream.secret = secret
397 channelstream.secret = secret
414 channelstream.history.location = %(here)s/channelstream_history
398 channelstream.history.location = %(here)s/channelstream_history
415
399
416 ## Internal application path that Javascript uses to connect into.
400 ## Internal application path that Javascript uses to connect into.
417 ## If you use proxy-prefix the prefix should be added before /_channelstream
401 ## If you use proxy-prefix the prefix should be added before /_channelstream
418 channelstream.proxy_path = /_channelstream
402 channelstream.proxy_path = /_channelstream
419
403
420
404
421 ###################################
405 ###################################
422 ## APPENLIGHT CONFIG ##
406 ## APPENLIGHT CONFIG ##
423 ###################################
407 ###################################
424
408
425 ## Appenlight is tailored to work with RhodeCode, see
409 ## Appenlight is tailored to work with RhodeCode, see
426 ## http://appenlight.com for details how to obtain an account
410 ## http://appenlight.com for details how to obtain an account
427
411
428 ## appenlight integration enabled
412 ## appenlight integration enabled
429 appenlight = false
413 appenlight = false
430
414
431 appenlight.server_url = https://api.appenlight.com
415 appenlight.server_url = https://api.appenlight.com
432 appenlight.api_key = YOUR_API_KEY
416 appenlight.api_key = YOUR_API_KEY
433 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
417 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
434
418
435 # used for JS client
419 # used for JS client
436 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
420 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
437
421
438 ## TWEAK AMOUNT OF INFO SENT HERE
422 ## TWEAK AMOUNT OF INFO SENT HERE
439
423
440 ## enables 404 error logging (default False)
424 ## enables 404 error logging (default False)
441 appenlight.report_404 = false
425 appenlight.report_404 = false
442
426
443 ## time in seconds after request is considered being slow (default 1)
427 ## time in seconds after request is considered being slow (default 1)
444 appenlight.slow_request_time = 1
428 appenlight.slow_request_time = 1
445
429
446 ## record slow requests in application
430 ## record slow requests in application
447 ## (needs to be enabled for slow datastore recording and time tracking)
431 ## (needs to be enabled for slow datastore recording and time tracking)
448 appenlight.slow_requests = true
432 appenlight.slow_requests = true
449
433
450 ## enable hooking to application loggers
434 ## enable hooking to application loggers
451 appenlight.logging = true
435 appenlight.logging = true
452
436
453 ## minimum log level for log capture
437 ## minimum log level for log capture
454 appenlight.logging.level = WARNING
438 appenlight.logging.level = WARNING
455
439
456 ## send logs only from erroneous/slow requests
440 ## send logs only from erroneous/slow requests
457 ## (saves API quota for intensive logging)
441 ## (saves API quota for intensive logging)
458 appenlight.logging_on_error = false
442 appenlight.logging_on_error = false
459
443
460 ## list of additonal keywords that should be grabbed from environ object
444 ## list of additonal keywords that should be grabbed from environ object
461 ## can be string with comma separated list of words in lowercase
445 ## can be string with comma separated list of words in lowercase
462 ## (by default client will always send following info:
446 ## (by default client will always send following info:
463 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
447 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
464 ## start with HTTP* this list be extended with additional keywords here
448 ## start with HTTP* this list be extended with additional keywords here
465 appenlight.environ_keys_whitelist =
449 appenlight.environ_keys_whitelist =
466
450
467 ## list of keywords that should be blanked from request object
451 ## list of keywords that should be blanked from request object
468 ## can be string with comma separated list of words in lowercase
452 ## can be string with comma separated list of words in lowercase
469 ## (by default client will always blank keys that contain following words
453 ## (by default client will always blank keys that contain following words
470 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
454 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
471 ## this list be extended with additional keywords set here
455 ## this list be extended with additional keywords set here
472 appenlight.request_keys_blacklist =
456 appenlight.request_keys_blacklist =
473
457
474 ## list of namespaces that should be ignores when gathering log entries
458 ## list of namespaces that should be ignores when gathering log entries
475 ## can be string with comma separated list of namespaces
459 ## can be string with comma separated list of namespaces
476 ## (by default the client ignores own entries: appenlight_client.client)
460 ## (by default the client ignores own entries: appenlight_client.client)
477 appenlight.log_namespace_blacklist =
461 appenlight.log_namespace_blacklist =
478
462
479
463
480 ################################################################################
464 ################################################################################
481 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
465 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
482 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
466 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
483 ## execute malicious code after an exception is raised. ##
467 ## execute malicious code after an exception is raised. ##
484 ################################################################################
468 ################################################################################
485 set debug = false
469 set debug = false
486
470
487
471
488 ###########################################
472 ###########################################
489 ### MAIN RHODECODE DATABASE CONFIG ###
473 ### MAIN RHODECODE DATABASE CONFIG ###
490 ###########################################
474 ###########################################
491 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
475 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
492 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
476 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
493 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
477 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
494 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
478 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
495
479
496 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
480 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
497
481
498 # see sqlalchemy docs for other advanced settings
482 # see sqlalchemy docs for other advanced settings
499
483
500 ## print the sql statements to output
484 ## print the sql statements to output
501 sqlalchemy.db1.echo = false
485 sqlalchemy.db1.echo = false
502 ## recycle the connections after this amount of seconds
486 ## recycle the connections after this amount of seconds
503 sqlalchemy.db1.pool_recycle = 3600
487 sqlalchemy.db1.pool_recycle = 3600
504 sqlalchemy.db1.convert_unicode = true
488 sqlalchemy.db1.convert_unicode = true
505
489
506 ## the number of connections to keep open inside the connection pool.
490 ## the number of connections to keep open inside the connection pool.
507 ## 0 indicates no limit
491 ## 0 indicates no limit
508 #sqlalchemy.db1.pool_size = 5
492 #sqlalchemy.db1.pool_size = 5
509
493
510 ## the number of connections to allow in connection pool "overflow", that is
494 ## the number of connections to allow in connection pool "overflow", that is
511 ## connections that can be opened above and beyond the pool_size setting,
495 ## connections that can be opened above and beyond the pool_size setting,
512 ## which defaults to five.
496 ## which defaults to five.
513 #sqlalchemy.db1.max_overflow = 10
497 #sqlalchemy.db1.max_overflow = 10
514
498
515 ## Connection check ping, used to detect broken database connections
499 ## Connection check ping, used to detect broken database connections
516 ## could be enabled to better handle cases if MySQL has gone away errors
500 ## could be enabled to better handle cases if MySQL has gone away errors
517 #sqlalchemy.db1.ping_connection = true
501 #sqlalchemy.db1.ping_connection = true
518
502
519 ##################
503 ##################
520 ### VCS CONFIG ###
504 ### VCS CONFIG ###
521 ##################
505 ##################
522 vcs.server.enable = true
506 vcs.server.enable = true
523 vcs.server = localhost:9900
507 vcs.server = localhost:9900
524
508
525 ## Web server connectivity protocol, responsible for web based VCS operatations
509 ## Web server connectivity protocol, responsible for web based VCS operatations
526 ## Available protocols are:
510 ## Available protocols are:
527 ## `http` - use http-rpc backend (default)
511 ## `http` - use http-rpc backend (default)
528 vcs.server.protocol = http
512 vcs.server.protocol = http
529
513
530 ## Push/Pull operations protocol, available options are:
514 ## Push/Pull operations protocol, available options are:
531 ## `http` - use http-rpc backend (default)
515 ## `http` - use http-rpc backend (default)
532 ##
516 ##
533 vcs.scm_app_implementation = http
517 vcs.scm_app_implementation = http
534
518
535 ## Push/Pull operations hooks protocol, available options are:
519 ## Push/Pull operations hooks protocol, available options are:
536 ## `http` - use http-rpc backend (default)
520 ## `http` - use http-rpc backend (default)
537 vcs.hooks.protocol = http
521 vcs.hooks.protocol = http
538 ## Host on which this instance is listening for hooks. If vcsserver is in other location
522 ## Host on which this instance is listening for hooks. If vcsserver is in other location
539 ## this should be adjusted.
523 ## this should be adjusted.
540 vcs.hooks.host = 127.0.0.1
524 vcs.hooks.host = 127.0.0.1
541
525
542 vcs.server.log_level = info
526 vcs.server.log_level = info
543 ## Start VCSServer with this instance as a subprocess, usefull for development
527 ## Start VCSServer with this instance as a subprocess, usefull for development
544 vcs.start_server = false
528 vcs.start_server = false
545
529
546 ## List of enabled VCS backends, available options are:
530 ## List of enabled VCS backends, available options are:
547 ## `hg` - mercurial
531 ## `hg` - mercurial
548 ## `git` - git
532 ## `git` - git
549 ## `svn` - subversion
533 ## `svn` - subversion
550 vcs.backends = hg, git, svn
534 vcs.backends = hg, git, svn
551
535
552 vcs.connection_timeout = 3600
536 vcs.connection_timeout = 3600
553 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
537 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
554 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
538 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
555 #vcs.svn.compatible_version = pre-1.8-compatible
539 #vcs.svn.compatible_version = pre-1.8-compatible
556
540
557
541
558 ############################################################
542 ############################################################
559 ### Subversion proxy support (mod_dav_svn) ###
543 ### Subversion proxy support (mod_dav_svn) ###
560 ### Maps RhodeCode repo groups into SVN paths for Apache ###
544 ### Maps RhodeCode repo groups into SVN paths for Apache ###
561 ############################################################
545 ############################################################
562 ## Enable or disable the config file generation.
546 ## Enable or disable the config file generation.
563 svn.proxy.generate_config = false
547 svn.proxy.generate_config = false
564 ## Generate config file with `SVNListParentPath` set to `On`.
548 ## Generate config file with `SVNListParentPath` set to `On`.
565 svn.proxy.list_parent_path = true
549 svn.proxy.list_parent_path = true
566 ## Set location and file name of generated config file.
550 ## Set location and file name of generated config file.
567 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
551 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
568 ## alternative mod_dav config template. This needs to be a mako template
552 ## alternative mod_dav config template. This needs to be a mako template
569 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
553 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
570 ## Used as a prefix to the `Location` block in the generated config file.
554 ## Used as a prefix to the `Location` block in the generated config file.
571 ## In most cases it should be set to `/`.
555 ## In most cases it should be set to `/`.
572 svn.proxy.location_root = /
556 svn.proxy.location_root = /
573 ## Command to reload the mod dav svn configuration on change.
557 ## Command to reload the mod dav svn configuration on change.
574 ## Example: `/etc/init.d/apache2 reload`
558 ## Example: `/etc/init.d/apache2 reload`
575 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
559 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
576 ## If the timeout expires before the reload command finishes, the command will
560 ## If the timeout expires before the reload command finishes, the command will
577 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
561 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
578 #svn.proxy.reload_timeout = 10
562 #svn.proxy.reload_timeout = 10
579
563
580 ############################################################
564 ############################################################
581 ### SSH Support Settings ###
565 ### SSH Support Settings ###
582 ############################################################
566 ############################################################
583
567
584 ## Defines if a custom authorized_keys file should be created and written on
568 ## Defines if a custom authorized_keys file should be created and written on
585 ## any change user ssh keys. Setting this to false also disables posibility
569 ## any change user ssh keys. Setting this to false also disables posibility
586 ## of adding SSH keys by users from web interface. Super admins can still
570 ## of adding SSH keys by users from web interface. Super admins can still
587 ## manage SSH Keys.
571 ## manage SSH Keys.
588 ssh.generate_authorized_keyfile = false
572 ssh.generate_authorized_keyfile = false
589
573
590 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
574 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
591 # ssh.authorized_keys_ssh_opts =
575 # ssh.authorized_keys_ssh_opts =
592
576
593 ## Path to the authrozied_keys file where the generate entries are placed.
577 ## Path to the authrozied_keys file where the generate entries are placed.
594 ## It is possible to have multiple key files specified in `sshd_config` e.g.
578 ## It is possible to have multiple key files specified in `sshd_config` e.g.
595 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
579 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
596 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
580 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
597
581
598 ## Command to execute the SSH wrapper. The binary is available in the
582 ## Command to execute the SSH wrapper. The binary is available in the
599 ## rhodecode installation directory.
583 ## rhodecode installation directory.
600 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
584 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
601 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
585 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
602
586
603 ## Allow shell when executing the ssh-wrapper command
587 ## Allow shell when executing the ssh-wrapper command
604 ssh.wrapper_cmd_allow_shell = false
588 ssh.wrapper_cmd_allow_shell = false
605
589
606 ## Enables logging, and detailed output send back to the client during SSH
590 ## Enables logging, and detailed output send back to the client during SSH
607 ## operations. Usefull for debugging, shouldn't be used in production.
591 ## operations. Usefull for debugging, shouldn't be used in production.
608 ssh.enable_debug_logging = false
592 ssh.enable_debug_logging = false
609
593
610 ## Paths to binary executable, by default they are the names, but we can
594 ## Paths to binary executable, by default they are the names, but we can
611 ## override them if we want to use a custom one
595 ## override them if we want to use a custom one
612 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
596 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
613 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
597 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
614 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
598 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
615
599
616
600
617 ## Dummy marker to add new entries after.
601 ## Dummy marker to add new entries after.
618 ## Add any custom entries below. Please don't remove.
602 ## Add any custom entries below. Please don't remove.
619 custom.conf = 1
603 custom.conf = 1
620
604
621
605
622 ################################
606 ################################
623 ### LOGGING CONFIGURATION ####
607 ### LOGGING CONFIGURATION ####
624 ################################
608 ################################
625 [loggers]
609 [loggers]
626 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
610 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
627
611
628 [handlers]
612 [handlers]
629 keys = console, console_sql
613 keys = console, console_sql
630
614
631 [formatters]
615 [formatters]
632 keys = generic, color_formatter, color_formatter_sql
616 keys = generic, color_formatter, color_formatter_sql
633
617
634 #############
618 #############
635 ## LOGGERS ##
619 ## LOGGERS ##
636 #############
620 #############
637 [logger_root]
621 [logger_root]
638 level = NOTSET
622 level = NOTSET
639 handlers = console
623 handlers = console
640
624
641 [logger_sqlalchemy]
625 [logger_sqlalchemy]
642 level = INFO
626 level = INFO
643 handlers = console_sql
627 handlers = console_sql
644 qualname = sqlalchemy.engine
628 qualname = sqlalchemy.engine
645 propagate = 0
629 propagate = 0
646
630
647 [logger_beaker]
631 [logger_beaker]
648 level = DEBUG
632 level = DEBUG
649 handlers =
633 handlers =
650 qualname = beaker.container
634 qualname = beaker.container
651 propagate = 1
635 propagate = 1
652
636
653 [logger_rhodecode]
637 [logger_rhodecode]
654 level = DEBUG
638 level = DEBUG
655 handlers =
639 handlers =
656 qualname = rhodecode
640 qualname = rhodecode
657 propagate = 1
641 propagate = 1
658
642
659 [logger_ssh_wrapper]
643 [logger_ssh_wrapper]
660 level = DEBUG
644 level = DEBUG
661 handlers =
645 handlers =
662 qualname = ssh_wrapper
646 qualname = ssh_wrapper
663 propagate = 1
647 propagate = 1
664
648
665 [logger_celery]
649 [logger_celery]
666 level = DEBUG
650 level = DEBUG
667 handlers =
651 handlers =
668 qualname = celery
652 qualname = celery
669
653
670
654
671 ##############
655 ##############
672 ## HANDLERS ##
656 ## HANDLERS ##
673 ##############
657 ##############
674
658
675 [handler_console]
659 [handler_console]
676 class = StreamHandler
660 class = StreamHandler
677 args = (sys.stderr, )
661 args = (sys.stderr, )
678 level = INFO
662 level = INFO
679 formatter = generic
663 formatter = generic
680
664
681 [handler_console_sql]
665 [handler_console_sql]
682 # "level = DEBUG" logs SQL queries and results.
666 # "level = DEBUG" logs SQL queries and results.
683 # "level = INFO" logs SQL queries.
667 # "level = INFO" logs SQL queries.
684 # "level = WARN" logs neither. (Recommended for production systems.)
668 # "level = WARN" logs neither. (Recommended for production systems.)
685 class = StreamHandler
669 class = StreamHandler
686 args = (sys.stderr, )
670 args = (sys.stderr, )
687 level = WARN
671 level = WARN
688 formatter = generic
672 formatter = generic
689
673
690 ################
674 ################
691 ## FORMATTERS ##
675 ## FORMATTERS ##
692 ################
676 ################
693
677
694 [formatter_generic]
678 [formatter_generic]
695 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
679 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
696 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
680 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
697 datefmt = %Y-%m-%d %H:%M:%S
681 datefmt = %Y-%m-%d %H:%M:%S
698
682
699 [formatter_color_formatter]
683 [formatter_color_formatter]
700 class = rhodecode.lib.logging_formatter.ColorFormatter
684 class = rhodecode.lib.logging_formatter.ColorFormatter
701 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
685 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
702 datefmt = %Y-%m-%d %H:%M:%S
686 datefmt = %Y-%m-%d %H:%M:%S
703
687
704 [formatter_color_formatter_sql]
688 [formatter_color_formatter_sql]
705 class = rhodecode.lib.logging_formatter.ColorFormatterSql
689 class = rhodecode.lib.logging_formatter.ColorFormatterSql
706 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
690 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
707 datefmt = %Y-%m-%d %H:%M:%S
691 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,220 +1,244 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2018 RhodeCode GmbH
3 # Copyright (C) 2017-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20 import time
21 import pytz
21 import pytz
22 import logging
22 import logging
23
23
24 from beaker.cache import cache_region
25 from pyramid.view import view_config
24 from pyramid.view import view_config
26 from pyramid.response import Response
25 from pyramid.response import Response
27 from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed
26 from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed
28
27
29 from rhodecode.apps._base import RepoAppView
28 from rhodecode.apps._base import RepoAppView
30 from rhodecode.lib import audit_logger
29 from rhodecode.lib import audit_logger
30 from rhodecode.lib import rc_cache
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, HasRepoPermissionAnyDecorator)
33 LoginRequired, HasRepoPermissionAnyDecorator)
34 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
34 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
35 from rhodecode.lib.utils2 import str2bool, safe_int, md5_safe
35 from rhodecode.lib.utils2 import str2bool, safe_int, md5_safe
36 from rhodecode.model.db import UserApiKeys, CacheKey
36 from rhodecode.model.db import UserApiKeys, CacheKey
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class RepoFeedView(RepoAppView):
41 class RepoFeedView(RepoAppView):
42 def load_default_context(self):
42 def load_default_context(self):
43 c = self._get_local_tmpl_context()
43 c = self._get_local_tmpl_context()
44
44
45
45
46 self._load_defaults()
46 self._load_defaults()
47 return c
47 return c
48
48
49 def _get_config(self):
49 def _get_config(self):
50 import rhodecode
50 import rhodecode
51 config = rhodecode.CONFIG
51 config = rhodecode.CONFIG
52
52
53 return {
53 return {
54 'language': 'en-us',
54 'language': 'en-us',
55 'feed_ttl': '5', # TTL of feed,
55 'feed_ttl': '5', # TTL of feed,
56 'feed_include_diff':
56 'feed_include_diff':
57 str2bool(config.get('rss_include_diff', False)),
57 str2bool(config.get('rss_include_diff', False)),
58 'feed_items_per_page':
58 'feed_items_per_page':
59 safe_int(config.get('rss_items_per_page', 20)),
59 safe_int(config.get('rss_items_per_page', 20)),
60 'feed_diff_limit':
60 'feed_diff_limit':
61 # we need to protect from parsing huge diffs here other way
61 # we need to protect from parsing huge diffs here other way
62 # we can kill the server
62 # we can kill the server
63 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
63 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
64 }
64 }
65
65
66 def _load_defaults(self):
66 def _load_defaults(self):
67 _ = self.request.translate
67 _ = self.request.translate
68 config = self._get_config()
68 config = self._get_config()
69 # common values for feeds
69 # common values for feeds
70 self.description = _('Changes on %s repository')
70 self.description = _('Changes on %s repository')
71 self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s')
71 self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s')
72 self.language = config["language"]
72 self.language = config["language"]
73 self.ttl = config["feed_ttl"]
73 self.ttl = config["feed_ttl"]
74 self.feed_include_diff = config['feed_include_diff']
74 self.feed_include_diff = config['feed_include_diff']
75 self.feed_diff_limit = config['feed_diff_limit']
75 self.feed_diff_limit = config['feed_diff_limit']
76 self.feed_items_per_page = config['feed_items_per_page']
76 self.feed_items_per_page = config['feed_items_per_page']
77
77
78 def _changes(self, commit):
78 def _changes(self, commit):
79 diff_processor = DiffProcessor(
79 diff_processor = DiffProcessor(
80 commit.diff(), diff_limit=self.feed_diff_limit)
80 commit.diff(), diff_limit=self.feed_diff_limit)
81 _parsed = diff_processor.prepare(inline_diff=False)
81 _parsed = diff_processor.prepare(inline_diff=False)
82 limited_diff = isinstance(_parsed, LimitedDiffContainer)
82 limited_diff = isinstance(_parsed, LimitedDiffContainer)
83
83
84 return diff_processor, _parsed, limited_diff
84 return diff_processor, _parsed, limited_diff
85
85
86 def _get_title(self, commit):
86 def _get_title(self, commit):
87 return h.shorter(commit.message, 160)
87 return h.shorter(commit.message, 160)
88
88
89 def _get_description(self, commit):
89 def _get_description(self, commit):
90 _renderer = self.request.get_partial_renderer(
90 _renderer = self.request.get_partial_renderer(
91 'rhodecode:templates/feed/atom_feed_entry.mako')
91 'rhodecode:templates/feed/atom_feed_entry.mako')
92 diff_processor, parsed_diff, limited_diff = self._changes(commit)
92 diff_processor, parsed_diff, limited_diff = self._changes(commit)
93 filtered_parsed_diff, has_hidden_changes = self.path_filter.filter_patchset(parsed_diff)
93 filtered_parsed_diff, has_hidden_changes = self.path_filter.filter_patchset(parsed_diff)
94 return _renderer(
94 return _renderer(
95 'body',
95 'body',
96 commit=commit,
96 commit=commit,
97 parsed_diff=filtered_parsed_diff,
97 parsed_diff=filtered_parsed_diff,
98 limited_diff=limited_diff,
98 limited_diff=limited_diff,
99 feed_include_diff=self.feed_include_diff,
99 feed_include_diff=self.feed_include_diff,
100 diff_processor=diff_processor,
100 diff_processor=diff_processor,
101 has_hidden_changes=has_hidden_changes
101 has_hidden_changes=has_hidden_changes
102 )
102 )
103
103
104 def _set_timezone(self, date, tzinfo=pytz.utc):
104 def _set_timezone(self, date, tzinfo=pytz.utc):
105 if not getattr(date, "tzinfo", None):
105 if not getattr(date, "tzinfo", None):
106 date.replace(tzinfo=tzinfo)
106 date.replace(tzinfo=tzinfo)
107 return date
107 return date
108
108
109 def _get_commits(self):
109 def _get_commits(self):
110 return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:])
110 return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:])
111
111
112 def uid(self, repo_id, commit_id):
112 def uid(self, repo_id, commit_id):
113 return '{}:{}'.format(md5_safe(repo_id), md5_safe(commit_id))
113 return '{}:{}'.format(md5_safe(repo_id), md5_safe(commit_id))
114
114
115 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
115 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
116 @HasRepoPermissionAnyDecorator(
116 @HasRepoPermissionAnyDecorator(
117 'repository.read', 'repository.write', 'repository.admin')
117 'repository.read', 'repository.write', 'repository.admin')
118 @view_config(
118 @view_config(
119 route_name='atom_feed_home', request_method='GET',
119 route_name='atom_feed_home', request_method='GET',
120 renderer=None)
120 renderer=None)
121 def atom(self):
121 def atom(self):
122 """
122 """
123 Produce an atom-1.0 feed via feedgenerator module
123 Produce an atom-1.0 feed via feedgenerator module
124 """
124 """
125 self.load_default_context()
125 self.load_default_context()
126
126
127 def _generate_feed():
127 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
128 self.db_repo.repo_id, CacheKey.CACHE_TYPE_FEED)
129 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
130 repo_id=self.db_repo.repo_id)
131
132 region = rc_cache.get_or_create_region('cache_repo_longterm',
133 cache_namespace_uid)
134
135 condition = not self.path_filter.is_enabled
136
137 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
138 condition=condition)
139 def generate_atom_feed(repo_id, _repo_name, _feed_type):
128 feed = Atom1Feed(
140 feed = Atom1Feed(
129 title=self.title % self.db_repo_name,
141 title=self.title % _repo_name,
130 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
142 link=h.route_url('repo_summary', repo_name=_repo_name),
131 description=self.description % self.db_repo_name,
143 description=self.description % _repo_name,
132 language=self.language,
144 language=self.language,
133 ttl=self.ttl
145 ttl=self.ttl
134 )
146 )
135
147
136 for commit in reversed(self._get_commits()):
148 for commit in reversed(self._get_commits()):
137 date = self._set_timezone(commit.date)
149 date = self._set_timezone(commit.date)
138 feed.add_item(
150 feed.add_item(
139 unique_id=self.uid(self.db_repo.repo_id, commit.raw_id),
151 unique_id=self.uid(repo_id, commit.raw_id),
140 title=self._get_title(commit),
152 title=self._get_title(commit),
141 author_name=commit.author,
153 author_name=commit.author,
142 description=self._get_description(commit),
154 description=self._get_description(commit),
143 link=h.route_url(
155 link=h.route_url(
144 'repo_commit', repo_name=self.db_repo_name,
156 'repo_commit', repo_name=_repo_name,
145 commit_id=commit.raw_id),
157 commit_id=commit.raw_id),
146 pubdate=date,)
158 pubdate=date,)
147
159
148 return feed.mime_type, feed.writeString('utf-8')
160 return feed.mime_type, feed.writeString('utf-8')
149
161
150 @cache_region('long_term')
162 start = time.time()
151 def _generate_feed_and_cache(cache_key):
163 inv_context_manager = rc_cache.InvalidationContext(
152 return _generate_feed()
164 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
165 with inv_context_manager as invalidation_context:
166 # check for stored invalidation signal, and maybe purge the cache
167 # before computing it again
168 if invalidation_context.should_invalidate():
169 generate_atom_feed.invalidate(
170 self.db_repo.repo_id, self.db_repo.repo_name, 'atom')
153
171
154 if self.path_filter.is_enabled:
172 mime_type, feed = generate_atom_feed(
155 mime_type, feed = _generate_feed()
173 self.db_repo.repo_id, self.db_repo.repo_name, 'atom')
156 else:
174 compute_time = time.time() - start
157 invalidator_context = CacheKey.repo_context_cache(
175 log.debug('Repo ATOM feed computed in %.3fs', compute_time)
158 _generate_feed_and_cache, self.db_repo_name,
159 CacheKey.CACHE_TYPE_ATOM)
160 with invalidator_context as context:
161 context.invalidate()
162 mime_type, feed = context.compute()
163
176
164 response = Response(feed)
177 response = Response(feed)
165 response.content_type = mime_type
178 response.content_type = mime_type
166 return response
179 return response
167
180
168 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
181 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
169 @HasRepoPermissionAnyDecorator(
182 @HasRepoPermissionAnyDecorator(
170 'repository.read', 'repository.write', 'repository.admin')
183 'repository.read', 'repository.write', 'repository.admin')
171 @view_config(
184 @view_config(
172 route_name='rss_feed_home', request_method='GET',
185 route_name='rss_feed_home', request_method='GET',
173 renderer=None)
186 renderer=None)
174 def rss(self):
187 def rss(self):
175 """
188 """
176 Produce an rss2 feed via feedgenerator module
189 Produce an rss2 feed via feedgenerator module
177 """
190 """
178 self.load_default_context()
191 self.load_default_context()
179
192
180 def _generate_feed():
193 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
194 self.db_repo.repo_id, CacheKey.CACHE_TYPE_FEED)
195 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
196 repo_id=self.db_repo.repo_id)
197 region = rc_cache.get_or_create_region('cache_repo_longterm',
198 cache_namespace_uid)
199
200 condition = not self.path_filter.is_enabled
201
202 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
203 condition=condition)
204 def generate_rss_feed(repo_id, _repo_name, _feed_type):
181 feed = Rss201rev2Feed(
205 feed = Rss201rev2Feed(
182 title=self.title % self.db_repo_name,
206 title=self.title % _repo_name,
183 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
207 link=h.route_url('repo_summary', repo_name=_repo_name),
184 description=self.description % self.db_repo_name,
208 description=self.description % _repo_name,
185 language=self.language,
209 language=self.language,
186 ttl=self.ttl
210 ttl=self.ttl
187 )
211 )
188
212
189 for commit in reversed(self._get_commits()):
213 for commit in reversed(self._get_commits()):
190 date = self._set_timezone(commit.date)
214 date = self._set_timezone(commit.date)
191 feed.add_item(
215 feed.add_item(
192 unique_id=self.uid(self.db_repo.repo_id, commit.raw_id),
216 unique_id=self.uid(repo_id, commit.raw_id),
193 title=self._get_title(commit),
217 title=self._get_title(commit),
194 author_name=commit.author,
218 author_name=commit.author,
195 description=self._get_description(commit),
219 description=self._get_description(commit),
196 link=h.route_url(
220 link=h.route_url(
197 'repo_commit', repo_name=self.db_repo_name,
221 'repo_commit', repo_name=_repo_name,
198 commit_id=commit.raw_id),
222 commit_id=commit.raw_id),
199 pubdate=date,)
223 pubdate=date,)
200
224
201 return feed.mime_type, feed.writeString('utf-8')
225 return feed.mime_type, feed.writeString('utf-8')
202
226
203 @cache_region('long_term')
227 start = time.time()
204 def _generate_feed_and_cache(cache_key):
228 inv_context_manager = rc_cache.InvalidationContext(
205 return _generate_feed()
229 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
230 with inv_context_manager as invalidation_context:
231 # check for stored invalidation signal, and maybe purge the cache
232 # before computing it again
233 if invalidation_context.should_invalidate():
234 generate_rss_feed.invalidate(
235 self.db_repo.repo_id, self.db_repo.repo_name, 'rss')
206
236
207 if self.path_filter.is_enabled:
237 mime_type, feed = generate_rss_feed(
208 mime_type, feed = _generate_feed()
238 self.db_repo.repo_id, self.db_repo.repo_name, 'rss')
209 else:
239 compute_time = time.time() - start
210 invalidator_context = CacheKey.repo_context_cache(
240 log.debug('Repo RSS feed computed in %.3fs', compute_time)
211 _generate_feed_and_cache, self.db_repo_name,
212 CacheKey.CACHE_TYPE_RSS)
213
214 with invalidator_context as context:
215 context.invalidate()
216 mime_type, feed = context.compute()
217
241
218 response = Response(feed)
242 response = Response(feed)
219 response.content_type = mime_type
243 response.content_type = mime_type
220 return response
244 return response
@@ -1,1297 +1,1297 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import itertools
21 import itertools
22 import logging
22 import logging
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import collections
26 import collections
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.apps._base import RepoAppView
34 from rhodecode.apps._base import RepoAppView
35
35
36 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.controllers.utils import parse_path_ref
37 from rhodecode.lib import diffs, helpers as h, caches, rc_cache
37 from rhodecode.lib import diffs, helpers as h, rc_cache
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.exceptions import NonRelativePathError
39 from rhodecode.lib.exceptions import NonRelativePathError
40 from rhodecode.lib.codeblocks import (
40 from rhodecode.lib.codeblocks import (
41 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
42 from rhodecode.lib.utils2 import (
42 from rhodecode.lib.utils2 import (
43 convert_line_endings, detect_mode, safe_str, str2bool, safe_int)
43 convert_line_endings, detect_mode, safe_str, str2bool, safe_int)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
46 from rhodecode.lib.vcs import path as vcspath
46 from rhodecode.lib.vcs import path as vcspath
47 from rhodecode.lib.vcs.backends.base import EmptyCommit
47 from rhodecode.lib.vcs.backends.base import EmptyCommit
48 from rhodecode.lib.vcs.conf import settings
48 from rhodecode.lib.vcs.conf import settings
49 from rhodecode.lib.vcs.nodes import FileNode
49 from rhodecode.lib.vcs.nodes import FileNode
50 from rhodecode.lib.vcs.exceptions import (
50 from rhodecode.lib.vcs.exceptions import (
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 NodeDoesNotExistError, CommitError, NodeError)
53 NodeDoesNotExistError, CommitError, NodeError)
54
54
55 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.scm import ScmModel
56 from rhodecode.model.db import Repository
56 from rhodecode.model.db import Repository
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60
60
61 class RepoFilesView(RepoAppView):
61 class RepoFilesView(RepoAppView):
62
62
63 @staticmethod
63 @staticmethod
64 def adjust_file_path_for_svn(f_path, repo):
64 def adjust_file_path_for_svn(f_path, repo):
65 """
65 """
66 Computes the relative path of `f_path`.
66 Computes the relative path of `f_path`.
67
67
68 This is mainly based on prefix matching of the recognized tags and
68 This is mainly based on prefix matching of the recognized tags and
69 branches in the underlying repository.
69 branches in the underlying repository.
70 """
70 """
71 tags_and_branches = itertools.chain(
71 tags_and_branches = itertools.chain(
72 repo.branches.iterkeys(),
72 repo.branches.iterkeys(),
73 repo.tags.iterkeys())
73 repo.tags.iterkeys())
74 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
74 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
75
75
76 for name in tags_and_branches:
76 for name in tags_and_branches:
77 if f_path.startswith('{}/'.format(name)):
77 if f_path.startswith('{}/'.format(name)):
78 f_path = vcspath.relpath(f_path, name)
78 f_path = vcspath.relpath(f_path, name)
79 break
79 break
80 return f_path
80 return f_path
81
81
82 def load_default_context(self):
82 def load_default_context(self):
83 c = self._get_local_tmpl_context(include_app_defaults=True)
83 c = self._get_local_tmpl_context(include_app_defaults=True)
84 c.rhodecode_repo = self.rhodecode_vcs_repo
84 c.rhodecode_repo = self.rhodecode_vcs_repo
85 return c
85 return c
86
86
87 def _ensure_not_locked(self):
87 def _ensure_not_locked(self):
88 _ = self.request.translate
88 _ = self.request.translate
89
89
90 repo = self.db_repo
90 repo = self.db_repo
91 if repo.enable_locking and repo.locked[0]:
91 if repo.enable_locking and repo.locked[0]:
92 h.flash(_('This repository has been locked by %s on %s')
92 h.flash(_('This repository has been locked by %s on %s')
93 % (h.person_by_id(repo.locked[0]),
93 % (h.person_by_id(repo.locked[0]),
94 h.format_date(h.time_to_datetime(repo.locked[1]))),
94 h.format_date(h.time_to_datetime(repo.locked[1]))),
95 'warning')
95 'warning')
96 files_url = h.route_path(
96 files_url = h.route_path(
97 'repo_files:default_path',
97 'repo_files:default_path',
98 repo_name=self.db_repo_name, commit_id='tip')
98 repo_name=self.db_repo_name, commit_id='tip')
99 raise HTTPFound(files_url)
99 raise HTTPFound(files_url)
100
100
101 def _get_commit_and_path(self):
101 def _get_commit_and_path(self):
102 default_commit_id = self.db_repo.landing_rev[1]
102 default_commit_id = self.db_repo.landing_rev[1]
103 default_f_path = '/'
103 default_f_path = '/'
104
104
105 commit_id = self.request.matchdict.get(
105 commit_id = self.request.matchdict.get(
106 'commit_id', default_commit_id)
106 'commit_id', default_commit_id)
107 f_path = self._get_f_path(self.request.matchdict, default_f_path)
107 f_path = self._get_f_path(self.request.matchdict, default_f_path)
108 return commit_id, f_path
108 return commit_id, f_path
109
109
110 def _get_default_encoding(self, c):
110 def _get_default_encoding(self, c):
111 enc_list = getattr(c, 'default_encodings', [])
111 enc_list = getattr(c, 'default_encodings', [])
112 return enc_list[0] if enc_list else 'UTF-8'
112 return enc_list[0] if enc_list else 'UTF-8'
113
113
114 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
114 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
115 """
115 """
116 This is a safe way to get commit. If an error occurs it redirects to
116 This is a safe way to get commit. If an error occurs it redirects to
117 tip with proper message
117 tip with proper message
118
118
119 :param commit_id: id of commit to fetch
119 :param commit_id: id of commit to fetch
120 :param redirect_after: toggle redirection
120 :param redirect_after: toggle redirection
121 """
121 """
122 _ = self.request.translate
122 _ = self.request.translate
123
123
124 try:
124 try:
125 return self.rhodecode_vcs_repo.get_commit(commit_id)
125 return self.rhodecode_vcs_repo.get_commit(commit_id)
126 except EmptyRepositoryError:
126 except EmptyRepositoryError:
127 if not redirect_after:
127 if not redirect_after:
128 return None
128 return None
129
129
130 _url = h.route_path(
130 _url = h.route_path(
131 'repo_files_add_file',
131 'repo_files_add_file',
132 repo_name=self.db_repo_name, commit_id=0, f_path='',
132 repo_name=self.db_repo_name, commit_id=0, f_path='',
133 _anchor='edit')
133 _anchor='edit')
134
134
135 if h.HasRepoPermissionAny(
135 if h.HasRepoPermissionAny(
136 'repository.write', 'repository.admin')(self.db_repo_name):
136 'repository.write', 'repository.admin')(self.db_repo_name):
137 add_new = h.link_to(
137 add_new = h.link_to(
138 _('Click here to add a new file.'), _url, class_="alert-link")
138 _('Click here to add a new file.'), _url, class_="alert-link")
139 else:
139 else:
140 add_new = ""
140 add_new = ""
141
141
142 h.flash(h.literal(
142 h.flash(h.literal(
143 _('There are no files yet. %s') % add_new), category='warning')
143 _('There are no files yet. %s') % add_new), category='warning')
144 raise HTTPFound(
144 raise HTTPFound(
145 h.route_path('repo_summary', repo_name=self.db_repo_name))
145 h.route_path('repo_summary', repo_name=self.db_repo_name))
146
146
147 except (CommitDoesNotExistError, LookupError):
147 except (CommitDoesNotExistError, LookupError):
148 msg = _('No such commit exists for this repository')
148 msg = _('No such commit exists for this repository')
149 h.flash(msg, category='error')
149 h.flash(msg, category='error')
150 raise HTTPNotFound()
150 raise HTTPNotFound()
151 except RepositoryError as e:
151 except RepositoryError as e:
152 h.flash(safe_str(h.escape(e)), category='error')
152 h.flash(safe_str(h.escape(e)), category='error')
153 raise HTTPNotFound()
153 raise HTTPNotFound()
154
154
155 def _get_filenode_or_redirect(self, commit_obj, path):
155 def _get_filenode_or_redirect(self, commit_obj, path):
156 """
156 """
157 Returns file_node, if error occurs or given path is directory,
157 Returns file_node, if error occurs or given path is directory,
158 it'll redirect to top level path
158 it'll redirect to top level path
159 """
159 """
160 _ = self.request.translate
160 _ = self.request.translate
161
161
162 try:
162 try:
163 file_node = commit_obj.get_node(path)
163 file_node = commit_obj.get_node(path)
164 if file_node.is_dir():
164 if file_node.is_dir():
165 raise RepositoryError('The given path is a directory')
165 raise RepositoryError('The given path is a directory')
166 except CommitDoesNotExistError:
166 except CommitDoesNotExistError:
167 log.exception('No such commit exists for this repository')
167 log.exception('No such commit exists for this repository')
168 h.flash(_('No such commit exists for this repository'), category='error')
168 h.flash(_('No such commit exists for this repository'), category='error')
169 raise HTTPNotFound()
169 raise HTTPNotFound()
170 except RepositoryError as e:
170 except RepositoryError as e:
171 log.warning('Repository error while fetching '
171 log.warning('Repository error while fetching '
172 'filenode `%s`. Err:%s', path, e)
172 'filenode `%s`. Err:%s', path, e)
173 h.flash(safe_str(h.escape(e)), category='error')
173 h.flash(safe_str(h.escape(e)), category='error')
174 raise HTTPNotFound()
174 raise HTTPNotFound()
175
175
176 return file_node
176 return file_node
177
177
178 def _is_valid_head(self, commit_id, repo):
178 def _is_valid_head(self, commit_id, repo):
179 # check if commit is a branch identifier- basically we cannot
179 # check if commit is a branch identifier- basically we cannot
180 # create multiple heads via file editing
180 # create multiple heads via file editing
181 valid_heads = repo.branches.keys() + repo.branches.values()
181 valid_heads = repo.branches.keys() + repo.branches.values()
182
182
183 if h.is_svn(repo) and not repo.is_empty():
183 if h.is_svn(repo) and not repo.is_empty():
184 # Note: Subversion only has one head, we add it here in case there
184 # Note: Subversion only has one head, we add it here in case there
185 # is no branch matched.
185 # is no branch matched.
186 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
186 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
187
187
188 # check if commit is a branch name or branch hash
188 # check if commit is a branch name or branch hash
189 return commit_id in valid_heads
189 return commit_id in valid_heads
190
190
191 def _get_tree_at_commit(
191 def _get_tree_at_commit(
192 self, c, commit_id, f_path, full_load=False):
192 self, c, commit_id, f_path, full_load=False):
193
193
194 repo_id = self.db_repo.repo_id
194 repo_id = self.db_repo.repo_id
195
195
196 cache_seconds = safe_int(
196 cache_seconds = safe_int(
197 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
197 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
198 cache_on = cache_seconds > 0
198 cache_on = cache_seconds > 0
199 log.debug(
199 log.debug(
200 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
200 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
201 'with caching: %s[TTL: %ss]' % (
201 'with caching: %s[TTL: %ss]' % (
202 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
202 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
203
203
204 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
204 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
205 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
205 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
206
206
207 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
207 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
208 condition=cache_on)
208 condition=cache_on)
209 def compute_file_tree(repo_id, commit_id, f_path, full_load):
209 def compute_file_tree(repo_id, commit_id, f_path, full_load):
210 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
210 log.debug('Generating cached file tree for repo_id: %s, %s, %s',
211 repo_id, commit_id, f_path)
211 repo_id, commit_id, f_path)
212
212
213 c.full_load = full_load
213 c.full_load = full_load
214 return render(
214 return render(
215 'rhodecode:templates/files/files_browser_tree.mako',
215 'rhodecode:templates/files/files_browser_tree.mako',
216 self._get_template_context(c), self.request)
216 self._get_template_context(c), self.request)
217
217
218 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
218 return compute_file_tree(self.db_repo.repo_id, commit_id, f_path, full_load)
219
219
220 def _get_archive_spec(self, fname):
220 def _get_archive_spec(self, fname):
221 log.debug('Detecting archive spec for: `%s`', fname)
221 log.debug('Detecting archive spec for: `%s`', fname)
222
222
223 fileformat = None
223 fileformat = None
224 ext = None
224 ext = None
225 content_type = None
225 content_type = None
226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
226 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
227 content_type, extension = ext_data
227 content_type, extension = ext_data
228
228
229 if fname.endswith(extension):
229 if fname.endswith(extension):
230 fileformat = a_type
230 fileformat = a_type
231 log.debug('archive is of type: %s', fileformat)
231 log.debug('archive is of type: %s', fileformat)
232 ext = extension
232 ext = extension
233 break
233 break
234
234
235 if not fileformat:
235 if not fileformat:
236 raise ValueError()
236 raise ValueError()
237
237
238 # left over part of whole fname is the commit
238 # left over part of whole fname is the commit
239 commit_id = fname[:-len(ext)]
239 commit_id = fname[:-len(ext)]
240
240
241 return commit_id, ext, fileformat, content_type
241 return commit_id, ext, fileformat, content_type
242
242
243 @LoginRequired()
243 @LoginRequired()
244 @HasRepoPermissionAnyDecorator(
244 @HasRepoPermissionAnyDecorator(
245 'repository.read', 'repository.write', 'repository.admin')
245 'repository.read', 'repository.write', 'repository.admin')
246 @view_config(
246 @view_config(
247 route_name='repo_archivefile', request_method='GET',
247 route_name='repo_archivefile', request_method='GET',
248 renderer=None)
248 renderer=None)
249 def repo_archivefile(self):
249 def repo_archivefile(self):
250 # archive cache config
250 # archive cache config
251 from rhodecode import CONFIG
251 from rhodecode import CONFIG
252 _ = self.request.translate
252 _ = self.request.translate
253 self.load_default_context()
253 self.load_default_context()
254
254
255 fname = self.request.matchdict['fname']
255 fname = self.request.matchdict['fname']
256 subrepos = self.request.GET.get('subrepos') == 'true'
256 subrepos = self.request.GET.get('subrepos') == 'true'
257
257
258 if not self.db_repo.enable_downloads:
258 if not self.db_repo.enable_downloads:
259 return Response(_('Downloads disabled'))
259 return Response(_('Downloads disabled'))
260
260
261 try:
261 try:
262 commit_id, ext, fileformat, content_type = \
262 commit_id, ext, fileformat, content_type = \
263 self._get_archive_spec(fname)
263 self._get_archive_spec(fname)
264 except ValueError:
264 except ValueError:
265 return Response(_('Unknown archive type for: `{}`').format(
265 return Response(_('Unknown archive type for: `{}`').format(
266 h.escape(fname)))
266 h.escape(fname)))
267
267
268 try:
268 try:
269 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
269 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
270 except CommitDoesNotExistError:
270 except CommitDoesNotExistError:
271 return Response(_('Unknown commit_id {}').format(
271 return Response(_('Unknown commit_id {}').format(
272 h.escape(commit_id)))
272 h.escape(commit_id)))
273 except EmptyRepositoryError:
273 except EmptyRepositoryError:
274 return Response(_('Empty repository'))
274 return Response(_('Empty repository'))
275
275
276 archive_name = '%s-%s%s%s' % (
276 archive_name = '%s-%s%s%s' % (
277 safe_str(self.db_repo_name.replace('/', '_')),
277 safe_str(self.db_repo_name.replace('/', '_')),
278 '-sub' if subrepos else '',
278 '-sub' if subrepos else '',
279 safe_str(commit.short_id), ext)
279 safe_str(commit.short_id), ext)
280
280
281 use_cached_archive = False
281 use_cached_archive = False
282 archive_cache_enabled = CONFIG.get(
282 archive_cache_enabled = CONFIG.get(
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
283 'archive_cache_dir') and not self.request.GET.get('no_cache')
284
284
285 if archive_cache_enabled:
285 if archive_cache_enabled:
286 # check if we it's ok to write
286 # check if we it's ok to write
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
287 if not os.path.isdir(CONFIG['archive_cache_dir']):
288 os.makedirs(CONFIG['archive_cache_dir'])
288 os.makedirs(CONFIG['archive_cache_dir'])
289 cached_archive_path = os.path.join(
289 cached_archive_path = os.path.join(
290 CONFIG['archive_cache_dir'], archive_name)
290 CONFIG['archive_cache_dir'], archive_name)
291 if os.path.isfile(cached_archive_path):
291 if os.path.isfile(cached_archive_path):
292 log.debug('Found cached archive in %s', cached_archive_path)
292 log.debug('Found cached archive in %s', cached_archive_path)
293 fd, archive = None, cached_archive_path
293 fd, archive = None, cached_archive_path
294 use_cached_archive = True
294 use_cached_archive = True
295 else:
295 else:
296 log.debug('Archive %s is not yet cached', archive_name)
296 log.debug('Archive %s is not yet cached', archive_name)
297
297
298 if not use_cached_archive:
298 if not use_cached_archive:
299 # generate new archive
299 # generate new archive
300 fd, archive = tempfile.mkstemp()
300 fd, archive = tempfile.mkstemp()
301 log.debug('Creating new temp archive in %s', archive)
301 log.debug('Creating new temp archive in %s', archive)
302 try:
302 try:
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
303 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
304 except ImproperArchiveTypeError:
304 except ImproperArchiveTypeError:
305 return _('Unknown archive type')
305 return _('Unknown archive type')
306 if archive_cache_enabled:
306 if archive_cache_enabled:
307 # if we generated the archive and we have cache enabled
307 # if we generated the archive and we have cache enabled
308 # let's use this for future
308 # let's use this for future
309 log.debug('Storing new archive in %s', cached_archive_path)
309 log.debug('Storing new archive in %s', cached_archive_path)
310 shutil.move(archive, cached_archive_path)
310 shutil.move(archive, cached_archive_path)
311 archive = cached_archive_path
311 archive = cached_archive_path
312
312
313 # store download action
313 # store download action
314 audit_logger.store_web(
314 audit_logger.store_web(
315 'repo.archive.download', action_data={
315 'repo.archive.download', action_data={
316 'user_agent': self.request.user_agent,
316 'user_agent': self.request.user_agent,
317 'archive_name': archive_name,
317 'archive_name': archive_name,
318 'archive_spec': fname,
318 'archive_spec': fname,
319 'archive_cached': use_cached_archive},
319 'archive_cached': use_cached_archive},
320 user=self._rhodecode_user,
320 user=self._rhodecode_user,
321 repo=self.db_repo,
321 repo=self.db_repo,
322 commit=True
322 commit=True
323 )
323 )
324
324
325 def get_chunked_archive(archive):
325 def get_chunked_archive(archive):
326 with open(archive, 'rb') as stream:
326 with open(archive, 'rb') as stream:
327 while True:
327 while True:
328 data = stream.read(16 * 1024)
328 data = stream.read(16 * 1024)
329 if not data:
329 if not data:
330 if fd: # fd means we used temporary file
330 if fd: # fd means we used temporary file
331 os.close(fd)
331 os.close(fd)
332 if not archive_cache_enabled:
332 if not archive_cache_enabled:
333 log.debug('Destroying temp archive %s', archive)
333 log.debug('Destroying temp archive %s', archive)
334 os.remove(archive)
334 os.remove(archive)
335 break
335 break
336 yield data
336 yield data
337
337
338 response = Response(app_iter=get_chunked_archive(archive))
338 response = Response(app_iter=get_chunked_archive(archive))
339 response.content_disposition = str(
339 response.content_disposition = str(
340 'attachment; filename=%s' % archive_name)
340 'attachment; filename=%s' % archive_name)
341 response.content_type = str(content_type)
341 response.content_type = str(content_type)
342
342
343 return response
343 return response
344
344
345 def _get_file_node(self, commit_id, f_path):
345 def _get_file_node(self, commit_id, f_path):
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
346 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
347 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
348 try:
348 try:
349 node = commit.get_node(f_path)
349 node = commit.get_node(f_path)
350 if node.is_dir():
350 if node.is_dir():
351 raise NodeError('%s path is a %s not a file'
351 raise NodeError('%s path is a %s not a file'
352 % (node, type(node)))
352 % (node, type(node)))
353 except NodeDoesNotExistError:
353 except NodeDoesNotExistError:
354 commit = EmptyCommit(
354 commit = EmptyCommit(
355 commit_id=commit_id,
355 commit_id=commit_id,
356 idx=commit.idx,
356 idx=commit.idx,
357 repo=commit.repository,
357 repo=commit.repository,
358 alias=commit.repository.alias,
358 alias=commit.repository.alias,
359 message=commit.message,
359 message=commit.message,
360 author=commit.author,
360 author=commit.author,
361 date=commit.date)
361 date=commit.date)
362 node = FileNode(f_path, '', commit=commit)
362 node = FileNode(f_path, '', commit=commit)
363 else:
363 else:
364 commit = EmptyCommit(
364 commit = EmptyCommit(
365 repo=self.rhodecode_vcs_repo,
365 repo=self.rhodecode_vcs_repo,
366 alias=self.rhodecode_vcs_repo.alias)
366 alias=self.rhodecode_vcs_repo.alias)
367 node = FileNode(f_path, '', commit=commit)
367 node = FileNode(f_path, '', commit=commit)
368 return node
368 return node
369
369
370 @LoginRequired()
370 @LoginRequired()
371 @HasRepoPermissionAnyDecorator(
371 @HasRepoPermissionAnyDecorator(
372 'repository.read', 'repository.write', 'repository.admin')
372 'repository.read', 'repository.write', 'repository.admin')
373 @view_config(
373 @view_config(
374 route_name='repo_files_diff', request_method='GET',
374 route_name='repo_files_diff', request_method='GET',
375 renderer=None)
375 renderer=None)
376 def repo_files_diff(self):
376 def repo_files_diff(self):
377 c = self.load_default_context()
377 c = self.load_default_context()
378 f_path = self._get_f_path(self.request.matchdict)
378 f_path = self._get_f_path(self.request.matchdict)
379 diff1 = self.request.GET.get('diff1', '')
379 diff1 = self.request.GET.get('diff1', '')
380 diff2 = self.request.GET.get('diff2', '')
380 diff2 = self.request.GET.get('diff2', '')
381
381
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
382 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
383
383
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
384 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
385 line_context = self.request.GET.get('context', 3)
385 line_context = self.request.GET.get('context', 3)
386
386
387 if not any((diff1, diff2)):
387 if not any((diff1, diff2)):
388 h.flash(
388 h.flash(
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
389 'Need query parameter "diff1" or "diff2" to generate a diff.',
390 category='error')
390 category='error')
391 raise HTTPBadRequest()
391 raise HTTPBadRequest()
392
392
393 c.action = self.request.GET.get('diff')
393 c.action = self.request.GET.get('diff')
394 if c.action not in ['download', 'raw']:
394 if c.action not in ['download', 'raw']:
395 compare_url = h.route_path(
395 compare_url = h.route_path(
396 'repo_compare',
396 'repo_compare',
397 repo_name=self.db_repo_name,
397 repo_name=self.db_repo_name,
398 source_ref_type='rev',
398 source_ref_type='rev',
399 source_ref=diff1,
399 source_ref=diff1,
400 target_repo=self.db_repo_name,
400 target_repo=self.db_repo_name,
401 target_ref_type='rev',
401 target_ref_type='rev',
402 target_ref=diff2,
402 target_ref=diff2,
403 _query=dict(f_path=f_path))
403 _query=dict(f_path=f_path))
404 # redirect to new view if we render diff
404 # redirect to new view if we render diff
405 raise HTTPFound(compare_url)
405 raise HTTPFound(compare_url)
406
406
407 try:
407 try:
408 node1 = self._get_file_node(diff1, path1)
408 node1 = self._get_file_node(diff1, path1)
409 node2 = self._get_file_node(diff2, f_path)
409 node2 = self._get_file_node(diff2, f_path)
410 except (RepositoryError, NodeError):
410 except (RepositoryError, NodeError):
411 log.exception("Exception while trying to get node from repository")
411 log.exception("Exception while trying to get node from repository")
412 raise HTTPFound(
412 raise HTTPFound(
413 h.route_path('repo_files', repo_name=self.db_repo_name,
413 h.route_path('repo_files', repo_name=self.db_repo_name,
414 commit_id='tip', f_path=f_path))
414 commit_id='tip', f_path=f_path))
415
415
416 if all(isinstance(node.commit, EmptyCommit)
416 if all(isinstance(node.commit, EmptyCommit)
417 for node in (node1, node2)):
417 for node in (node1, node2)):
418 raise HTTPNotFound()
418 raise HTTPNotFound()
419
419
420 c.commit_1 = node1.commit
420 c.commit_1 = node1.commit
421 c.commit_2 = node2.commit
421 c.commit_2 = node2.commit
422
422
423 if c.action == 'download':
423 if c.action == 'download':
424 _diff = diffs.get_gitdiff(node1, node2,
424 _diff = diffs.get_gitdiff(node1, node2,
425 ignore_whitespace=ignore_whitespace,
425 ignore_whitespace=ignore_whitespace,
426 context=line_context)
426 context=line_context)
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
428
428
429 response = Response(self.path_filter.get_raw_patch(diff))
429 response = Response(self.path_filter.get_raw_patch(diff))
430 response.content_type = 'text/plain'
430 response.content_type = 'text/plain'
431 response.content_disposition = (
431 response.content_disposition = (
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
433 )
433 )
434 charset = self._get_default_encoding(c)
434 charset = self._get_default_encoding(c)
435 if charset:
435 if charset:
436 response.charset = charset
436 response.charset = charset
437 return response
437 return response
438
438
439 elif c.action == 'raw':
439 elif c.action == 'raw':
440 _diff = diffs.get_gitdiff(node1, node2,
440 _diff = diffs.get_gitdiff(node1, node2,
441 ignore_whitespace=ignore_whitespace,
441 ignore_whitespace=ignore_whitespace,
442 context=line_context)
442 context=line_context)
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
444
444
445 response = Response(self.path_filter.get_raw_patch(diff))
445 response = Response(self.path_filter.get_raw_patch(diff))
446 response.content_type = 'text/plain'
446 response.content_type = 'text/plain'
447 charset = self._get_default_encoding(c)
447 charset = self._get_default_encoding(c)
448 if charset:
448 if charset:
449 response.charset = charset
449 response.charset = charset
450 return response
450 return response
451
451
452 # in case we ever end up here
452 # in case we ever end up here
453 raise HTTPNotFound()
453 raise HTTPNotFound()
454
454
455 @LoginRequired()
455 @LoginRequired()
456 @HasRepoPermissionAnyDecorator(
456 @HasRepoPermissionAnyDecorator(
457 'repository.read', 'repository.write', 'repository.admin')
457 'repository.read', 'repository.write', 'repository.admin')
458 @view_config(
458 @view_config(
459 route_name='repo_files_diff_2way_redirect', request_method='GET',
459 route_name='repo_files_diff_2way_redirect', request_method='GET',
460 renderer=None)
460 renderer=None)
461 def repo_files_diff_2way_redirect(self):
461 def repo_files_diff_2way_redirect(self):
462 """
462 """
463 Kept only to make OLD links work
463 Kept only to make OLD links work
464 """
464 """
465 f_path = self._get_f_path_unchecked(self.request.matchdict)
465 f_path = self._get_f_path_unchecked(self.request.matchdict)
466 diff1 = self.request.GET.get('diff1', '')
466 diff1 = self.request.GET.get('diff1', '')
467 diff2 = self.request.GET.get('diff2', '')
467 diff2 = self.request.GET.get('diff2', '')
468
468
469 if not any((diff1, diff2)):
469 if not any((diff1, diff2)):
470 h.flash(
470 h.flash(
471 'Need query parameter "diff1" or "diff2" to generate a diff.',
471 'Need query parameter "diff1" or "diff2" to generate a diff.',
472 category='error')
472 category='error')
473 raise HTTPBadRequest()
473 raise HTTPBadRequest()
474
474
475 compare_url = h.route_path(
475 compare_url = h.route_path(
476 'repo_compare',
476 'repo_compare',
477 repo_name=self.db_repo_name,
477 repo_name=self.db_repo_name,
478 source_ref_type='rev',
478 source_ref_type='rev',
479 source_ref=diff1,
479 source_ref=diff1,
480 target_ref_type='rev',
480 target_ref_type='rev',
481 target_ref=diff2,
481 target_ref=diff2,
482 _query=dict(f_path=f_path, diffmode='sideside',
482 _query=dict(f_path=f_path, diffmode='sideside',
483 target_repo=self.db_repo_name,))
483 target_repo=self.db_repo_name,))
484 raise HTTPFound(compare_url)
484 raise HTTPFound(compare_url)
485
485
486 @LoginRequired()
486 @LoginRequired()
487 @HasRepoPermissionAnyDecorator(
487 @HasRepoPermissionAnyDecorator(
488 'repository.read', 'repository.write', 'repository.admin')
488 'repository.read', 'repository.write', 'repository.admin')
489 @view_config(
489 @view_config(
490 route_name='repo_files', request_method='GET',
490 route_name='repo_files', request_method='GET',
491 renderer=None)
491 renderer=None)
492 @view_config(
492 @view_config(
493 route_name='repo_files:default_path', request_method='GET',
493 route_name='repo_files:default_path', request_method='GET',
494 renderer=None)
494 renderer=None)
495 @view_config(
495 @view_config(
496 route_name='repo_files:default_commit', request_method='GET',
496 route_name='repo_files:default_commit', request_method='GET',
497 renderer=None)
497 renderer=None)
498 @view_config(
498 @view_config(
499 route_name='repo_files:rendered', request_method='GET',
499 route_name='repo_files:rendered', request_method='GET',
500 renderer=None)
500 renderer=None)
501 @view_config(
501 @view_config(
502 route_name='repo_files:annotated', request_method='GET',
502 route_name='repo_files:annotated', request_method='GET',
503 renderer=None)
503 renderer=None)
504 def repo_files(self):
504 def repo_files(self):
505 c = self.load_default_context()
505 c = self.load_default_context()
506
506
507 view_name = getattr(self.request.matched_route, 'name', None)
507 view_name = getattr(self.request.matched_route, 'name', None)
508
508
509 c.annotate = view_name == 'repo_files:annotated'
509 c.annotate = view_name == 'repo_files:annotated'
510 # default is false, but .rst/.md files later are auto rendered, we can
510 # default is false, but .rst/.md files later are auto rendered, we can
511 # overwrite auto rendering by setting this GET flag
511 # overwrite auto rendering by setting this GET flag
512 c.renderer = view_name == 'repo_files:rendered' or \
512 c.renderer = view_name == 'repo_files:rendered' or \
513 not self.request.GET.get('no-render', False)
513 not self.request.GET.get('no-render', False)
514
514
515 # redirect to given commit_id from form if given
515 # redirect to given commit_id from form if given
516 get_commit_id = self.request.GET.get('at_rev', None)
516 get_commit_id = self.request.GET.get('at_rev', None)
517 if get_commit_id:
517 if get_commit_id:
518 self._get_commit_or_redirect(get_commit_id)
518 self._get_commit_or_redirect(get_commit_id)
519
519
520 commit_id, f_path = self._get_commit_and_path()
520 commit_id, f_path = self._get_commit_and_path()
521 c.commit = self._get_commit_or_redirect(commit_id)
521 c.commit = self._get_commit_or_redirect(commit_id)
522 c.branch = self.request.GET.get('branch', None)
522 c.branch = self.request.GET.get('branch', None)
523 c.f_path = f_path
523 c.f_path = f_path
524
524
525 # prev link
525 # prev link
526 try:
526 try:
527 prev_commit = c.commit.prev(c.branch)
527 prev_commit = c.commit.prev(c.branch)
528 c.prev_commit = prev_commit
528 c.prev_commit = prev_commit
529 c.url_prev = h.route_path(
529 c.url_prev = h.route_path(
530 'repo_files', repo_name=self.db_repo_name,
530 'repo_files', repo_name=self.db_repo_name,
531 commit_id=prev_commit.raw_id, f_path=f_path)
531 commit_id=prev_commit.raw_id, f_path=f_path)
532 if c.branch:
532 if c.branch:
533 c.url_prev += '?branch=%s' % c.branch
533 c.url_prev += '?branch=%s' % c.branch
534 except (CommitDoesNotExistError, VCSError):
534 except (CommitDoesNotExistError, VCSError):
535 c.url_prev = '#'
535 c.url_prev = '#'
536 c.prev_commit = EmptyCommit()
536 c.prev_commit = EmptyCommit()
537
537
538 # next link
538 # next link
539 try:
539 try:
540 next_commit = c.commit.next(c.branch)
540 next_commit = c.commit.next(c.branch)
541 c.next_commit = next_commit
541 c.next_commit = next_commit
542 c.url_next = h.route_path(
542 c.url_next = h.route_path(
543 'repo_files', repo_name=self.db_repo_name,
543 'repo_files', repo_name=self.db_repo_name,
544 commit_id=next_commit.raw_id, f_path=f_path)
544 commit_id=next_commit.raw_id, f_path=f_path)
545 if c.branch:
545 if c.branch:
546 c.url_next += '?branch=%s' % c.branch
546 c.url_next += '?branch=%s' % c.branch
547 except (CommitDoesNotExistError, VCSError):
547 except (CommitDoesNotExistError, VCSError):
548 c.url_next = '#'
548 c.url_next = '#'
549 c.next_commit = EmptyCommit()
549 c.next_commit = EmptyCommit()
550
550
551 # files or dirs
551 # files or dirs
552 try:
552 try:
553 c.file = c.commit.get_node(f_path)
553 c.file = c.commit.get_node(f_path)
554 c.file_author = True
554 c.file_author = True
555 c.file_tree = ''
555 c.file_tree = ''
556
556
557 # load file content
557 # load file content
558 if c.file.is_file():
558 if c.file.is_file():
559 c.lf_node = c.file.get_largefile_node()
559 c.lf_node = c.file.get_largefile_node()
560
560
561 c.file_source_page = 'true'
561 c.file_source_page = 'true'
562 c.file_last_commit = c.file.last_commit
562 c.file_last_commit = c.file.last_commit
563 if c.file.size < c.visual.cut_off_limit_diff:
563 if c.file.size < c.visual.cut_off_limit_diff:
564 if c.annotate: # annotation has precedence over renderer
564 if c.annotate: # annotation has precedence over renderer
565 c.annotated_lines = filenode_as_annotated_lines_tokens(
565 c.annotated_lines = filenode_as_annotated_lines_tokens(
566 c.file
566 c.file
567 )
567 )
568 else:
568 else:
569 c.renderer = (
569 c.renderer = (
570 c.renderer and h.renderer_from_filename(c.file.path)
570 c.renderer and h.renderer_from_filename(c.file.path)
571 )
571 )
572 if not c.renderer:
572 if not c.renderer:
573 c.lines = filenode_as_lines_tokens(c.file)
573 c.lines = filenode_as_lines_tokens(c.file)
574
574
575 c.on_branch_head = self._is_valid_head(
575 c.on_branch_head = self._is_valid_head(
576 commit_id, self.rhodecode_vcs_repo)
576 commit_id, self.rhodecode_vcs_repo)
577
577
578 branch = c.commit.branch if (
578 branch = c.commit.branch if (
579 c.commit.branch and '/' not in c.commit.branch) else None
579 c.commit.branch and '/' not in c.commit.branch) else None
580 c.branch_or_raw_id = branch or c.commit.raw_id
580 c.branch_or_raw_id = branch or c.commit.raw_id
581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
581 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
582
582
583 author = c.file_last_commit.author
583 author = c.file_last_commit.author
584 c.authors = [[
584 c.authors = [[
585 h.email(author),
585 h.email(author),
586 h.person(author, 'username_or_name_or_email'),
586 h.person(author, 'username_or_name_or_email'),
587 1
587 1
588 ]]
588 ]]
589
589
590 else: # load tree content at path
590 else: # load tree content at path
591 c.file_source_page = 'false'
591 c.file_source_page = 'false'
592 c.authors = []
592 c.authors = []
593 # this loads a simple tree without metadata to speed things up
593 # this loads a simple tree without metadata to speed things up
594 # later via ajax we call repo_nodetree_full and fetch whole
594 # later via ajax we call repo_nodetree_full and fetch whole
595 c.file_tree = self._get_tree_at_commit(
595 c.file_tree = self._get_tree_at_commit(
596 c, c.commit.raw_id, f_path)
596 c, c.commit.raw_id, f_path)
597
597
598 except RepositoryError as e:
598 except RepositoryError as e:
599 h.flash(safe_str(h.escape(e)), category='error')
599 h.flash(safe_str(h.escape(e)), category='error')
600 raise HTTPNotFound()
600 raise HTTPNotFound()
601
601
602 if self.request.environ.get('HTTP_X_PJAX'):
602 if self.request.environ.get('HTTP_X_PJAX'):
603 html = render('rhodecode:templates/files/files_pjax.mako',
603 html = render('rhodecode:templates/files/files_pjax.mako',
604 self._get_template_context(c), self.request)
604 self._get_template_context(c), self.request)
605 else:
605 else:
606 html = render('rhodecode:templates/files/files.mako',
606 html = render('rhodecode:templates/files/files.mako',
607 self._get_template_context(c), self.request)
607 self._get_template_context(c), self.request)
608 return Response(html)
608 return Response(html)
609
609
610 @HasRepoPermissionAnyDecorator(
610 @HasRepoPermissionAnyDecorator(
611 'repository.read', 'repository.write', 'repository.admin')
611 'repository.read', 'repository.write', 'repository.admin')
612 @view_config(
612 @view_config(
613 route_name='repo_files:annotated_previous', request_method='GET',
613 route_name='repo_files:annotated_previous', request_method='GET',
614 renderer=None)
614 renderer=None)
615 def repo_files_annotated_previous(self):
615 def repo_files_annotated_previous(self):
616 self.load_default_context()
616 self.load_default_context()
617
617
618 commit_id, f_path = self._get_commit_and_path()
618 commit_id, f_path = self._get_commit_and_path()
619 commit = self._get_commit_or_redirect(commit_id)
619 commit = self._get_commit_or_redirect(commit_id)
620 prev_commit_id = commit.raw_id
620 prev_commit_id = commit.raw_id
621 line_anchor = self.request.GET.get('line_anchor')
621 line_anchor = self.request.GET.get('line_anchor')
622 is_file = False
622 is_file = False
623 try:
623 try:
624 _file = commit.get_node(f_path)
624 _file = commit.get_node(f_path)
625 is_file = _file.is_file()
625 is_file = _file.is_file()
626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
626 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
627 pass
627 pass
628
628
629 if is_file:
629 if is_file:
630 history = commit.get_file_history(f_path)
630 history = commit.get_file_history(f_path)
631 prev_commit_id = history[1].raw_id \
631 prev_commit_id = history[1].raw_id \
632 if len(history) > 1 else prev_commit_id
632 if len(history) > 1 else prev_commit_id
633 prev_url = h.route_path(
633 prev_url = h.route_path(
634 'repo_files:annotated', repo_name=self.db_repo_name,
634 'repo_files:annotated', repo_name=self.db_repo_name,
635 commit_id=prev_commit_id, f_path=f_path,
635 commit_id=prev_commit_id, f_path=f_path,
636 _anchor='L{}'.format(line_anchor))
636 _anchor='L{}'.format(line_anchor))
637
637
638 raise HTTPFound(prev_url)
638 raise HTTPFound(prev_url)
639
639
640 @LoginRequired()
640 @LoginRequired()
641 @HasRepoPermissionAnyDecorator(
641 @HasRepoPermissionAnyDecorator(
642 'repository.read', 'repository.write', 'repository.admin')
642 'repository.read', 'repository.write', 'repository.admin')
643 @view_config(
643 @view_config(
644 route_name='repo_nodetree_full', request_method='GET',
644 route_name='repo_nodetree_full', request_method='GET',
645 renderer=None, xhr=True)
645 renderer=None, xhr=True)
646 @view_config(
646 @view_config(
647 route_name='repo_nodetree_full:default_path', request_method='GET',
647 route_name='repo_nodetree_full:default_path', request_method='GET',
648 renderer=None, xhr=True)
648 renderer=None, xhr=True)
649 def repo_nodetree_full(self):
649 def repo_nodetree_full(self):
650 """
650 """
651 Returns rendered html of file tree that contains commit date,
651 Returns rendered html of file tree that contains commit date,
652 author, commit_id for the specified combination of
652 author, commit_id for the specified combination of
653 repo, commit_id and file path
653 repo, commit_id and file path
654 """
654 """
655 c = self.load_default_context()
655 c = self.load_default_context()
656
656
657 commit_id, f_path = self._get_commit_and_path()
657 commit_id, f_path = self._get_commit_and_path()
658 commit = self._get_commit_or_redirect(commit_id)
658 commit = self._get_commit_or_redirect(commit_id)
659 try:
659 try:
660 dir_node = commit.get_node(f_path)
660 dir_node = commit.get_node(f_path)
661 except RepositoryError as e:
661 except RepositoryError as e:
662 return Response('error: {}'.format(h.escape(safe_str(e))))
662 return Response('error: {}'.format(h.escape(safe_str(e))))
663
663
664 if dir_node.is_file():
664 if dir_node.is_file():
665 return Response('')
665 return Response('')
666
666
667 c.file = dir_node
667 c.file = dir_node
668 c.commit = commit
668 c.commit = commit
669
669
670 html = self._get_tree_at_commit(
670 html = self._get_tree_at_commit(
671 c, commit.raw_id, dir_node.path, full_load=True)
671 c, commit.raw_id, dir_node.path, full_load=True)
672
672
673 return Response(html)
673 return Response(html)
674
674
675 def _get_attachement_disposition(self, f_path):
675 def _get_attachement_disposition(self, f_path):
676 return 'attachment; filename=%s' % \
676 return 'attachment; filename=%s' % \
677 safe_str(f_path.split(Repository.NAME_SEP)[-1])
677 safe_str(f_path.split(Repository.NAME_SEP)[-1])
678
678
679 @LoginRequired()
679 @LoginRequired()
680 @HasRepoPermissionAnyDecorator(
680 @HasRepoPermissionAnyDecorator(
681 'repository.read', 'repository.write', 'repository.admin')
681 'repository.read', 'repository.write', 'repository.admin')
682 @view_config(
682 @view_config(
683 route_name='repo_file_raw', request_method='GET',
683 route_name='repo_file_raw', request_method='GET',
684 renderer=None)
684 renderer=None)
685 def repo_file_raw(self):
685 def repo_file_raw(self):
686 """
686 """
687 Action for show as raw, some mimetypes are "rendered",
687 Action for show as raw, some mimetypes are "rendered",
688 those include images, icons.
688 those include images, icons.
689 """
689 """
690 c = self.load_default_context()
690 c = self.load_default_context()
691
691
692 commit_id, f_path = self._get_commit_and_path()
692 commit_id, f_path = self._get_commit_and_path()
693 commit = self._get_commit_or_redirect(commit_id)
693 commit = self._get_commit_or_redirect(commit_id)
694 file_node = self._get_filenode_or_redirect(commit, f_path)
694 file_node = self._get_filenode_or_redirect(commit, f_path)
695
695
696 raw_mimetype_mapping = {
696 raw_mimetype_mapping = {
697 # map original mimetype to a mimetype used for "show as raw"
697 # map original mimetype to a mimetype used for "show as raw"
698 # you can also provide a content-disposition to override the
698 # you can also provide a content-disposition to override the
699 # default "attachment" disposition.
699 # default "attachment" disposition.
700 # orig_type: (new_type, new_dispo)
700 # orig_type: (new_type, new_dispo)
701
701
702 # show images inline:
702 # show images inline:
703 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
703 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
704 # for example render an SVG with javascript inside or even render
704 # for example render an SVG with javascript inside or even render
705 # HTML.
705 # HTML.
706 'image/x-icon': ('image/x-icon', 'inline'),
706 'image/x-icon': ('image/x-icon', 'inline'),
707 'image/png': ('image/png', 'inline'),
707 'image/png': ('image/png', 'inline'),
708 'image/gif': ('image/gif', 'inline'),
708 'image/gif': ('image/gif', 'inline'),
709 'image/jpeg': ('image/jpeg', 'inline'),
709 'image/jpeg': ('image/jpeg', 'inline'),
710 'application/pdf': ('application/pdf', 'inline'),
710 'application/pdf': ('application/pdf', 'inline'),
711 }
711 }
712
712
713 mimetype = file_node.mimetype
713 mimetype = file_node.mimetype
714 try:
714 try:
715 mimetype, disposition = raw_mimetype_mapping[mimetype]
715 mimetype, disposition = raw_mimetype_mapping[mimetype]
716 except KeyError:
716 except KeyError:
717 # we don't know anything special about this, handle it safely
717 # we don't know anything special about this, handle it safely
718 if file_node.is_binary:
718 if file_node.is_binary:
719 # do same as download raw for binary files
719 # do same as download raw for binary files
720 mimetype, disposition = 'application/octet-stream', 'attachment'
720 mimetype, disposition = 'application/octet-stream', 'attachment'
721 else:
721 else:
722 # do not just use the original mimetype, but force text/plain,
722 # do not just use the original mimetype, but force text/plain,
723 # otherwise it would serve text/html and that might be unsafe.
723 # otherwise it would serve text/html and that might be unsafe.
724 # Note: underlying vcs library fakes text/plain mimetype if the
724 # Note: underlying vcs library fakes text/plain mimetype if the
725 # mimetype can not be determined and it thinks it is not
725 # mimetype can not be determined and it thinks it is not
726 # binary.This might lead to erroneous text display in some
726 # binary.This might lead to erroneous text display in some
727 # cases, but helps in other cases, like with text files
727 # cases, but helps in other cases, like with text files
728 # without extension.
728 # without extension.
729 mimetype, disposition = 'text/plain', 'inline'
729 mimetype, disposition = 'text/plain', 'inline'
730
730
731 if disposition == 'attachment':
731 if disposition == 'attachment':
732 disposition = self._get_attachement_disposition(f_path)
732 disposition = self._get_attachement_disposition(f_path)
733
733
734 def stream_node():
734 def stream_node():
735 yield file_node.raw_bytes
735 yield file_node.raw_bytes
736
736
737 response = Response(app_iter=stream_node())
737 response = Response(app_iter=stream_node())
738 response.content_disposition = disposition
738 response.content_disposition = disposition
739 response.content_type = mimetype
739 response.content_type = mimetype
740
740
741 charset = self._get_default_encoding(c)
741 charset = self._get_default_encoding(c)
742 if charset:
742 if charset:
743 response.charset = charset
743 response.charset = charset
744
744
745 return response
745 return response
746
746
747 @LoginRequired()
747 @LoginRequired()
748 @HasRepoPermissionAnyDecorator(
748 @HasRepoPermissionAnyDecorator(
749 'repository.read', 'repository.write', 'repository.admin')
749 'repository.read', 'repository.write', 'repository.admin')
750 @view_config(
750 @view_config(
751 route_name='repo_file_download', request_method='GET',
751 route_name='repo_file_download', request_method='GET',
752 renderer=None)
752 renderer=None)
753 @view_config(
753 @view_config(
754 route_name='repo_file_download:legacy', request_method='GET',
754 route_name='repo_file_download:legacy', request_method='GET',
755 renderer=None)
755 renderer=None)
756 def repo_file_download(self):
756 def repo_file_download(self):
757 c = self.load_default_context()
757 c = self.load_default_context()
758
758
759 commit_id, f_path = self._get_commit_and_path()
759 commit_id, f_path = self._get_commit_and_path()
760 commit = self._get_commit_or_redirect(commit_id)
760 commit = self._get_commit_or_redirect(commit_id)
761 file_node = self._get_filenode_or_redirect(commit, f_path)
761 file_node = self._get_filenode_or_redirect(commit, f_path)
762
762
763 if self.request.GET.get('lf'):
763 if self.request.GET.get('lf'):
764 # only if lf get flag is passed, we download this file
764 # only if lf get flag is passed, we download this file
765 # as LFS/Largefile
765 # as LFS/Largefile
766 lf_node = file_node.get_largefile_node()
766 lf_node = file_node.get_largefile_node()
767 if lf_node:
767 if lf_node:
768 # overwrite our pointer with the REAL large-file
768 # overwrite our pointer with the REAL large-file
769 file_node = lf_node
769 file_node = lf_node
770
770
771 disposition = self._get_attachement_disposition(f_path)
771 disposition = self._get_attachement_disposition(f_path)
772
772
773 def stream_node():
773 def stream_node():
774 yield file_node.raw_bytes
774 yield file_node.raw_bytes
775
775
776 response = Response(app_iter=stream_node())
776 response = Response(app_iter=stream_node())
777 response.content_disposition = disposition
777 response.content_disposition = disposition
778 response.content_type = file_node.mimetype
778 response.content_type = file_node.mimetype
779
779
780 charset = self._get_default_encoding(c)
780 charset = self._get_default_encoding(c)
781 if charset:
781 if charset:
782 response.charset = charset
782 response.charset = charset
783
783
784 return response
784 return response
785
785
786 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
786 def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
787
787
788 cache_seconds = safe_int(
788 cache_seconds = safe_int(
789 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
789 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
790 cache_on = cache_seconds > 0
790 cache_on = cache_seconds > 0
791 log.debug(
791 log.debug(
792 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
792 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
793 'with caching: %s[TTL: %ss]' % (
793 'with caching: %s[TTL: %ss]' % (
794 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
794 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
795
795
796 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
796 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
797 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
797 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
798
798
799 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
799 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
800 condition=cache_on)
800 condition=cache_on)
801 def compute_file_search(repo_id, commit_id, f_path):
801 def compute_file_search(repo_id, commit_id, f_path):
802 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
802 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
803 repo_id, commit_id, f_path)
803 repo_id, commit_id, f_path)
804 try:
804 try:
805 _d, _f = ScmModel().get_nodes(
805 _d, _f = ScmModel().get_nodes(
806 repo_name, commit_id, f_path, flat=False)
806 repo_name, commit_id, f_path, flat=False)
807 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
807 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
808 log.exception(safe_str(e))
808 log.exception(safe_str(e))
809 h.flash(safe_str(h.escape(e)), category='error')
809 h.flash(safe_str(h.escape(e)), category='error')
810 raise HTTPFound(h.route_path(
810 raise HTTPFound(h.route_path(
811 'repo_files', repo_name=self.db_repo_name,
811 'repo_files', repo_name=self.db_repo_name,
812 commit_id='tip', f_path='/'))
812 commit_id='tip', f_path='/'))
813 return _d + _f
813 return _d + _f
814
814
815 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
815 return compute_file_search(self.db_repo.repo_id, commit_id, f_path)
816
816
817 @LoginRequired()
817 @LoginRequired()
818 @HasRepoPermissionAnyDecorator(
818 @HasRepoPermissionAnyDecorator(
819 'repository.read', 'repository.write', 'repository.admin')
819 'repository.read', 'repository.write', 'repository.admin')
820 @view_config(
820 @view_config(
821 route_name='repo_files_nodelist', request_method='GET',
821 route_name='repo_files_nodelist', request_method='GET',
822 renderer='json_ext', xhr=True)
822 renderer='json_ext', xhr=True)
823 def repo_nodelist(self):
823 def repo_nodelist(self):
824 self.load_default_context()
824 self.load_default_context()
825
825
826 commit_id, f_path = self._get_commit_and_path()
826 commit_id, f_path = self._get_commit_and_path()
827 commit = self._get_commit_or_redirect(commit_id)
827 commit = self._get_commit_or_redirect(commit_id)
828
828
829 metadata = self._get_nodelist_at_commit(
829 metadata = self._get_nodelist_at_commit(
830 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
830 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
831 return {'nodes': metadata}
831 return {'nodes': metadata}
832
832
833 def _create_references(
833 def _create_references(
834 self, branches_or_tags, symbolic_reference, f_path):
834 self, branches_or_tags, symbolic_reference, f_path):
835 items = []
835 items = []
836 for name, commit_id in branches_or_tags.items():
836 for name, commit_id in branches_or_tags.items():
837 sym_ref = symbolic_reference(commit_id, name, f_path)
837 sym_ref = symbolic_reference(commit_id, name, f_path)
838 items.append((sym_ref, name))
838 items.append((sym_ref, name))
839 return items
839 return items
840
840
841 def _symbolic_reference(self, commit_id, name, f_path):
841 def _symbolic_reference(self, commit_id, name, f_path):
842 return commit_id
842 return commit_id
843
843
844 def _symbolic_reference_svn(self, commit_id, name, f_path):
844 def _symbolic_reference_svn(self, commit_id, name, f_path):
845 new_f_path = vcspath.join(name, f_path)
845 new_f_path = vcspath.join(name, f_path)
846 return u'%s@%s' % (new_f_path, commit_id)
846 return u'%s@%s' % (new_f_path, commit_id)
847
847
848 def _get_node_history(self, commit_obj, f_path, commits=None):
848 def _get_node_history(self, commit_obj, f_path, commits=None):
849 """
849 """
850 get commit history for given node
850 get commit history for given node
851
851
852 :param commit_obj: commit to calculate history
852 :param commit_obj: commit to calculate history
853 :param f_path: path for node to calculate history for
853 :param f_path: path for node to calculate history for
854 :param commits: if passed don't calculate history and take
854 :param commits: if passed don't calculate history and take
855 commits defined in this list
855 commits defined in this list
856 """
856 """
857 _ = self.request.translate
857 _ = self.request.translate
858
858
859 # calculate history based on tip
859 # calculate history based on tip
860 tip = self.rhodecode_vcs_repo.get_commit()
860 tip = self.rhodecode_vcs_repo.get_commit()
861 if commits is None:
861 if commits is None:
862 pre_load = ["author", "branch"]
862 pre_load = ["author", "branch"]
863 try:
863 try:
864 commits = tip.get_file_history(f_path, pre_load=pre_load)
864 commits = tip.get_file_history(f_path, pre_load=pre_load)
865 except (NodeDoesNotExistError, CommitError):
865 except (NodeDoesNotExistError, CommitError):
866 # this node is not present at tip!
866 # this node is not present at tip!
867 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
867 commits = commit_obj.get_file_history(f_path, pre_load=pre_load)
868
868
869 history = []
869 history = []
870 commits_group = ([], _("Changesets"))
870 commits_group = ([], _("Changesets"))
871 for commit in commits:
871 for commit in commits:
872 branch = ' (%s)' % commit.branch if commit.branch else ''
872 branch = ' (%s)' % commit.branch if commit.branch else ''
873 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
873 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
874 commits_group[0].append((commit.raw_id, n_desc,))
874 commits_group[0].append((commit.raw_id, n_desc,))
875 history.append(commits_group)
875 history.append(commits_group)
876
876
877 symbolic_reference = self._symbolic_reference
877 symbolic_reference = self._symbolic_reference
878
878
879 if self.rhodecode_vcs_repo.alias == 'svn':
879 if self.rhodecode_vcs_repo.alias == 'svn':
880 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
880 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
881 f_path, self.rhodecode_vcs_repo)
881 f_path, self.rhodecode_vcs_repo)
882 if adjusted_f_path != f_path:
882 if adjusted_f_path != f_path:
883 log.debug(
883 log.debug(
884 'Recognized svn tag or branch in file "%s", using svn '
884 'Recognized svn tag or branch in file "%s", using svn '
885 'specific symbolic references', f_path)
885 'specific symbolic references', f_path)
886 f_path = adjusted_f_path
886 f_path = adjusted_f_path
887 symbolic_reference = self._symbolic_reference_svn
887 symbolic_reference = self._symbolic_reference_svn
888
888
889 branches = self._create_references(
889 branches = self._create_references(
890 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
890 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path)
891 branches_group = (branches, _("Branches"))
891 branches_group = (branches, _("Branches"))
892
892
893 tags = self._create_references(
893 tags = self._create_references(
894 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
894 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path)
895 tags_group = (tags, _("Tags"))
895 tags_group = (tags, _("Tags"))
896
896
897 history.append(branches_group)
897 history.append(branches_group)
898 history.append(tags_group)
898 history.append(tags_group)
899
899
900 return history, commits
900 return history, commits
901
901
902 @LoginRequired()
902 @LoginRequired()
903 @HasRepoPermissionAnyDecorator(
903 @HasRepoPermissionAnyDecorator(
904 'repository.read', 'repository.write', 'repository.admin')
904 'repository.read', 'repository.write', 'repository.admin')
905 @view_config(
905 @view_config(
906 route_name='repo_file_history', request_method='GET',
906 route_name='repo_file_history', request_method='GET',
907 renderer='json_ext')
907 renderer='json_ext')
908 def repo_file_history(self):
908 def repo_file_history(self):
909 self.load_default_context()
909 self.load_default_context()
910
910
911 commit_id, f_path = self._get_commit_and_path()
911 commit_id, f_path = self._get_commit_and_path()
912 commit = self._get_commit_or_redirect(commit_id)
912 commit = self._get_commit_or_redirect(commit_id)
913 file_node = self._get_filenode_or_redirect(commit, f_path)
913 file_node = self._get_filenode_or_redirect(commit, f_path)
914
914
915 if file_node.is_file():
915 if file_node.is_file():
916 file_history, _hist = self._get_node_history(commit, f_path)
916 file_history, _hist = self._get_node_history(commit, f_path)
917
917
918 res = []
918 res = []
919 for obj in file_history:
919 for obj in file_history:
920 res.append({
920 res.append({
921 'text': obj[1],
921 'text': obj[1],
922 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
922 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
923 })
923 })
924
924
925 data = {
925 data = {
926 'more': False,
926 'more': False,
927 'results': res
927 'results': res
928 }
928 }
929 return data
929 return data
930
930
931 log.warning('Cannot fetch history for directory')
931 log.warning('Cannot fetch history for directory')
932 raise HTTPBadRequest()
932 raise HTTPBadRequest()
933
933
934 @LoginRequired()
934 @LoginRequired()
935 @HasRepoPermissionAnyDecorator(
935 @HasRepoPermissionAnyDecorator(
936 'repository.read', 'repository.write', 'repository.admin')
936 'repository.read', 'repository.write', 'repository.admin')
937 @view_config(
937 @view_config(
938 route_name='repo_file_authors', request_method='GET',
938 route_name='repo_file_authors', request_method='GET',
939 renderer='rhodecode:templates/files/file_authors_box.mako')
939 renderer='rhodecode:templates/files/file_authors_box.mako')
940 def repo_file_authors(self):
940 def repo_file_authors(self):
941 c = self.load_default_context()
941 c = self.load_default_context()
942
942
943 commit_id, f_path = self._get_commit_and_path()
943 commit_id, f_path = self._get_commit_and_path()
944 commit = self._get_commit_or_redirect(commit_id)
944 commit = self._get_commit_or_redirect(commit_id)
945 file_node = self._get_filenode_or_redirect(commit, f_path)
945 file_node = self._get_filenode_or_redirect(commit, f_path)
946
946
947 if not file_node.is_file():
947 if not file_node.is_file():
948 raise HTTPBadRequest()
948 raise HTTPBadRequest()
949
949
950 c.file_last_commit = file_node.last_commit
950 c.file_last_commit = file_node.last_commit
951 if self.request.GET.get('annotate') == '1':
951 if self.request.GET.get('annotate') == '1':
952 # use _hist from annotation if annotation mode is on
952 # use _hist from annotation if annotation mode is on
953 commit_ids = set(x[1] for x in file_node.annotate)
953 commit_ids = set(x[1] for x in file_node.annotate)
954 _hist = (
954 _hist = (
955 self.rhodecode_vcs_repo.get_commit(commit_id)
955 self.rhodecode_vcs_repo.get_commit(commit_id)
956 for commit_id in commit_ids)
956 for commit_id in commit_ids)
957 else:
957 else:
958 _f_history, _hist = self._get_node_history(commit, f_path)
958 _f_history, _hist = self._get_node_history(commit, f_path)
959 c.file_author = False
959 c.file_author = False
960
960
961 unique = collections.OrderedDict()
961 unique = collections.OrderedDict()
962 for commit in _hist:
962 for commit in _hist:
963 author = commit.author
963 author = commit.author
964 if author not in unique:
964 if author not in unique:
965 unique[commit.author] = [
965 unique[commit.author] = [
966 h.email(author),
966 h.email(author),
967 h.person(author, 'username_or_name_or_email'),
967 h.person(author, 'username_or_name_or_email'),
968 1 # counter
968 1 # counter
969 ]
969 ]
970
970
971 else:
971 else:
972 # increase counter
972 # increase counter
973 unique[commit.author][2] += 1
973 unique[commit.author][2] += 1
974
974
975 c.authors = [val for val in unique.values()]
975 c.authors = [val for val in unique.values()]
976
976
977 return self._get_template_context(c)
977 return self._get_template_context(c)
978
978
979 @LoginRequired()
979 @LoginRequired()
980 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
980 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
981 @view_config(
981 @view_config(
982 route_name='repo_files_remove_file', request_method='GET',
982 route_name='repo_files_remove_file', request_method='GET',
983 renderer='rhodecode:templates/files/files_delete.mako')
983 renderer='rhodecode:templates/files/files_delete.mako')
984 def repo_files_remove_file(self):
984 def repo_files_remove_file(self):
985 _ = self.request.translate
985 _ = self.request.translate
986 c = self.load_default_context()
986 c = self.load_default_context()
987 commit_id, f_path = self._get_commit_and_path()
987 commit_id, f_path = self._get_commit_and_path()
988
988
989 self._ensure_not_locked()
989 self._ensure_not_locked()
990
990
991 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
991 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
992 h.flash(_('You can only delete files with commit '
992 h.flash(_('You can only delete files with commit '
993 'being a valid branch '), category='warning')
993 'being a valid branch '), category='warning')
994 raise HTTPFound(
994 raise HTTPFound(
995 h.route_path('repo_files',
995 h.route_path('repo_files',
996 repo_name=self.db_repo_name, commit_id='tip',
996 repo_name=self.db_repo_name, commit_id='tip',
997 f_path=f_path))
997 f_path=f_path))
998
998
999 c.commit = self._get_commit_or_redirect(commit_id)
999 c.commit = self._get_commit_or_redirect(commit_id)
1000 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1000 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1001
1001
1002 c.default_message = _(
1002 c.default_message = _(
1003 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1003 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1004 c.f_path = f_path
1004 c.f_path = f_path
1005
1005
1006 return self._get_template_context(c)
1006 return self._get_template_context(c)
1007
1007
1008 @LoginRequired()
1008 @LoginRequired()
1009 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1009 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1010 @CSRFRequired()
1010 @CSRFRequired()
1011 @view_config(
1011 @view_config(
1012 route_name='repo_files_delete_file', request_method='POST',
1012 route_name='repo_files_delete_file', request_method='POST',
1013 renderer=None)
1013 renderer=None)
1014 def repo_files_delete_file(self):
1014 def repo_files_delete_file(self):
1015 _ = self.request.translate
1015 _ = self.request.translate
1016
1016
1017 c = self.load_default_context()
1017 c = self.load_default_context()
1018 commit_id, f_path = self._get_commit_and_path()
1018 commit_id, f_path = self._get_commit_and_path()
1019
1019
1020 self._ensure_not_locked()
1020 self._ensure_not_locked()
1021
1021
1022 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1022 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1023 h.flash(_('You can only delete files with commit '
1023 h.flash(_('You can only delete files with commit '
1024 'being a valid branch '), category='warning')
1024 'being a valid branch '), category='warning')
1025 raise HTTPFound(
1025 raise HTTPFound(
1026 h.route_path('repo_files',
1026 h.route_path('repo_files',
1027 repo_name=self.db_repo_name, commit_id='tip',
1027 repo_name=self.db_repo_name, commit_id='tip',
1028 f_path=f_path))
1028 f_path=f_path))
1029
1029
1030 c.commit = self._get_commit_or_redirect(commit_id)
1030 c.commit = self._get_commit_or_redirect(commit_id)
1031 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1031 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1032
1032
1033 c.default_message = _(
1033 c.default_message = _(
1034 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1034 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1035 c.f_path = f_path
1035 c.f_path = f_path
1036 node_path = f_path
1036 node_path = f_path
1037 author = self._rhodecode_db_user.full_contact
1037 author = self._rhodecode_db_user.full_contact
1038 message = self.request.POST.get('message') or c.default_message
1038 message = self.request.POST.get('message') or c.default_message
1039 try:
1039 try:
1040 nodes = {
1040 nodes = {
1041 node_path: {
1041 node_path: {
1042 'content': ''
1042 'content': ''
1043 }
1043 }
1044 }
1044 }
1045 ScmModel().delete_nodes(
1045 ScmModel().delete_nodes(
1046 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1046 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1047 message=message,
1047 message=message,
1048 nodes=nodes,
1048 nodes=nodes,
1049 parent_commit=c.commit,
1049 parent_commit=c.commit,
1050 author=author,
1050 author=author,
1051 )
1051 )
1052
1052
1053 h.flash(
1053 h.flash(
1054 _('Successfully deleted file `{}`').format(
1054 _('Successfully deleted file `{}`').format(
1055 h.escape(f_path)), category='success')
1055 h.escape(f_path)), category='success')
1056 except Exception:
1056 except Exception:
1057 log.exception('Error during commit operation')
1057 log.exception('Error during commit operation')
1058 h.flash(_('Error occurred during commit'), category='error')
1058 h.flash(_('Error occurred during commit'), category='error')
1059 raise HTTPFound(
1059 raise HTTPFound(
1060 h.route_path('repo_commit', repo_name=self.db_repo_name,
1060 h.route_path('repo_commit', repo_name=self.db_repo_name,
1061 commit_id='tip'))
1061 commit_id='tip'))
1062
1062
1063 @LoginRequired()
1063 @LoginRequired()
1064 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1064 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1065 @view_config(
1065 @view_config(
1066 route_name='repo_files_edit_file', request_method='GET',
1066 route_name='repo_files_edit_file', request_method='GET',
1067 renderer='rhodecode:templates/files/files_edit.mako')
1067 renderer='rhodecode:templates/files/files_edit.mako')
1068 def repo_files_edit_file(self):
1068 def repo_files_edit_file(self):
1069 _ = self.request.translate
1069 _ = self.request.translate
1070 c = self.load_default_context()
1070 c = self.load_default_context()
1071 commit_id, f_path = self._get_commit_and_path()
1071 commit_id, f_path = self._get_commit_and_path()
1072
1072
1073 self._ensure_not_locked()
1073 self._ensure_not_locked()
1074
1074
1075 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1075 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1076 h.flash(_('You can only edit files with commit '
1076 h.flash(_('You can only edit files with commit '
1077 'being a valid branch '), category='warning')
1077 'being a valid branch '), category='warning')
1078 raise HTTPFound(
1078 raise HTTPFound(
1079 h.route_path('repo_files',
1079 h.route_path('repo_files',
1080 repo_name=self.db_repo_name, commit_id='tip',
1080 repo_name=self.db_repo_name, commit_id='tip',
1081 f_path=f_path))
1081 f_path=f_path))
1082
1082
1083 c.commit = self._get_commit_or_redirect(commit_id)
1083 c.commit = self._get_commit_or_redirect(commit_id)
1084 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1084 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1085
1085
1086 if c.file.is_binary:
1086 if c.file.is_binary:
1087 files_url = h.route_path(
1087 files_url = h.route_path(
1088 'repo_files',
1088 'repo_files',
1089 repo_name=self.db_repo_name,
1089 repo_name=self.db_repo_name,
1090 commit_id=c.commit.raw_id, f_path=f_path)
1090 commit_id=c.commit.raw_id, f_path=f_path)
1091 raise HTTPFound(files_url)
1091 raise HTTPFound(files_url)
1092
1092
1093 c.default_message = _(
1093 c.default_message = _(
1094 'Edited file {} via RhodeCode Enterprise').format(f_path)
1094 'Edited file {} via RhodeCode Enterprise').format(f_path)
1095 c.f_path = f_path
1095 c.f_path = f_path
1096
1096
1097 return self._get_template_context(c)
1097 return self._get_template_context(c)
1098
1098
1099 @LoginRequired()
1099 @LoginRequired()
1100 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1100 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1101 @CSRFRequired()
1101 @CSRFRequired()
1102 @view_config(
1102 @view_config(
1103 route_name='repo_files_update_file', request_method='POST',
1103 route_name='repo_files_update_file', request_method='POST',
1104 renderer=None)
1104 renderer=None)
1105 def repo_files_update_file(self):
1105 def repo_files_update_file(self):
1106 _ = self.request.translate
1106 _ = self.request.translate
1107 c = self.load_default_context()
1107 c = self.load_default_context()
1108 commit_id, f_path = self._get_commit_and_path()
1108 commit_id, f_path = self._get_commit_and_path()
1109
1109
1110 self._ensure_not_locked()
1110 self._ensure_not_locked()
1111
1111
1112 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1112 if not self._is_valid_head(commit_id, self.rhodecode_vcs_repo):
1113 h.flash(_('You can only edit files with commit '
1113 h.flash(_('You can only edit files with commit '
1114 'being a valid branch '), category='warning')
1114 'being a valid branch '), category='warning')
1115 raise HTTPFound(
1115 raise HTTPFound(
1116 h.route_path('repo_files',
1116 h.route_path('repo_files',
1117 repo_name=self.db_repo_name, commit_id='tip',
1117 repo_name=self.db_repo_name, commit_id='tip',
1118 f_path=f_path))
1118 f_path=f_path))
1119
1119
1120 c.commit = self._get_commit_or_redirect(commit_id)
1120 c.commit = self._get_commit_or_redirect(commit_id)
1121 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1121 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1122
1122
1123 if c.file.is_binary:
1123 if c.file.is_binary:
1124 raise HTTPFound(
1124 raise HTTPFound(
1125 h.route_path('repo_files',
1125 h.route_path('repo_files',
1126 repo_name=self.db_repo_name,
1126 repo_name=self.db_repo_name,
1127 commit_id=c.commit.raw_id,
1127 commit_id=c.commit.raw_id,
1128 f_path=f_path))
1128 f_path=f_path))
1129
1129
1130 c.default_message = _(
1130 c.default_message = _(
1131 'Edited file {} via RhodeCode Enterprise').format(f_path)
1131 'Edited file {} via RhodeCode Enterprise').format(f_path)
1132 c.f_path = f_path
1132 c.f_path = f_path
1133 old_content = c.file.content
1133 old_content = c.file.content
1134 sl = old_content.splitlines(1)
1134 sl = old_content.splitlines(1)
1135 first_line = sl[0] if sl else ''
1135 first_line = sl[0] if sl else ''
1136
1136
1137 r_post = self.request.POST
1137 r_post = self.request.POST
1138 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1138 # modes: 0 - Unix, 1 - Mac, 2 - DOS
1139 mode = detect_mode(first_line, 0)
1139 mode = detect_mode(first_line, 0)
1140 content = convert_line_endings(r_post.get('content', ''), mode)
1140 content = convert_line_endings(r_post.get('content', ''), mode)
1141
1141
1142 message = r_post.get('message') or c.default_message
1142 message = r_post.get('message') or c.default_message
1143 org_f_path = c.file.unicode_path
1143 org_f_path = c.file.unicode_path
1144 filename = r_post['filename']
1144 filename = r_post['filename']
1145 org_filename = c.file.name
1145 org_filename = c.file.name
1146
1146
1147 if content == old_content and filename == org_filename:
1147 if content == old_content and filename == org_filename:
1148 h.flash(_('No changes'), category='warning')
1148 h.flash(_('No changes'), category='warning')
1149 raise HTTPFound(
1149 raise HTTPFound(
1150 h.route_path('repo_commit', repo_name=self.db_repo_name,
1150 h.route_path('repo_commit', repo_name=self.db_repo_name,
1151 commit_id='tip'))
1151 commit_id='tip'))
1152 try:
1152 try:
1153 mapping = {
1153 mapping = {
1154 org_f_path: {
1154 org_f_path: {
1155 'org_filename': org_f_path,
1155 'org_filename': org_f_path,
1156 'filename': os.path.join(c.file.dir_path, filename),
1156 'filename': os.path.join(c.file.dir_path, filename),
1157 'content': content,
1157 'content': content,
1158 'lexer': '',
1158 'lexer': '',
1159 'op': 'mod',
1159 'op': 'mod',
1160 }
1160 }
1161 }
1161 }
1162
1162
1163 ScmModel().update_nodes(
1163 ScmModel().update_nodes(
1164 user=self._rhodecode_db_user.user_id,
1164 user=self._rhodecode_db_user.user_id,
1165 repo=self.db_repo,
1165 repo=self.db_repo,
1166 message=message,
1166 message=message,
1167 nodes=mapping,
1167 nodes=mapping,
1168 parent_commit=c.commit,
1168 parent_commit=c.commit,
1169 )
1169 )
1170
1170
1171 h.flash(
1171 h.flash(
1172 _('Successfully committed changes to file `{}`').format(
1172 _('Successfully committed changes to file `{}`').format(
1173 h.escape(f_path)), category='success')
1173 h.escape(f_path)), category='success')
1174 except Exception:
1174 except Exception:
1175 log.exception('Error occurred during commit')
1175 log.exception('Error occurred during commit')
1176 h.flash(_('Error occurred during commit'), category='error')
1176 h.flash(_('Error occurred during commit'), category='error')
1177 raise HTTPFound(
1177 raise HTTPFound(
1178 h.route_path('repo_commit', repo_name=self.db_repo_name,
1178 h.route_path('repo_commit', repo_name=self.db_repo_name,
1179 commit_id='tip'))
1179 commit_id='tip'))
1180
1180
1181 @LoginRequired()
1181 @LoginRequired()
1182 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1182 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1183 @view_config(
1183 @view_config(
1184 route_name='repo_files_add_file', request_method='GET',
1184 route_name='repo_files_add_file', request_method='GET',
1185 renderer='rhodecode:templates/files/files_add.mako')
1185 renderer='rhodecode:templates/files/files_add.mako')
1186 def repo_files_add_file(self):
1186 def repo_files_add_file(self):
1187 _ = self.request.translate
1187 _ = self.request.translate
1188 c = self.load_default_context()
1188 c = self.load_default_context()
1189 commit_id, f_path = self._get_commit_and_path()
1189 commit_id, f_path = self._get_commit_and_path()
1190
1190
1191 self._ensure_not_locked()
1191 self._ensure_not_locked()
1192
1192
1193 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1193 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1194 if c.commit is None:
1194 if c.commit is None:
1195 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1195 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1196 c.default_message = (_('Added file via RhodeCode Enterprise'))
1196 c.default_message = (_('Added file via RhodeCode Enterprise'))
1197 c.f_path = f_path.lstrip('/') # ensure not relative path
1197 c.f_path = f_path.lstrip('/') # ensure not relative path
1198
1198
1199 return self._get_template_context(c)
1199 return self._get_template_context(c)
1200
1200
1201 @LoginRequired()
1201 @LoginRequired()
1202 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1202 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1203 @CSRFRequired()
1203 @CSRFRequired()
1204 @view_config(
1204 @view_config(
1205 route_name='repo_files_create_file', request_method='POST',
1205 route_name='repo_files_create_file', request_method='POST',
1206 renderer=None)
1206 renderer=None)
1207 def repo_files_create_file(self):
1207 def repo_files_create_file(self):
1208 _ = self.request.translate
1208 _ = self.request.translate
1209 c = self.load_default_context()
1209 c = self.load_default_context()
1210 commit_id, f_path = self._get_commit_and_path()
1210 commit_id, f_path = self._get_commit_and_path()
1211
1211
1212 self._ensure_not_locked()
1212 self._ensure_not_locked()
1213
1213
1214 r_post = self.request.POST
1214 r_post = self.request.POST
1215
1215
1216 c.commit = self._get_commit_or_redirect(
1216 c.commit = self._get_commit_or_redirect(
1217 commit_id, redirect_after=False)
1217 commit_id, redirect_after=False)
1218 if c.commit is None:
1218 if c.commit is None:
1219 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1219 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1220 c.default_message = (_('Added file via RhodeCode Enterprise'))
1220 c.default_message = (_('Added file via RhodeCode Enterprise'))
1221 c.f_path = f_path
1221 c.f_path = f_path
1222 unix_mode = 0
1222 unix_mode = 0
1223 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1223 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1224
1224
1225 message = r_post.get('message') or c.default_message
1225 message = r_post.get('message') or c.default_message
1226 filename = r_post.get('filename')
1226 filename = r_post.get('filename')
1227 location = r_post.get('location', '') # dir location
1227 location = r_post.get('location', '') # dir location
1228 file_obj = r_post.get('upload_file', None)
1228 file_obj = r_post.get('upload_file', None)
1229
1229
1230 if file_obj is not None and hasattr(file_obj, 'filename'):
1230 if file_obj is not None and hasattr(file_obj, 'filename'):
1231 filename = r_post.get('filename_upload')
1231 filename = r_post.get('filename_upload')
1232 content = file_obj.file
1232 content = file_obj.file
1233
1233
1234 if hasattr(content, 'file'):
1234 if hasattr(content, 'file'):
1235 # non posix systems store real file under file attr
1235 # non posix systems store real file under file attr
1236 content = content.file
1236 content = content.file
1237
1237
1238 if self.rhodecode_vcs_repo.is_empty:
1238 if self.rhodecode_vcs_repo.is_empty:
1239 default_redirect_url = h.route_path(
1239 default_redirect_url = h.route_path(
1240 'repo_summary', repo_name=self.db_repo_name)
1240 'repo_summary', repo_name=self.db_repo_name)
1241 else:
1241 else:
1242 default_redirect_url = h.route_path(
1242 default_redirect_url = h.route_path(
1243 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1243 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1244
1244
1245 # If there's no commit, redirect to repo summary
1245 # If there's no commit, redirect to repo summary
1246 if type(c.commit) is EmptyCommit:
1246 if type(c.commit) is EmptyCommit:
1247 redirect_url = h.route_path(
1247 redirect_url = h.route_path(
1248 'repo_summary', repo_name=self.db_repo_name)
1248 'repo_summary', repo_name=self.db_repo_name)
1249 else:
1249 else:
1250 redirect_url = default_redirect_url
1250 redirect_url = default_redirect_url
1251
1251
1252 if not filename:
1252 if not filename:
1253 h.flash(_('No filename'), category='warning')
1253 h.flash(_('No filename'), category='warning')
1254 raise HTTPFound(redirect_url)
1254 raise HTTPFound(redirect_url)
1255
1255
1256 # extract the location from filename,
1256 # extract the location from filename,
1257 # allows using foo/bar.txt syntax to create subdirectories
1257 # allows using foo/bar.txt syntax to create subdirectories
1258 subdir_loc = filename.rsplit('/', 1)
1258 subdir_loc = filename.rsplit('/', 1)
1259 if len(subdir_loc) == 2:
1259 if len(subdir_loc) == 2:
1260 location = os.path.join(location, subdir_loc[0])
1260 location = os.path.join(location, subdir_loc[0])
1261
1261
1262 # strip all crap out of file, just leave the basename
1262 # strip all crap out of file, just leave the basename
1263 filename = os.path.basename(filename)
1263 filename = os.path.basename(filename)
1264 node_path = os.path.join(location, filename)
1264 node_path = os.path.join(location, filename)
1265 author = self._rhodecode_db_user.full_contact
1265 author = self._rhodecode_db_user.full_contact
1266
1266
1267 try:
1267 try:
1268 nodes = {
1268 nodes = {
1269 node_path: {
1269 node_path: {
1270 'content': content
1270 'content': content
1271 }
1271 }
1272 }
1272 }
1273 ScmModel().create_nodes(
1273 ScmModel().create_nodes(
1274 user=self._rhodecode_db_user.user_id,
1274 user=self._rhodecode_db_user.user_id,
1275 repo=self.db_repo,
1275 repo=self.db_repo,
1276 message=message,
1276 message=message,
1277 nodes=nodes,
1277 nodes=nodes,
1278 parent_commit=c.commit,
1278 parent_commit=c.commit,
1279 author=author,
1279 author=author,
1280 )
1280 )
1281
1281
1282 h.flash(
1282 h.flash(
1283 _('Successfully committed new file `{}`').format(
1283 _('Successfully committed new file `{}`').format(
1284 h.escape(node_path)), category='success')
1284 h.escape(node_path)), category='success')
1285 except NonRelativePathError:
1285 except NonRelativePathError:
1286 log.exception('Non Relative path found')
1286 log.exception('Non Relative path found')
1287 h.flash(_(
1287 h.flash(_(
1288 'The location specified must be a relative path and must not '
1288 'The location specified must be a relative path and must not '
1289 'contain .. in the path'), category='warning')
1289 'contain .. in the path'), category='warning')
1290 raise HTTPFound(default_redirect_url)
1290 raise HTTPFound(default_redirect_url)
1291 except (NodeError, NodeAlreadyExistsError) as e:
1291 except (NodeError, NodeAlreadyExistsError) as e:
1292 h.flash(_(h.escape(e)), category='error')
1292 h.flash(_(h.escape(e)), category='error')
1293 except Exception:
1293 except Exception:
1294 log.exception('Error occurred during commit')
1294 log.exception('Error occurred during commit')
1295 h.flash(_('Error occurred during commit'), category='error')
1295 h.flash(_('Error occurred during commit'), category='error')
1296
1296
1297 raise HTTPFound(default_redirect_url)
1297 raise HTTPFound(default_redirect_url)
@@ -1,379 +1,392 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import logging
22 import logging
22 import string
23 import string
23 import rhodecode
24 import rhodecode
24
25
25 from pyramid.view import view_config
26 from pyramid.view import view_config
26 from beaker.cache import cache_region
27
27
28 from rhodecode.controllers import utils
28 from rhodecode.controllers import utils
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
31 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib import helpers as h, rc_cache
32 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.utils2 import safe_str, safe_int
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.exceptions import (
37 from rhodecode.lib.vcs.exceptions import (
38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.db import Statistics, CacheKey, User
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.repo import ReadmeFinder
42 from rhodecode.model.scm import ScmModel
42 from rhodecode.model.scm import ScmModel
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoSummaryView(RepoAppView):
47 class RepoSummaryView(RepoAppView):
48
48
49 def load_default_context(self):
49 def load_default_context(self):
50 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c = self._get_local_tmpl_context(include_app_defaults=True)
51 c.rhodecode_repo = None
51 c.rhodecode_repo = None
52 if not c.repository_requirements_missing:
52 if not c.repository_requirements_missing:
53 c.rhodecode_repo = self.rhodecode_vcs_repo
53 c.rhodecode_repo = self.rhodecode_vcs_repo
54 return c
54 return c
55
55
56 def _get_readme_data(self, db_repo, default_renderer):
56 def _get_readme_data(self, db_repo, renderer_type):
57 repo_name = db_repo.repo_name
57
58 log.debug('Looking for README file')
58 log.debug('Looking for README file')
59
59
60 @cache_region('long_term')
60 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
61 def _generate_readme(cache_key):
61 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
62 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
63 repo_id=self.db_repo.repo_id)
64 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
65
66 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
67 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
62 readme_data = None
68 readme_data = None
63 readme_node = None
69 readme_node = None
64 readme_filename = None
70 readme_filename = None
65 commit = self._get_landing_commit_or_none(db_repo)
71 commit = self._get_landing_commit_or_none(db_repo)
66 if commit:
72 if commit:
67 log.debug("Searching for a README file.")
73 log.debug("Searching for a README file.")
68 readme_node = ReadmeFinder(default_renderer).search(commit)
74 readme_node = ReadmeFinder(_renderer_type).search(commit)
69 if readme_node:
75 if readme_node:
70 relative_urls = {
76 relative_urls = {
71 'raw': h.route_path(
77 'raw': h.route_path(
72 'repo_file_raw', repo_name=repo_name,
78 'repo_file_raw', repo_name=_repo_name,
73 commit_id=commit.raw_id, f_path=readme_node.path),
79 commit_id=commit.raw_id, f_path=readme_node.path),
74 'standard': h.route_path(
80 'standard': h.route_path(
75 'repo_files', repo_name=repo_name,
81 'repo_files', repo_name=_repo_name,
76 commit_id=commit.raw_id, f_path=readme_node.path),
82 commit_id=commit.raw_id, f_path=readme_node.path),
77 }
83 }
78 readme_data = self._render_readme_or_none(
84 readme_data = self._render_readme_or_none(
79 commit, readme_node, relative_urls)
85 commit, readme_node, relative_urls)
80 readme_filename = readme_node.path
86 readme_filename = readme_node.path
81 return readme_data, readme_filename
87 return readme_data, readme_filename
82
88
83 invalidator_context = CacheKey.repo_context_cache(
89 start = time.time()
84 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
90 inv_context_manager = rc_cache.InvalidationContext(
91 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
92 with inv_context_manager as invalidation_context:
93 # check for stored invalidation signal, and maybe purge the cache
94 # before computing it again
95 if invalidation_context.should_invalidate():
96 generate_repo_readme.invalidate(
97 db_repo.repo_id, db_repo.repo_name, renderer_type)
85
98
86 with invalidator_context as context:
99 instance = generate_repo_readme(
87 context.invalidate()
100 db_repo.repo_id, db_repo.repo_name, renderer_type)
88 computed = context.compute()
101 compute_time = time.time() - start
89
102 log.debug('Repo readme generated and computed in %.3fs', compute_time)
90 return computed
103 return instance
91
104
92 def _get_landing_commit_or_none(self, db_repo):
105 def _get_landing_commit_or_none(self, db_repo):
93 log.debug("Getting the landing commit.")
106 log.debug("Getting the landing commit.")
94 try:
107 try:
95 commit = db_repo.get_landing_commit()
108 commit = db_repo.get_landing_commit()
96 if not isinstance(commit, EmptyCommit):
109 if not isinstance(commit, EmptyCommit):
97 return commit
110 return commit
98 else:
111 else:
99 log.debug("Repository is empty, no README to render.")
112 log.debug("Repository is empty, no README to render.")
100 except CommitError:
113 except CommitError:
101 log.exception(
114 log.exception(
102 "Problem getting commit when trying to render the README.")
115 "Problem getting commit when trying to render the README.")
103
116
104 def _render_readme_or_none(self, commit, readme_node, relative_urls):
117 def _render_readme_or_none(self, commit, readme_node, relative_urls):
105 log.debug(
118 log.debug(
106 'Found README file `%s` rendering...', readme_node.path)
119 'Found README file `%s` rendering...', readme_node.path)
107 renderer = MarkupRenderer()
120 renderer = MarkupRenderer()
108 try:
121 try:
109 html_source = renderer.render(
122 html_source = renderer.render(
110 readme_node.content, filename=readme_node.path)
123 readme_node.content, filename=readme_node.path)
111 if relative_urls:
124 if relative_urls:
112 return relative_links(html_source, relative_urls)
125 return relative_links(html_source, relative_urls)
113 return html_source
126 return html_source
114 except Exception:
127 except Exception:
115 log.exception(
128 log.exception(
116 "Exception while trying to render the README")
129 "Exception while trying to render the README")
117
130
118 def _load_commits_context(self, c):
131 def _load_commits_context(self, c):
119 p = safe_int(self.request.GET.get('page'), 1)
132 p = safe_int(self.request.GET.get('page'), 1)
120 size = safe_int(self.request.GET.get('size'), 10)
133 size = safe_int(self.request.GET.get('size'), 10)
121
134
122 def url_generator(**kw):
135 def url_generator(**kw):
123 query_params = {
136 query_params = {
124 'size': size
137 'size': size
125 }
138 }
126 query_params.update(kw)
139 query_params.update(kw)
127 return h.route_path(
140 return h.route_path(
128 'repo_summary_commits',
141 'repo_summary_commits',
129 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
142 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
130
143
131 pre_load = ['author', 'branch', 'date', 'message']
144 pre_load = ['author', 'branch', 'date', 'message']
132 try:
145 try:
133 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
146 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
134 except EmptyRepositoryError:
147 except EmptyRepositoryError:
135 collection = self.rhodecode_vcs_repo
148 collection = self.rhodecode_vcs_repo
136
149
137 c.repo_commits = h.RepoPage(
150 c.repo_commits = h.RepoPage(
138 collection, page=p, items_per_page=size, url=url_generator)
151 collection, page=p, items_per_page=size, url=url_generator)
139 page_ids = [x.raw_id for x in c.repo_commits]
152 page_ids = [x.raw_id for x in c.repo_commits]
140 c.comments = self.db_repo.get_comments(page_ids)
153 c.comments = self.db_repo.get_comments(page_ids)
141 c.statuses = self.db_repo.statuses(page_ids)
154 c.statuses = self.db_repo.statuses(page_ids)
142
155
143 @LoginRequired()
156 @LoginRequired()
144 @HasRepoPermissionAnyDecorator(
157 @HasRepoPermissionAnyDecorator(
145 'repository.read', 'repository.write', 'repository.admin')
158 'repository.read', 'repository.write', 'repository.admin')
146 @view_config(
159 @view_config(
147 route_name='repo_summary_commits', request_method='GET',
160 route_name='repo_summary_commits', request_method='GET',
148 renderer='rhodecode:templates/summary/summary_commits.mako')
161 renderer='rhodecode:templates/summary/summary_commits.mako')
149 def summary_commits(self):
162 def summary_commits(self):
150 c = self.load_default_context()
163 c = self.load_default_context()
151 self._load_commits_context(c)
164 self._load_commits_context(c)
152 return self._get_template_context(c)
165 return self._get_template_context(c)
153
166
154 @LoginRequired()
167 @LoginRequired()
155 @HasRepoPermissionAnyDecorator(
168 @HasRepoPermissionAnyDecorator(
156 'repository.read', 'repository.write', 'repository.admin')
169 'repository.read', 'repository.write', 'repository.admin')
157 @view_config(
170 @view_config(
158 route_name='repo_summary', request_method='GET',
171 route_name='repo_summary', request_method='GET',
159 renderer='rhodecode:templates/summary/summary.mako')
172 renderer='rhodecode:templates/summary/summary.mako')
160 @view_config(
173 @view_config(
161 route_name='repo_summary_slash', request_method='GET',
174 route_name='repo_summary_slash', request_method='GET',
162 renderer='rhodecode:templates/summary/summary.mako')
175 renderer='rhodecode:templates/summary/summary.mako')
163 @view_config(
176 @view_config(
164 route_name='repo_summary_explicit', request_method='GET',
177 route_name='repo_summary_explicit', request_method='GET',
165 renderer='rhodecode:templates/summary/summary.mako')
178 renderer='rhodecode:templates/summary/summary.mako')
166 def summary(self):
179 def summary(self):
167 c = self.load_default_context()
180 c = self.load_default_context()
168
181
169 # Prepare the clone URL
182 # Prepare the clone URL
170 username = ''
183 username = ''
171 if self._rhodecode_user.username != User.DEFAULT_USER:
184 if self._rhodecode_user.username != User.DEFAULT_USER:
172 username = safe_str(self._rhodecode_user.username)
185 username = safe_str(self._rhodecode_user.username)
173
186
174 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
187 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
175 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
188 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
176
189
177 if '{repo}' in _def_clone_uri:
190 if '{repo}' in _def_clone_uri:
178 _def_clone_uri_id = _def_clone_uri.replace(
191 _def_clone_uri_id = _def_clone_uri.replace(
179 '{repo}', '_{repoid}')
192 '{repo}', '_{repoid}')
180 elif '{repoid}' in _def_clone_uri:
193 elif '{repoid}' in _def_clone_uri:
181 _def_clone_uri_id = _def_clone_uri.replace(
194 _def_clone_uri_id = _def_clone_uri.replace(
182 '_{repoid}', '{repo}')
195 '_{repoid}', '{repo}')
183
196
184 c.clone_repo_url = self.db_repo.clone_url(
197 c.clone_repo_url = self.db_repo.clone_url(
185 user=username, uri_tmpl=_def_clone_uri)
198 user=username, uri_tmpl=_def_clone_uri)
186 c.clone_repo_url_id = self.db_repo.clone_url(
199 c.clone_repo_url_id = self.db_repo.clone_url(
187 user=username, uri_tmpl=_def_clone_uri_id)
200 user=username, uri_tmpl=_def_clone_uri_id)
188 c.clone_repo_url_ssh = self.db_repo.clone_url(
201 c.clone_repo_url_ssh = self.db_repo.clone_url(
189 uri_tmpl=_def_clone_uri_ssh, ssh=True)
202 uri_tmpl=_def_clone_uri_ssh, ssh=True)
190
203
191 # If enabled, get statistics data
204 # If enabled, get statistics data
192
205
193 c.show_stats = bool(self.db_repo.enable_statistics)
206 c.show_stats = bool(self.db_repo.enable_statistics)
194
207
195 stats = Session().query(Statistics) \
208 stats = Session().query(Statistics) \
196 .filter(Statistics.repository == self.db_repo) \
209 .filter(Statistics.repository == self.db_repo) \
197 .scalar()
210 .scalar()
198
211
199 c.stats_percentage = 0
212 c.stats_percentage = 0
200
213
201 if stats and stats.languages:
214 if stats and stats.languages:
202 c.no_data = False is self.db_repo.enable_statistics
215 c.no_data = False is self.db_repo.enable_statistics
203 lang_stats_d = json.loads(stats.languages)
216 lang_stats_d = json.loads(stats.languages)
204
217
205 # Sort first by decreasing count and second by the file extension,
218 # Sort first by decreasing count and second by the file extension,
206 # so we have a consistent output.
219 # so we have a consistent output.
207 lang_stats_items = sorted(lang_stats_d.iteritems(),
220 lang_stats_items = sorted(lang_stats_d.iteritems(),
208 key=lambda k: (-k[1], k[0]))[:10]
221 key=lambda k: (-k[1], k[0]))[:10]
209 lang_stats = [(x, {"count": y,
222 lang_stats = [(x, {"count": y,
210 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
223 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
211 for x, y in lang_stats_items]
224 for x, y in lang_stats_items]
212
225
213 c.trending_languages = json.dumps(lang_stats)
226 c.trending_languages = json.dumps(lang_stats)
214 else:
227 else:
215 c.no_data = True
228 c.no_data = True
216 c.trending_languages = json.dumps({})
229 c.trending_languages = json.dumps({})
217
230
218 scm_model = ScmModel()
231 scm_model = ScmModel()
219 c.enable_downloads = self.db_repo.enable_downloads
232 c.enable_downloads = self.db_repo.enable_downloads
220 c.repository_followers = scm_model.get_followers(self.db_repo)
233 c.repository_followers = scm_model.get_followers(self.db_repo)
221 c.repository_forks = scm_model.get_forks(self.db_repo)
234 c.repository_forks = scm_model.get_forks(self.db_repo)
222 c.repository_is_user_following = scm_model.is_following_repo(
235 c.repository_is_user_following = scm_model.is_following_repo(
223 self.db_repo_name, self._rhodecode_user.user_id)
236 self.db_repo_name, self._rhodecode_user.user_id)
224
237
225 # first interaction with the VCS instance after here...
238 # first interaction with the VCS instance after here...
226 if c.repository_requirements_missing:
239 if c.repository_requirements_missing:
227 self.request.override_renderer = \
240 self.request.override_renderer = \
228 'rhodecode:templates/summary/missing_requirements.mako'
241 'rhodecode:templates/summary/missing_requirements.mako'
229 return self._get_template_context(c)
242 return self._get_template_context(c)
230
243
231 c.readme_data, c.readme_file = \
244 c.readme_data, c.readme_file = \
232 self._get_readme_data(self.db_repo, c.visual.default_renderer)
245 self._get_readme_data(self.db_repo, c.visual.default_renderer)
233
246
234 # loads the summary commits template context
247 # loads the summary commits template context
235 self._load_commits_context(c)
248 self._load_commits_context(c)
236
249
237 return self._get_template_context(c)
250 return self._get_template_context(c)
238
251
239 def get_request_commit_id(self):
252 def get_request_commit_id(self):
240 return self.request.matchdict['commit_id']
253 return self.request.matchdict['commit_id']
241
254
242 @LoginRequired()
255 @LoginRequired()
243 @HasRepoPermissionAnyDecorator(
256 @HasRepoPermissionAnyDecorator(
244 'repository.read', 'repository.write', 'repository.admin')
257 'repository.read', 'repository.write', 'repository.admin')
245 @view_config(
258 @view_config(
246 route_name='repo_stats', request_method='GET',
259 route_name='repo_stats', request_method='GET',
247 renderer='json_ext')
260 renderer='json_ext')
248 def repo_stats(self):
261 def repo_stats(self):
249 commit_id = self.get_request_commit_id()
262 commit_id = self.get_request_commit_id()
250 show_stats = bool(self.db_repo.enable_statistics)
263 show_stats = bool(self.db_repo.enable_statistics)
251 repo_id = self.db_repo.repo_id
264 repo_id = self.db_repo.repo_id
252
265
253 cache_seconds = safe_int(
266 cache_seconds = safe_int(
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
267 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 cache_on = cache_seconds > 0
268 cache_on = cache_seconds > 0
256 log.debug(
269 log.debug(
257 'Computing REPO TREE for repo_id %s commit_id `%s` '
270 'Computing REPO TREE for repo_id %s commit_id `%s` '
258 'with caching: %s[TTL: %ss]' % (
271 'with caching: %s[TTL: %ss]' % (
259 repo_id, commit_id, cache_on, cache_seconds or 0))
272 repo_id, commit_id, cache_on, cache_seconds or 0))
260
273
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
274 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
275 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263
276
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
277 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
265 condition=cache_on)
278 condition=cache_on)
266 def compute_stats(repo_id, commit_id, show_stats):
279 def compute_stats(repo_id, commit_id, show_stats):
267 code_stats = {}
280 code_stats = {}
268 size = 0
281 size = 0
269 try:
282 try:
270 scm_instance = self.db_repo.scm_instance()
283 scm_instance = self.db_repo.scm_instance()
271 commit = scm_instance.get_commit(commit_id)
284 commit = scm_instance.get_commit(commit_id)
272
285
273 for node in commit.get_filenodes_generator():
286 for node in commit.get_filenodes_generator():
274 size += node.size
287 size += node.size
275 if not show_stats:
288 if not show_stats:
276 continue
289 continue
277 ext = string.lower(node.extension)
290 ext = string.lower(node.extension)
278 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
291 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
279 if ext_info:
292 if ext_info:
280 if ext in code_stats:
293 if ext in code_stats:
281 code_stats[ext]['count'] += 1
294 code_stats[ext]['count'] += 1
282 else:
295 else:
283 code_stats[ext] = {"count": 1, "desc": ext_info}
296 code_stats[ext] = {"count": 1, "desc": ext_info}
284 except (EmptyRepositoryError, CommitDoesNotExistError):
297 except (EmptyRepositoryError, CommitDoesNotExistError):
285 pass
298 pass
286 return {'size': h.format_byte_size_binary(size),
299 return {'size': h.format_byte_size_binary(size),
287 'code_stats': code_stats}
300 'code_stats': code_stats}
288
301
289 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
302 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
290 return stats
303 return stats
291
304
292 @LoginRequired()
305 @LoginRequired()
293 @HasRepoPermissionAnyDecorator(
306 @HasRepoPermissionAnyDecorator(
294 'repository.read', 'repository.write', 'repository.admin')
307 'repository.read', 'repository.write', 'repository.admin')
295 @view_config(
308 @view_config(
296 route_name='repo_refs_data', request_method='GET',
309 route_name='repo_refs_data', request_method='GET',
297 renderer='json_ext')
310 renderer='json_ext')
298 def repo_refs_data(self):
311 def repo_refs_data(self):
299 _ = self.request.translate
312 _ = self.request.translate
300 self.load_default_context()
313 self.load_default_context()
301
314
302 repo = self.rhodecode_vcs_repo
315 repo = self.rhodecode_vcs_repo
303 refs_to_create = [
316 refs_to_create = [
304 (_("Branch"), repo.branches, 'branch'),
317 (_("Branch"), repo.branches, 'branch'),
305 (_("Tag"), repo.tags, 'tag'),
318 (_("Tag"), repo.tags, 'tag'),
306 (_("Bookmark"), repo.bookmarks, 'book'),
319 (_("Bookmark"), repo.bookmarks, 'book'),
307 ]
320 ]
308 res = self._create_reference_data(
321 res = self._create_reference_data(
309 repo, self.db_repo_name, refs_to_create)
322 repo, self.db_repo_name, refs_to_create)
310 data = {
323 data = {
311 'more': False,
324 'more': False,
312 'results': res
325 'results': res
313 }
326 }
314 return data
327 return data
315
328
316 @LoginRequired()
329 @LoginRequired()
317 @HasRepoPermissionAnyDecorator(
330 @HasRepoPermissionAnyDecorator(
318 'repository.read', 'repository.write', 'repository.admin')
331 'repository.read', 'repository.write', 'repository.admin')
319 @view_config(
332 @view_config(
320 route_name='repo_refs_changelog_data', request_method='GET',
333 route_name='repo_refs_changelog_data', request_method='GET',
321 renderer='json_ext')
334 renderer='json_ext')
322 def repo_refs_changelog_data(self):
335 def repo_refs_changelog_data(self):
323 _ = self.request.translate
336 _ = self.request.translate
324 self.load_default_context()
337 self.load_default_context()
325
338
326 repo = self.rhodecode_vcs_repo
339 repo = self.rhodecode_vcs_repo
327
340
328 refs_to_create = [
341 refs_to_create = [
329 (_("Branches"), repo.branches, 'branch'),
342 (_("Branches"), repo.branches, 'branch'),
330 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
343 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
331 # TODO: enable when vcs can handle bookmarks filters
344 # TODO: enable when vcs can handle bookmarks filters
332 # (_("Bookmarks"), repo.bookmarks, "book"),
345 # (_("Bookmarks"), repo.bookmarks, "book"),
333 ]
346 ]
334 res = self._create_reference_data(
347 res = self._create_reference_data(
335 repo, self.db_repo_name, refs_to_create)
348 repo, self.db_repo_name, refs_to_create)
336 data = {
349 data = {
337 'more': False,
350 'more': False,
338 'results': res
351 'results': res
339 }
352 }
340 return data
353 return data
341
354
342 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
355 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
343 format_ref_id = utils.get_format_ref_id(repo)
356 format_ref_id = utils.get_format_ref_id(repo)
344
357
345 result = []
358 result = []
346 for title, refs, ref_type in refs_to_create:
359 for title, refs, ref_type in refs_to_create:
347 if refs:
360 if refs:
348 result.append({
361 result.append({
349 'text': title,
362 'text': title,
350 'children': self._create_reference_items(
363 'children': self._create_reference_items(
351 repo, full_repo_name, refs, ref_type,
364 repo, full_repo_name, refs, ref_type,
352 format_ref_id),
365 format_ref_id),
353 })
366 })
354 return result
367 return result
355
368
356 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
369 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
357 format_ref_id):
370 format_ref_id):
358 result = []
371 result = []
359 is_svn = h.is_svn(repo)
372 is_svn = h.is_svn(repo)
360 for ref_name, raw_id in refs.iteritems():
373 for ref_name, raw_id in refs.iteritems():
361 files_url = self._create_files_url(
374 files_url = self._create_files_url(
362 repo, full_repo_name, ref_name, raw_id, is_svn)
375 repo, full_repo_name, ref_name, raw_id, is_svn)
363 result.append({
376 result.append({
364 'text': ref_name,
377 'text': ref_name,
365 'id': format_ref_id(ref_name, raw_id),
378 'id': format_ref_id(ref_name, raw_id),
366 'raw_id': raw_id,
379 'raw_id': raw_id,
367 'type': ref_type,
380 'type': ref_type,
368 'files_url': files_url,
381 'files_url': files_url,
369 })
382 })
370 return result
383 return result
371
384
372 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
385 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
373 use_commit_id = '/' in ref_name or is_svn
386 use_commit_id = '/' in ref_name or is_svn
374 return h.route_path(
387 return h.route_path(
375 'repo_files',
388 'repo_files',
376 repo_name=full_repo_name,
389 repo_name=full_repo_name,
377 f_path=ref_name if is_svn else '',
390 f_path=ref_name if is_svn else '',
378 commit_id=raw_id if use_commit_id else ref_name,
391 commit_id=raw_id if use_commit_id else ref_name,
379 _query=dict(at=ref_name))
392 _query=dict(at=ref_name))
@@ -1,759 +1,759 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Authentication modules
22 Authentication modules
23 """
23 """
24 import socket
24 import socket
25 import string
25 import string
26 import colander
26 import colander
27 import copy
27 import copy
28 import logging
28 import logging
29 import time
29 import time
30 import traceback
30 import traceback
31 import warnings
31 import warnings
32 import functools
32 import functools
33
33
34 from pyramid.threadlocal import get_current_registry
34 from pyramid.threadlocal import get_current_registry
35
35
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.lib import caches, rc_cache
38 from rhodecode.lib import rc_cache
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
40 from rhodecode.lib.utils2 import safe_int, safe_str
40 from rhodecode.lib.utils2 import safe_int, safe_str
41 from rhodecode.lib.exceptions import LdapConnectionError
41 from rhodecode.lib.exceptions import LdapConnectionError
42 from rhodecode.model.db import User
42 from rhodecode.model.db import User
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.user import UserModel
45 from rhodecode.model.user import UserModel
46 from rhodecode.model.user_group import UserGroupModel
46 from rhodecode.model.user_group import UserGroupModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51 # auth types that authenticate() function can receive
51 # auth types that authenticate() function can receive
52 VCS_TYPE = 'vcs'
52 VCS_TYPE = 'vcs'
53 HTTP_TYPE = 'http'
53 HTTP_TYPE = 'http'
54
54
55
55
56 class hybrid_property(object):
56 class hybrid_property(object):
57 """
57 """
58 a property decorator that works both for instance and class
58 a property decorator that works both for instance and class
59 """
59 """
60 def __init__(self, fget, fset=None, fdel=None, expr=None):
60 def __init__(self, fget, fset=None, fdel=None, expr=None):
61 self.fget = fget
61 self.fget = fget
62 self.fset = fset
62 self.fset = fset
63 self.fdel = fdel
63 self.fdel = fdel
64 self.expr = expr or fget
64 self.expr = expr or fget
65 functools.update_wrapper(self, fget)
65 functools.update_wrapper(self, fget)
66
66
67 def __get__(self, instance, owner):
67 def __get__(self, instance, owner):
68 if instance is None:
68 if instance is None:
69 return self.expr(owner)
69 return self.expr(owner)
70 else:
70 else:
71 return self.fget(instance)
71 return self.fget(instance)
72
72
73 def __set__(self, instance, value):
73 def __set__(self, instance, value):
74 self.fset(instance, value)
74 self.fset(instance, value)
75
75
76 def __delete__(self, instance):
76 def __delete__(self, instance):
77 self.fdel(instance)
77 self.fdel(instance)
78
78
79
79
80 class LazyFormencode(object):
80 class LazyFormencode(object):
81 def __init__(self, formencode_obj, *args, **kwargs):
81 def __init__(self, formencode_obj, *args, **kwargs):
82 self.formencode_obj = formencode_obj
82 self.formencode_obj = formencode_obj
83 self.args = args
83 self.args = args
84 self.kwargs = kwargs
84 self.kwargs = kwargs
85
85
86 def __call__(self, *args, **kwargs):
86 def __call__(self, *args, **kwargs):
87 from inspect import isfunction
87 from inspect import isfunction
88 formencode_obj = self.formencode_obj
88 formencode_obj = self.formencode_obj
89 if isfunction(formencode_obj):
89 if isfunction(formencode_obj):
90 # case we wrap validators into functions
90 # case we wrap validators into functions
91 formencode_obj = self.formencode_obj(*args, **kwargs)
91 formencode_obj = self.formencode_obj(*args, **kwargs)
92 return formencode_obj(*self.args, **self.kwargs)
92 return formencode_obj(*self.args, **self.kwargs)
93
93
94
94
95 class RhodeCodeAuthPluginBase(object):
95 class RhodeCodeAuthPluginBase(object):
96 # cache the authentication request for N amount of seconds. Some kind
96 # cache the authentication request for N amount of seconds. Some kind
97 # of authentication methods are very heavy and it's very efficient to cache
97 # of authentication methods are very heavy and it's very efficient to cache
98 # the result of a call. If it's set to None (default) cache is off
98 # the result of a call. If it's set to None (default) cache is off
99 AUTH_CACHE_TTL = None
99 AUTH_CACHE_TTL = None
100 AUTH_CACHE = {}
100 AUTH_CACHE = {}
101
101
102 auth_func_attrs = {
102 auth_func_attrs = {
103 "username": "unique username",
103 "username": "unique username",
104 "firstname": "first name",
104 "firstname": "first name",
105 "lastname": "last name",
105 "lastname": "last name",
106 "email": "email address",
106 "email": "email address",
107 "groups": '["list", "of", "groups"]',
107 "groups": '["list", "of", "groups"]',
108 "user_group_sync":
108 "user_group_sync":
109 'True|False defines if returned user groups should be synced',
109 'True|False defines if returned user groups should be synced',
110 "extern_name": "name in external source of record",
110 "extern_name": "name in external source of record",
111 "extern_type": "type of external source of record",
111 "extern_type": "type of external source of record",
112 "admin": 'True|False defines if user should be RhodeCode super admin',
112 "admin": 'True|False defines if user should be RhodeCode super admin',
113 "active":
113 "active":
114 'True|False defines active state of user internally for RhodeCode',
114 'True|False defines active state of user internally for RhodeCode',
115 "active_from_extern":
115 "active_from_extern":
116 "True|False\None, active state from the external auth, "
116 "True|False\None, active state from the external auth, "
117 "None means use definition from RhodeCode extern_type active value"
117 "None means use definition from RhodeCode extern_type active value"
118
118
119 }
119 }
120 # set on authenticate() method and via set_auth_type func.
120 # set on authenticate() method and via set_auth_type func.
121 auth_type = None
121 auth_type = None
122
122
123 # set on authenticate() method and via set_calling_scope_repo, this is a
123 # set on authenticate() method and via set_calling_scope_repo, this is a
124 # calling scope repository when doing authentication most likely on VCS
124 # calling scope repository when doing authentication most likely on VCS
125 # operations
125 # operations
126 acl_repo_name = None
126 acl_repo_name = None
127
127
128 # List of setting names to store encrypted. Plugins may override this list
128 # List of setting names to store encrypted. Plugins may override this list
129 # to store settings encrypted.
129 # to store settings encrypted.
130 _settings_encrypted = []
130 _settings_encrypted = []
131
131
132 # Mapping of python to DB settings model types. Plugins may override or
132 # Mapping of python to DB settings model types. Plugins may override or
133 # extend this mapping.
133 # extend this mapping.
134 _settings_type_map = {
134 _settings_type_map = {
135 colander.String: 'unicode',
135 colander.String: 'unicode',
136 colander.Integer: 'int',
136 colander.Integer: 'int',
137 colander.Boolean: 'bool',
137 colander.Boolean: 'bool',
138 colander.List: 'list',
138 colander.List: 'list',
139 }
139 }
140
140
141 # list of keys in settings that are unsafe to be logged, should be passwords
141 # list of keys in settings that are unsafe to be logged, should be passwords
142 # or other crucial credentials
142 # or other crucial credentials
143 _settings_unsafe_keys = []
143 _settings_unsafe_keys = []
144
144
145 def __init__(self, plugin_id):
145 def __init__(self, plugin_id):
146 self._plugin_id = plugin_id
146 self._plugin_id = plugin_id
147
147
148 def __str__(self):
148 def __str__(self):
149 return self.get_id()
149 return self.get_id()
150
150
151 def _get_setting_full_name(self, name):
151 def _get_setting_full_name(self, name):
152 """
152 """
153 Return the full setting name used for storing values in the database.
153 Return the full setting name used for storing values in the database.
154 """
154 """
155 # TODO: johbo: Using the name here is problematic. It would be good to
155 # TODO: johbo: Using the name here is problematic. It would be good to
156 # introduce either new models in the database to hold Plugin and
156 # introduce either new models in the database to hold Plugin and
157 # PluginSetting or to use the plugin id here.
157 # PluginSetting or to use the plugin id here.
158 return 'auth_{}_{}'.format(self.name, name)
158 return 'auth_{}_{}'.format(self.name, name)
159
159
160 def _get_setting_type(self, name):
160 def _get_setting_type(self, name):
161 """
161 """
162 Return the type of a setting. This type is defined by the SettingsModel
162 Return the type of a setting. This type is defined by the SettingsModel
163 and determines how the setting is stored in DB. Optionally the suffix
163 and determines how the setting is stored in DB. Optionally the suffix
164 `.encrypted` is appended to instruct SettingsModel to store it
164 `.encrypted` is appended to instruct SettingsModel to store it
165 encrypted.
165 encrypted.
166 """
166 """
167 schema_node = self.get_settings_schema().get(name)
167 schema_node = self.get_settings_schema().get(name)
168 db_type = self._settings_type_map.get(
168 db_type = self._settings_type_map.get(
169 type(schema_node.typ), 'unicode')
169 type(schema_node.typ), 'unicode')
170 if name in self._settings_encrypted:
170 if name in self._settings_encrypted:
171 db_type = '{}.encrypted'.format(db_type)
171 db_type = '{}.encrypted'.format(db_type)
172 return db_type
172 return db_type
173
173
174 def is_enabled(self):
174 def is_enabled(self):
175 """
175 """
176 Returns true if this plugin is enabled. An enabled plugin can be
176 Returns true if this plugin is enabled. An enabled plugin can be
177 configured in the admin interface but it is not consulted during
177 configured in the admin interface but it is not consulted during
178 authentication.
178 authentication.
179 """
179 """
180 auth_plugins = SettingsModel().get_auth_plugins()
180 auth_plugins = SettingsModel().get_auth_plugins()
181 return self.get_id() in auth_plugins
181 return self.get_id() in auth_plugins
182
182
183 def is_active(self, plugin_cached_settings=None):
183 def is_active(self, plugin_cached_settings=None):
184 """
184 """
185 Returns true if the plugin is activated. An activated plugin is
185 Returns true if the plugin is activated. An activated plugin is
186 consulted during authentication, assumed it is also enabled.
186 consulted during authentication, assumed it is also enabled.
187 """
187 """
188 return self.get_setting_by_name(
188 return self.get_setting_by_name(
189 'enabled', plugin_cached_settings=plugin_cached_settings)
189 'enabled', plugin_cached_settings=plugin_cached_settings)
190
190
191 def get_id(self):
191 def get_id(self):
192 """
192 """
193 Returns the plugin id.
193 Returns the plugin id.
194 """
194 """
195 return self._plugin_id
195 return self._plugin_id
196
196
197 def get_display_name(self):
197 def get_display_name(self):
198 """
198 """
199 Returns a translation string for displaying purposes.
199 Returns a translation string for displaying purposes.
200 """
200 """
201 raise NotImplementedError('Not implemented in base class')
201 raise NotImplementedError('Not implemented in base class')
202
202
203 def get_settings_schema(self):
203 def get_settings_schema(self):
204 """
204 """
205 Returns a colander schema, representing the plugin settings.
205 Returns a colander schema, representing the plugin settings.
206 """
206 """
207 return AuthnPluginSettingsSchemaBase()
207 return AuthnPluginSettingsSchemaBase()
208
208
209 def get_settings(self):
209 def get_settings(self):
210 """
210 """
211 Returns the plugin settings as dictionary.
211 Returns the plugin settings as dictionary.
212 """
212 """
213 settings = {}
213 settings = {}
214 raw_settings = SettingsModel().get_all_settings()
214 raw_settings = SettingsModel().get_all_settings()
215 for node in self.get_settings_schema():
215 for node in self.get_settings_schema():
216 settings[node.name] = self.get_setting_by_name(
216 settings[node.name] = self.get_setting_by_name(
217 node.name, plugin_cached_settings=raw_settings)
217 node.name, plugin_cached_settings=raw_settings)
218 return settings
218 return settings
219
219
220 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
220 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
221 """
221 """
222 Returns a plugin setting by name.
222 Returns a plugin setting by name.
223 """
223 """
224 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
224 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
225 if plugin_cached_settings:
225 if plugin_cached_settings:
226 plugin_settings = plugin_cached_settings
226 plugin_settings = plugin_cached_settings
227 else:
227 else:
228 plugin_settings = SettingsModel().get_all_settings()
228 plugin_settings = SettingsModel().get_all_settings()
229
229
230 if full_name in plugin_settings:
230 if full_name in plugin_settings:
231 return plugin_settings[full_name]
231 return plugin_settings[full_name]
232 else:
232 else:
233 return default
233 return default
234
234
235 def create_or_update_setting(self, name, value):
235 def create_or_update_setting(self, name, value):
236 """
236 """
237 Create or update a setting for this plugin in the persistent storage.
237 Create or update a setting for this plugin in the persistent storage.
238 """
238 """
239 full_name = self._get_setting_full_name(name)
239 full_name = self._get_setting_full_name(name)
240 type_ = self._get_setting_type(name)
240 type_ = self._get_setting_type(name)
241 db_setting = SettingsModel().create_or_update_setting(
241 db_setting = SettingsModel().create_or_update_setting(
242 full_name, value, type_)
242 full_name, value, type_)
243 return db_setting.app_settings_value
243 return db_setting.app_settings_value
244
244
245 def log_safe_settings(self, settings):
245 def log_safe_settings(self, settings):
246 """
246 """
247 returns a log safe representation of settings, without any secrets
247 returns a log safe representation of settings, without any secrets
248 """
248 """
249 settings_copy = copy.deepcopy(settings)
249 settings_copy = copy.deepcopy(settings)
250 for k in self._settings_unsafe_keys:
250 for k in self._settings_unsafe_keys:
251 if k in settings_copy:
251 if k in settings_copy:
252 del settings_copy[k]
252 del settings_copy[k]
253 return settings_copy
253 return settings_copy
254
254
255 @hybrid_property
255 @hybrid_property
256 def name(self):
256 def name(self):
257 """
257 """
258 Returns the name of this authentication plugin.
258 Returns the name of this authentication plugin.
259
259
260 :returns: string
260 :returns: string
261 """
261 """
262 raise NotImplementedError("Not implemented in base class")
262 raise NotImplementedError("Not implemented in base class")
263
263
264 def get_url_slug(self):
264 def get_url_slug(self):
265 """
265 """
266 Returns a slug which should be used when constructing URLs which refer
266 Returns a slug which should be used when constructing URLs which refer
267 to this plugin. By default it returns the plugin name. If the name is
267 to this plugin. By default it returns the plugin name. If the name is
268 not suitable for using it in an URL the plugin should override this
268 not suitable for using it in an URL the plugin should override this
269 method.
269 method.
270 """
270 """
271 return self.name
271 return self.name
272
272
273 @property
273 @property
274 def is_headers_auth(self):
274 def is_headers_auth(self):
275 """
275 """
276 Returns True if this authentication plugin uses HTTP headers as
276 Returns True if this authentication plugin uses HTTP headers as
277 authentication method.
277 authentication method.
278 """
278 """
279 return False
279 return False
280
280
281 @hybrid_property
281 @hybrid_property
282 def is_container_auth(self):
282 def is_container_auth(self):
283 """
283 """
284 Deprecated method that indicates if this authentication plugin uses
284 Deprecated method that indicates if this authentication plugin uses
285 HTTP headers as authentication method.
285 HTTP headers as authentication method.
286 """
286 """
287 warnings.warn(
287 warnings.warn(
288 'Use is_headers_auth instead.', category=DeprecationWarning)
288 'Use is_headers_auth instead.', category=DeprecationWarning)
289 return self.is_headers_auth
289 return self.is_headers_auth
290
290
291 @hybrid_property
291 @hybrid_property
292 def allows_creating_users(self):
292 def allows_creating_users(self):
293 """
293 """
294 Defines if Plugin allows users to be created on-the-fly when
294 Defines if Plugin allows users to be created on-the-fly when
295 authentication is called. Controls how external plugins should behave
295 authentication is called. Controls how external plugins should behave
296 in terms if they are allowed to create new users, or not. Base plugins
296 in terms if they are allowed to create new users, or not. Base plugins
297 should not be allowed to, but External ones should be !
297 should not be allowed to, but External ones should be !
298
298
299 :return: bool
299 :return: bool
300 """
300 """
301 return False
301 return False
302
302
303 def set_auth_type(self, auth_type):
303 def set_auth_type(self, auth_type):
304 self.auth_type = auth_type
304 self.auth_type = auth_type
305
305
306 def set_calling_scope_repo(self, acl_repo_name):
306 def set_calling_scope_repo(self, acl_repo_name):
307 self.acl_repo_name = acl_repo_name
307 self.acl_repo_name = acl_repo_name
308
308
309 def allows_authentication_from(
309 def allows_authentication_from(
310 self, user, allows_non_existing_user=True,
310 self, user, allows_non_existing_user=True,
311 allowed_auth_plugins=None, allowed_auth_sources=None):
311 allowed_auth_plugins=None, allowed_auth_sources=None):
312 """
312 """
313 Checks if this authentication module should accept a request for
313 Checks if this authentication module should accept a request for
314 the current user.
314 the current user.
315
315
316 :param user: user object fetched using plugin's get_user() method.
316 :param user: user object fetched using plugin's get_user() method.
317 :param allows_non_existing_user: if True, don't allow the
317 :param allows_non_existing_user: if True, don't allow the
318 user to be empty, meaning not existing in our database
318 user to be empty, meaning not existing in our database
319 :param allowed_auth_plugins: if provided, users extern_type will be
319 :param allowed_auth_plugins: if provided, users extern_type will be
320 checked against a list of provided extern types, which are plugin
320 checked against a list of provided extern types, which are plugin
321 auth_names in the end
321 auth_names in the end
322 :param allowed_auth_sources: authentication type allowed,
322 :param allowed_auth_sources: authentication type allowed,
323 `http` or `vcs` default is both.
323 `http` or `vcs` default is both.
324 defines if plugin will accept only http authentication vcs
324 defines if plugin will accept only http authentication vcs
325 authentication(git/hg) or both
325 authentication(git/hg) or both
326 :returns: boolean
326 :returns: boolean
327 """
327 """
328 if not user and not allows_non_existing_user:
328 if not user and not allows_non_existing_user:
329 log.debug('User is empty but plugin does not allow empty users,'
329 log.debug('User is empty but plugin does not allow empty users,'
330 'not allowed to authenticate')
330 'not allowed to authenticate')
331 return False
331 return False
332
332
333 expected_auth_plugins = allowed_auth_plugins or [self.name]
333 expected_auth_plugins = allowed_auth_plugins or [self.name]
334 if user and (user.extern_type and
334 if user and (user.extern_type and
335 user.extern_type not in expected_auth_plugins):
335 user.extern_type not in expected_auth_plugins):
336 log.debug(
336 log.debug(
337 'User `%s` is bound to `%s` auth type. Plugin allows only '
337 'User `%s` is bound to `%s` auth type. Plugin allows only '
338 '%s, skipping', user, user.extern_type, expected_auth_plugins)
338 '%s, skipping', user, user.extern_type, expected_auth_plugins)
339
339
340 return False
340 return False
341
341
342 # by default accept both
342 # by default accept both
343 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
343 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
344 if self.auth_type not in expected_auth_from:
344 if self.auth_type not in expected_auth_from:
345 log.debug('Current auth source is %s but plugin only allows %s',
345 log.debug('Current auth source is %s but plugin only allows %s',
346 self.auth_type, expected_auth_from)
346 self.auth_type, expected_auth_from)
347 return False
347 return False
348
348
349 return True
349 return True
350
350
351 def get_user(self, username=None, **kwargs):
351 def get_user(self, username=None, **kwargs):
352 """
352 """
353 Helper method for user fetching in plugins, by default it's using
353 Helper method for user fetching in plugins, by default it's using
354 simple fetch by username, but this method can be custimized in plugins
354 simple fetch by username, but this method can be custimized in plugins
355 eg. headers auth plugin to fetch user by environ params
355 eg. headers auth plugin to fetch user by environ params
356
356
357 :param username: username if given to fetch from database
357 :param username: username if given to fetch from database
358 :param kwargs: extra arguments needed for user fetching.
358 :param kwargs: extra arguments needed for user fetching.
359 """
359 """
360 user = None
360 user = None
361 log.debug(
361 log.debug(
362 'Trying to fetch user `%s` from RhodeCode database', username)
362 'Trying to fetch user `%s` from RhodeCode database', username)
363 if username:
363 if username:
364 user = User.get_by_username(username)
364 user = User.get_by_username(username)
365 if not user:
365 if not user:
366 log.debug('User not found, fallback to fetch user in '
366 log.debug('User not found, fallback to fetch user in '
367 'case insensitive mode')
367 'case insensitive mode')
368 user = User.get_by_username(username, case_insensitive=True)
368 user = User.get_by_username(username, case_insensitive=True)
369 else:
369 else:
370 log.debug('provided username:`%s` is empty skipping...', username)
370 log.debug('provided username:`%s` is empty skipping...', username)
371 if not user:
371 if not user:
372 log.debug('User `%s` not found in database', username)
372 log.debug('User `%s` not found in database', username)
373 else:
373 else:
374 log.debug('Got DB user:%s', user)
374 log.debug('Got DB user:%s', user)
375 return user
375 return user
376
376
377 def user_activation_state(self):
377 def user_activation_state(self):
378 """
378 """
379 Defines user activation state when creating new users
379 Defines user activation state when creating new users
380
380
381 :returns: boolean
381 :returns: boolean
382 """
382 """
383 raise NotImplementedError("Not implemented in base class")
383 raise NotImplementedError("Not implemented in base class")
384
384
385 def auth(self, userobj, username, passwd, settings, **kwargs):
385 def auth(self, userobj, username, passwd, settings, **kwargs):
386 """
386 """
387 Given a user object (which may be null), username, a plaintext
387 Given a user object (which may be null), username, a plaintext
388 password, and a settings object (containing all the keys needed as
388 password, and a settings object (containing all the keys needed as
389 listed in settings()), authenticate this user's login attempt.
389 listed in settings()), authenticate this user's login attempt.
390
390
391 Return None on failure. On success, return a dictionary of the form:
391 Return None on failure. On success, return a dictionary of the form:
392
392
393 see: RhodeCodeAuthPluginBase.auth_func_attrs
393 see: RhodeCodeAuthPluginBase.auth_func_attrs
394 This is later validated for correctness
394 This is later validated for correctness
395 """
395 """
396 raise NotImplementedError("not implemented in base class")
396 raise NotImplementedError("not implemented in base class")
397
397
398 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
398 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
399 """
399 """
400 Wrapper to call self.auth() that validates call on it
400 Wrapper to call self.auth() that validates call on it
401
401
402 :param userobj: userobj
402 :param userobj: userobj
403 :param username: username
403 :param username: username
404 :param passwd: plaintext password
404 :param passwd: plaintext password
405 :param settings: plugin settings
405 :param settings: plugin settings
406 """
406 """
407 auth = self.auth(userobj, username, passwd, settings, **kwargs)
407 auth = self.auth(userobj, username, passwd, settings, **kwargs)
408 if auth:
408 if auth:
409 auth['_plugin'] = self.name
409 auth['_plugin'] = self.name
410 auth['_ttl_cache'] = self.get_ttl_cache(settings)
410 auth['_ttl_cache'] = self.get_ttl_cache(settings)
411 # check if hash should be migrated ?
411 # check if hash should be migrated ?
412 new_hash = auth.get('_hash_migrate')
412 new_hash = auth.get('_hash_migrate')
413 if new_hash:
413 if new_hash:
414 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
414 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
415 if 'user_group_sync' not in auth:
415 if 'user_group_sync' not in auth:
416 auth['user_group_sync'] = False
416 auth['user_group_sync'] = False
417 return self._validate_auth_return(auth)
417 return self._validate_auth_return(auth)
418 return auth
418 return auth
419
419
420 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
420 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
421 new_hash_cypher = _RhodeCodeCryptoBCrypt()
421 new_hash_cypher = _RhodeCodeCryptoBCrypt()
422 # extra checks, so make sure new hash is correct.
422 # extra checks, so make sure new hash is correct.
423 password_encoded = safe_str(password)
423 password_encoded = safe_str(password)
424 if new_hash and new_hash_cypher.hash_check(
424 if new_hash and new_hash_cypher.hash_check(
425 password_encoded, new_hash):
425 password_encoded, new_hash):
426 cur_user = User.get_by_username(username)
426 cur_user = User.get_by_username(username)
427 cur_user.password = new_hash
427 cur_user.password = new_hash
428 Session().add(cur_user)
428 Session().add(cur_user)
429 Session().flush()
429 Session().flush()
430 log.info('Migrated user %s hash to bcrypt', cur_user)
430 log.info('Migrated user %s hash to bcrypt', cur_user)
431
431
432 def _validate_auth_return(self, ret):
432 def _validate_auth_return(self, ret):
433 if not isinstance(ret, dict):
433 if not isinstance(ret, dict):
434 raise Exception('returned value from auth must be a dict')
434 raise Exception('returned value from auth must be a dict')
435 for k in self.auth_func_attrs:
435 for k in self.auth_func_attrs:
436 if k not in ret:
436 if k not in ret:
437 raise Exception('Missing %s attribute from returned data' % k)
437 raise Exception('Missing %s attribute from returned data' % k)
438 return ret
438 return ret
439
439
440 def get_ttl_cache(self, settings=None):
440 def get_ttl_cache(self, settings=None):
441 plugin_settings = settings or self.get_settings()
441 plugin_settings = settings or self.get_settings()
442 cache_ttl = 0
442 cache_ttl = 0
443
443
444 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
444 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
445 # plugin cache set inside is more important than the settings value
445 # plugin cache set inside is more important than the settings value
446 cache_ttl = self.AUTH_CACHE_TTL
446 cache_ttl = self.AUTH_CACHE_TTL
447 elif plugin_settings.get('cache_ttl'):
447 elif plugin_settings.get('cache_ttl'):
448 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
448 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
449
449
450 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
450 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
451 return plugin_cache_active, cache_ttl
451 return plugin_cache_active, cache_ttl
452
452
453
453
454 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
454 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
455
455
456 @hybrid_property
456 @hybrid_property
457 def allows_creating_users(self):
457 def allows_creating_users(self):
458 return True
458 return True
459
459
460 def use_fake_password(self):
460 def use_fake_password(self):
461 """
461 """
462 Return a boolean that indicates whether or not we should set the user's
462 Return a boolean that indicates whether or not we should set the user's
463 password to a random value when it is authenticated by this plugin.
463 password to a random value when it is authenticated by this plugin.
464 If your plugin provides authentication, then you will generally
464 If your plugin provides authentication, then you will generally
465 want this.
465 want this.
466
466
467 :returns: boolean
467 :returns: boolean
468 """
468 """
469 raise NotImplementedError("Not implemented in base class")
469 raise NotImplementedError("Not implemented in base class")
470
470
471 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
471 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
472 # at this point _authenticate calls plugin's `auth()` function
472 # at this point _authenticate calls plugin's `auth()` function
473 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
473 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
474 userobj, username, passwd, settings, **kwargs)
474 userobj, username, passwd, settings, **kwargs)
475
475
476 if auth:
476 if auth:
477 # maybe plugin will clean the username ?
477 # maybe plugin will clean the username ?
478 # we should use the return value
478 # we should use the return value
479 username = auth['username']
479 username = auth['username']
480
480
481 # if external source tells us that user is not active, we should
481 # if external source tells us that user is not active, we should
482 # skip rest of the process. This can prevent from creating users in
482 # skip rest of the process. This can prevent from creating users in
483 # RhodeCode when using external authentication, but if it's
483 # RhodeCode when using external authentication, but if it's
484 # inactive user we shouldn't create that user anyway
484 # inactive user we shouldn't create that user anyway
485 if auth['active_from_extern'] is False:
485 if auth['active_from_extern'] is False:
486 log.warning(
486 log.warning(
487 "User %s authenticated against %s, but is inactive",
487 "User %s authenticated against %s, but is inactive",
488 username, self.__module__)
488 username, self.__module__)
489 return None
489 return None
490
490
491 cur_user = User.get_by_username(username, case_insensitive=True)
491 cur_user = User.get_by_username(username, case_insensitive=True)
492 is_user_existing = cur_user is not None
492 is_user_existing = cur_user is not None
493
493
494 if is_user_existing:
494 if is_user_existing:
495 log.debug('Syncing user `%s` from '
495 log.debug('Syncing user `%s` from '
496 '`%s` plugin', username, self.name)
496 '`%s` plugin', username, self.name)
497 else:
497 else:
498 log.debug('Creating non existing user `%s` from '
498 log.debug('Creating non existing user `%s` from '
499 '`%s` plugin', username, self.name)
499 '`%s` plugin', username, self.name)
500
500
501 if self.allows_creating_users:
501 if self.allows_creating_users:
502 log.debug('Plugin `%s` allows to '
502 log.debug('Plugin `%s` allows to '
503 'create new users', self.name)
503 'create new users', self.name)
504 else:
504 else:
505 log.debug('Plugin `%s` does not allow to '
505 log.debug('Plugin `%s` does not allow to '
506 'create new users', self.name)
506 'create new users', self.name)
507
507
508 user_parameters = {
508 user_parameters = {
509 'username': username,
509 'username': username,
510 'email': auth["email"],
510 'email': auth["email"],
511 'firstname': auth["firstname"],
511 'firstname': auth["firstname"],
512 'lastname': auth["lastname"],
512 'lastname': auth["lastname"],
513 'active': auth["active"],
513 'active': auth["active"],
514 'admin': auth["admin"],
514 'admin': auth["admin"],
515 'extern_name': auth["extern_name"],
515 'extern_name': auth["extern_name"],
516 'extern_type': self.name,
516 'extern_type': self.name,
517 'plugin': self,
517 'plugin': self,
518 'allow_to_create_user': self.allows_creating_users,
518 'allow_to_create_user': self.allows_creating_users,
519 }
519 }
520
520
521 if not is_user_existing:
521 if not is_user_existing:
522 if self.use_fake_password():
522 if self.use_fake_password():
523 # Randomize the PW because we don't need it, but don't want
523 # Randomize the PW because we don't need it, but don't want
524 # them blank either
524 # them blank either
525 passwd = PasswordGenerator().gen_password(length=16)
525 passwd = PasswordGenerator().gen_password(length=16)
526 user_parameters['password'] = passwd
526 user_parameters['password'] = passwd
527 else:
527 else:
528 # Since the password is required by create_or_update method of
528 # Since the password is required by create_or_update method of
529 # UserModel, we need to set it explicitly.
529 # UserModel, we need to set it explicitly.
530 # The create_or_update method is smart and recognises the
530 # The create_or_update method is smart and recognises the
531 # password hashes as well.
531 # password hashes as well.
532 user_parameters['password'] = cur_user.password
532 user_parameters['password'] = cur_user.password
533
533
534 # we either create or update users, we also pass the flag
534 # we either create or update users, we also pass the flag
535 # that controls if this method can actually do that.
535 # that controls if this method can actually do that.
536 # raises NotAllowedToCreateUserError if it cannot, and we try to.
536 # raises NotAllowedToCreateUserError if it cannot, and we try to.
537 user = UserModel().create_or_update(**user_parameters)
537 user = UserModel().create_or_update(**user_parameters)
538 Session().flush()
538 Session().flush()
539 # enforce user is just in given groups, all of them has to be ones
539 # enforce user is just in given groups, all of them has to be ones
540 # created from plugins. We store this info in _group_data JSON
540 # created from plugins. We store this info in _group_data JSON
541 # field
541 # field
542
542
543 if auth['user_group_sync']:
543 if auth['user_group_sync']:
544 try:
544 try:
545 groups = auth['groups'] or []
545 groups = auth['groups'] or []
546 log.debug(
546 log.debug(
547 'Performing user_group sync based on set `%s` '
547 'Performing user_group sync based on set `%s` '
548 'returned by `%s` plugin', groups, self.name)
548 'returned by `%s` plugin', groups, self.name)
549 UserGroupModel().enforce_groups(user, groups, self.name)
549 UserGroupModel().enforce_groups(user, groups, self.name)
550 except Exception:
550 except Exception:
551 # for any reason group syncing fails, we should
551 # for any reason group syncing fails, we should
552 # proceed with login
552 # proceed with login
553 log.error(traceback.format_exc())
553 log.error(traceback.format_exc())
554
554
555 Session().commit()
555 Session().commit()
556 return auth
556 return auth
557
557
558
558
559 class AuthLdapBase(object):
559 class AuthLdapBase(object):
560
560
561 @classmethod
561 @classmethod
562 def _build_servers(cls, ldap_server_type, ldap_server, port):
562 def _build_servers(cls, ldap_server_type, ldap_server, port):
563 def host_resolver(host, port, full_resolve=True):
563 def host_resolver(host, port, full_resolve=True):
564 """
564 """
565 Main work for this function is to prevent ldap connection issues,
565 Main work for this function is to prevent ldap connection issues,
566 and detect them early using a "greenified" sockets
566 and detect them early using a "greenified" sockets
567 """
567 """
568 host = host.strip()
568 host = host.strip()
569 if not full_resolve:
569 if not full_resolve:
570 return '{}:{}'.format(host, port)
570 return '{}:{}'.format(host, port)
571
571
572 log.debug('LDAP: Resolving IP for LDAP host %s', host)
572 log.debug('LDAP: Resolving IP for LDAP host %s', host)
573 try:
573 try:
574 ip = socket.gethostbyname(host)
574 ip = socket.gethostbyname(host)
575 log.debug('Got LDAP server %s ip %s', host, ip)
575 log.debug('Got LDAP server %s ip %s', host, ip)
576 except Exception:
576 except Exception:
577 raise LdapConnectionError(
577 raise LdapConnectionError(
578 'Failed to resolve host: `{}`'.format(host))
578 'Failed to resolve host: `{}`'.format(host))
579
579
580 log.debug('LDAP: Checking if IP %s is accessible', ip)
580 log.debug('LDAP: Checking if IP %s is accessible', ip)
581 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
581 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
582 try:
582 try:
583 s.connect((ip, int(port)))
583 s.connect((ip, int(port)))
584 s.shutdown(socket.SHUT_RD)
584 s.shutdown(socket.SHUT_RD)
585 except Exception:
585 except Exception:
586 raise LdapConnectionError(
586 raise LdapConnectionError(
587 'Failed to connect to host: `{}:{}`'.format(host, port))
587 'Failed to connect to host: `{}:{}`'.format(host, port))
588
588
589 return '{}:{}'.format(host, port)
589 return '{}:{}'.format(host, port)
590
590
591 if len(ldap_server) == 1:
591 if len(ldap_server) == 1:
592 # in case of single server use resolver to detect potential
592 # in case of single server use resolver to detect potential
593 # connection issues
593 # connection issues
594 full_resolve = True
594 full_resolve = True
595 else:
595 else:
596 full_resolve = False
596 full_resolve = False
597
597
598 return ', '.join(
598 return ', '.join(
599 ["{}://{}".format(
599 ["{}://{}".format(
600 ldap_server_type,
600 ldap_server_type,
601 host_resolver(host, port, full_resolve=full_resolve))
601 host_resolver(host, port, full_resolve=full_resolve))
602 for host in ldap_server])
602 for host in ldap_server])
603
603
604 @classmethod
604 @classmethod
605 def _get_server_list(cls, servers):
605 def _get_server_list(cls, servers):
606 return map(string.strip, servers.split(','))
606 return map(string.strip, servers.split(','))
607
607
608 @classmethod
608 @classmethod
609 def get_uid(cls, username, server_addresses):
609 def get_uid(cls, username, server_addresses):
610 uid = username
610 uid = username
611 for server_addr in server_addresses:
611 for server_addr in server_addresses:
612 uid = chop_at(username, "@%s" % server_addr)
612 uid = chop_at(username, "@%s" % server_addr)
613 return uid
613 return uid
614
614
615
615
616 def loadplugin(plugin_id):
616 def loadplugin(plugin_id):
617 """
617 """
618 Loads and returns an instantiated authentication plugin.
618 Loads and returns an instantiated authentication plugin.
619 Returns the RhodeCodeAuthPluginBase subclass on success,
619 Returns the RhodeCodeAuthPluginBase subclass on success,
620 or None on failure.
620 or None on failure.
621 """
621 """
622 # TODO: Disusing pyramids thread locals to retrieve the registry.
622 # TODO: Disusing pyramids thread locals to retrieve the registry.
623 authn_registry = get_authn_registry()
623 authn_registry = get_authn_registry()
624 plugin = authn_registry.get_plugin(plugin_id)
624 plugin = authn_registry.get_plugin(plugin_id)
625 if plugin is None:
625 if plugin is None:
626 log.error('Authentication plugin not found: "%s"', plugin_id)
626 log.error('Authentication plugin not found: "%s"', plugin_id)
627 return plugin
627 return plugin
628
628
629
629
630 def get_authn_registry(registry=None):
630 def get_authn_registry(registry=None):
631 registry = registry or get_current_registry()
631 registry = registry or get_current_registry()
632 authn_registry = registry.getUtility(IAuthnPluginRegistry)
632 authn_registry = registry.getUtility(IAuthnPluginRegistry)
633 return authn_registry
633 return authn_registry
634
634
635
635
636 def authenticate(username, password, environ=None, auth_type=None,
636 def authenticate(username, password, environ=None, auth_type=None,
637 skip_missing=False, registry=None, acl_repo_name=None):
637 skip_missing=False, registry=None, acl_repo_name=None):
638 """
638 """
639 Authentication function used for access control,
639 Authentication function used for access control,
640 It tries to authenticate based on enabled authentication modules.
640 It tries to authenticate based on enabled authentication modules.
641
641
642 :param username: username can be empty for headers auth
642 :param username: username can be empty for headers auth
643 :param password: password can be empty for headers auth
643 :param password: password can be empty for headers auth
644 :param environ: environ headers passed for headers auth
644 :param environ: environ headers passed for headers auth
645 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
645 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
646 :param skip_missing: ignores plugins that are in db but not in environment
646 :param skip_missing: ignores plugins that are in db but not in environment
647 :returns: None if auth failed, plugin_user dict if auth is correct
647 :returns: None if auth failed, plugin_user dict if auth is correct
648 """
648 """
649 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
649 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
650 raise ValueError('auth type must be on of http, vcs got "%s" instead'
650 raise ValueError('auth type must be on of http, vcs got "%s" instead'
651 % auth_type)
651 % auth_type)
652 headers_only = environ and not (username and password)
652 headers_only = environ and not (username and password)
653
653
654 authn_registry = get_authn_registry(registry)
654 authn_registry = get_authn_registry(registry)
655 plugins_to_check = authn_registry.get_plugins_for_authentication()
655 plugins_to_check = authn_registry.get_plugins_for_authentication()
656 log.debug('Starting ordered authentication chain using %s plugins',
656 log.debug('Starting ordered authentication chain using %s plugins',
657 [x.name for x in plugins_to_check])
657 [x.name for x in plugins_to_check])
658 for plugin in plugins_to_check:
658 for plugin in plugins_to_check:
659 plugin.set_auth_type(auth_type)
659 plugin.set_auth_type(auth_type)
660 plugin.set_calling_scope_repo(acl_repo_name)
660 plugin.set_calling_scope_repo(acl_repo_name)
661
661
662 if headers_only and not plugin.is_headers_auth:
662 if headers_only and not plugin.is_headers_auth:
663 log.debug('Auth type is for headers only and plugin `%s` is not '
663 log.debug('Auth type is for headers only and plugin `%s` is not '
664 'headers plugin, skipping...', plugin.get_id())
664 'headers plugin, skipping...', plugin.get_id())
665 continue
665 continue
666
666
667 log.debug('Trying authentication using ** %s **', plugin.get_id())
667 log.debug('Trying authentication using ** %s **', plugin.get_id())
668
668
669 # load plugin settings from RhodeCode database
669 # load plugin settings from RhodeCode database
670 plugin_settings = plugin.get_settings()
670 plugin_settings = plugin.get_settings()
671 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
671 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
672 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
672 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
673
673
674 # use plugin's method of user extraction.
674 # use plugin's method of user extraction.
675 user = plugin.get_user(username, environ=environ,
675 user = plugin.get_user(username, environ=environ,
676 settings=plugin_settings)
676 settings=plugin_settings)
677 display_user = user.username if user else username
677 display_user = user.username if user else username
678 log.debug(
678 log.debug(
679 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
679 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
680
680
681 if not plugin.allows_authentication_from(user):
681 if not plugin.allows_authentication_from(user):
682 log.debug('Plugin %s does not accept user `%s` for authentication',
682 log.debug('Plugin %s does not accept user `%s` for authentication',
683 plugin.get_id(), display_user)
683 plugin.get_id(), display_user)
684 continue
684 continue
685 else:
685 else:
686 log.debug('Plugin %s accepted user `%s` for authentication',
686 log.debug('Plugin %s accepted user `%s` for authentication',
687 plugin.get_id(), display_user)
687 plugin.get_id(), display_user)
688
688
689 log.info('Authenticating user `%s` using %s plugin',
689 log.info('Authenticating user `%s` using %s plugin',
690 display_user, plugin.get_id())
690 display_user, plugin.get_id())
691
691
692 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
692 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
693
693
694 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
694 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
695 plugin.get_id(), plugin_cache_active, cache_ttl)
695 plugin.get_id(), plugin_cache_active, cache_ttl)
696
696
697 user_id = user.user_id if user else None
697 user_id = user.user_id if user else None
698 # don't cache for empty users
698 # don't cache for empty users
699 plugin_cache_active = plugin_cache_active and user_id
699 plugin_cache_active = plugin_cache_active and user_id
700 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
700 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
701 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
701 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
702
702
703 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
703 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
704 expiration_time=cache_ttl,
704 expiration_time=cache_ttl,
705 condition=plugin_cache_active)
705 condition=plugin_cache_active)
706 def compute_auth(
706 def compute_auth(
707 cache_name, plugin_name, username, password):
707 cache_name, plugin_name, username, password):
708
708
709 # _authenticate is a wrapper for .auth() method of plugin.
709 # _authenticate is a wrapper for .auth() method of plugin.
710 # it checks if .auth() sends proper data.
710 # it checks if .auth() sends proper data.
711 # For RhodeCodeExternalAuthPlugin it also maps users to
711 # For RhodeCodeExternalAuthPlugin it also maps users to
712 # Database and maps the attributes returned from .auth()
712 # Database and maps the attributes returned from .auth()
713 # to RhodeCode database. If this function returns data
713 # to RhodeCode database. If this function returns data
714 # then auth is correct.
714 # then auth is correct.
715 log.debug('Running plugin `%s` _authenticate method '
715 log.debug('Running plugin `%s` _authenticate method '
716 'using username and password', plugin.get_id())
716 'using username and password', plugin.get_id())
717 return plugin._authenticate(
717 return plugin._authenticate(
718 user, username, password, plugin_settings,
718 user, username, password, plugin_settings,
719 environ=environ or {})
719 environ=environ or {})
720
720
721 start = time.time()
721 start = time.time()
722 # for environ based auth, password can be empty, but then the validation is
722 # for environ based auth, password can be empty, but then the validation is
723 # on the server that fills in the env data needed for authentication
723 # on the server that fills in the env data needed for authentication
724 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
724 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
725
725
726 auth_time = time.time() - start
726 auth_time = time.time() - start
727 log.debug('Authentication for plugin `%s` completed in %.3fs, '
727 log.debug('Authentication for plugin `%s` completed in %.3fs, '
728 'expiration time of fetched cache %.1fs.',
728 'expiration time of fetched cache %.1fs.',
729 plugin.get_id(), auth_time, cache_ttl)
729 plugin.get_id(), auth_time, cache_ttl)
730
730
731 log.debug('PLUGIN USER DATA: %s', plugin_user)
731 log.debug('PLUGIN USER DATA: %s', plugin_user)
732
732
733 if plugin_user:
733 if plugin_user:
734 log.debug('Plugin returned proper authentication data')
734 log.debug('Plugin returned proper authentication data')
735 return plugin_user
735 return plugin_user
736 # we failed to Auth because .auth() method didn't return proper user
736 # we failed to Auth because .auth() method didn't return proper user
737 log.debug("User `%s` failed to authenticate against %s",
737 log.debug("User `%s` failed to authenticate against %s",
738 display_user, plugin.get_id())
738 display_user, plugin.get_id())
739
739
740 # case when we failed to authenticate against all defined plugins
740 # case when we failed to authenticate against all defined plugins
741 return None
741 return None
742
742
743
743
744 def chop_at(s, sub, inclusive=False):
744 def chop_at(s, sub, inclusive=False):
745 """Truncate string ``s`` at the first occurrence of ``sub``.
745 """Truncate string ``s`` at the first occurrence of ``sub``.
746
746
747 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
747 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
748
748
749 >>> chop_at("plutocratic brats", "rat")
749 >>> chop_at("plutocratic brats", "rat")
750 'plutoc'
750 'plutoc'
751 >>> chop_at("plutocratic brats", "rat", True)
751 >>> chop_at("plutocratic brats", "rat", True)
752 'plutocrat'
752 'plutocrat'
753 """
753 """
754 pos = s.find(sub)
754 pos = s.find(sub)
755 if pos == -1:
755 if pos == -1:
756 return s
756 return s
757 if inclusive:
757 if inclusive:
758 return s[:pos+len(sub)]
758 return s[:pos+len(sub)]
759 return s[:pos]
759 return s[:pos]
@@ -1,522 +1,535 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import tempfile
25 import tempfile
26
26
27 from paste.gzipper import make_gzip_middleware
27 from paste.gzipper import make_gzip_middleware
28 import pyramid.events
28 import pyramid.events
29 from pyramid.wsgi import wsgiapp
29 from pyramid.wsgi import wsgiapp
30 from pyramid.authorization import ACLAuthorizationPolicy
30 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.config import Configurator
31 from pyramid.config import Configurator
32 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.httpexceptions import (
33 from pyramid.httpexceptions import (
34 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
34 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 from pyramid.renderers import render_to_response
35 from pyramid.renderers import render_to_response
36
36
37 from rhodecode.model import meta
37 from rhodecode.model import meta
38 from rhodecode.config import patches
38 from rhodecode.config import patches
39 from rhodecode.config import utils as config_utils
39 from rhodecode.config import utils as config_utils
40 from rhodecode.config.environment import load_pyramid_environment
40 from rhodecode.config.environment import load_pyramid_environment
41
41
42 import rhodecode.events
42 import rhodecode.events
43 from rhodecode.lib.middleware.vcs import VCSMiddleware
43 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 from rhodecode.lib.request import Request
44 from rhodecode.lib.request import Request
45 from rhodecode.lib.vcs import VCSCommunicationError
45 from rhodecode.lib.vcs import VCSCommunicationError
46 from rhodecode.lib.exceptions import VCSServerUnavailable
46 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
47 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.https_fixup import HttpsFixup
48 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 from rhodecode.lib.celerylib.loader import configure_celery
49 from rhodecode.lib.celerylib.loader import configure_celery
50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
51 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 from rhodecode.lib.exc_tracking import store_exception
52 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.subscribers import (
53 from rhodecode.subscribers import (
54 scan_repositories_if_enabled, write_js_routes_if_enabled,
54 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 write_metadata_if_needed, inject_app_settings)
55 write_metadata_if_needed, inject_app_settings)
56
56
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60
60
61 def is_http_error(response):
61 def is_http_error(response):
62 # error which should have traceback
62 # error which should have traceback
63 return response.status_code > 499
63 return response.status_code > 499
64
64
65
65
66 def make_pyramid_app(global_config, **settings):
66 def make_pyramid_app(global_config, **settings):
67 """
67 """
68 Constructs the WSGI application based on Pyramid.
68 Constructs the WSGI application based on Pyramid.
69
69
70 Specials:
70 Specials:
71
71
72 * The application can also be integrated like a plugin via the call to
72 * The application can also be integrated like a plugin via the call to
73 `includeme`. This is accompanied with the other utility functions which
73 `includeme`. This is accompanied with the other utility functions which
74 are called. Changing this should be done with great care to not break
74 are called. Changing this should be done with great care to not break
75 cases when these fragments are assembled from another place.
75 cases when these fragments are assembled from another place.
76
76
77 """
77 """
78
78
79 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
79 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
80 # will be replaced by the value of the environment variable "NAME" in this case.
80 # will be replaced by the value of the environment variable "NAME" in this case.
81 environ = {
81 environ = {
82 'ENV_{}'.format(key): value for key, value in os.environ.items()}
82 'ENV_{}'.format(key): value for key, value in os.environ.items()}
83
83
84 global_config = _substitute_values(global_config, environ)
84 global_config = _substitute_values(global_config, environ)
85 settings = _substitute_values(settings, environ)
85 settings = _substitute_values(settings, environ)
86
86
87 sanitize_settings_and_apply_defaults(settings)
87 sanitize_settings_and_apply_defaults(settings)
88
88
89 config = Configurator(settings=settings)
89 config = Configurator(settings=settings)
90
90
91 # Apply compatibility patches
91 # Apply compatibility patches
92 patches.inspect_getargspec()
92 patches.inspect_getargspec()
93
93
94 load_pyramid_environment(global_config, settings)
94 load_pyramid_environment(global_config, settings)
95
95
96 # Static file view comes first
96 # Static file view comes first
97 includeme_first(config)
97 includeme_first(config)
98
98
99 includeme(config)
99 includeme(config)
100
100
101 pyramid_app = config.make_wsgi_app()
101 pyramid_app = config.make_wsgi_app()
102 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
102 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
103 pyramid_app.config = config
103 pyramid_app.config = config
104
104
105 config.configure_celery(global_config['__file__'])
105 config.configure_celery(global_config['__file__'])
106 # creating the app uses a connection - return it after we are done
106 # creating the app uses a connection - return it after we are done
107 meta.Session.remove()
107 meta.Session.remove()
108
108
109 log.info('Pyramid app %s created and configured.', pyramid_app)
109 log.info('Pyramid app %s created and configured.', pyramid_app)
110 return pyramid_app
110 return pyramid_app
111
111
112
112
113 def not_found_view(request):
113 def not_found_view(request):
114 """
114 """
115 This creates the view which should be registered as not-found-view to
115 This creates the view which should be registered as not-found-view to
116 pyramid.
116 pyramid.
117 """
117 """
118
118
119 if not getattr(request, 'vcs_call', None):
119 if not getattr(request, 'vcs_call', None):
120 # handle like regular case with our error_handler
120 # handle like regular case with our error_handler
121 return error_handler(HTTPNotFound(), request)
121 return error_handler(HTTPNotFound(), request)
122
122
123 # handle not found view as a vcs call
123 # handle not found view as a vcs call
124 settings = request.registry.settings
124 settings = request.registry.settings
125 ae_client = getattr(request, 'ae_client', None)
125 ae_client = getattr(request, 'ae_client', None)
126 vcs_app = VCSMiddleware(
126 vcs_app = VCSMiddleware(
127 HTTPNotFound(), request.registry, settings,
127 HTTPNotFound(), request.registry, settings,
128 appenlight_client=ae_client)
128 appenlight_client=ae_client)
129
129
130 return wsgiapp(vcs_app)(None, request)
130 return wsgiapp(vcs_app)(None, request)
131
131
132
132
133 def error_handler(exception, request):
133 def error_handler(exception, request):
134 import rhodecode
134 import rhodecode
135 from rhodecode.lib import helpers
135 from rhodecode.lib import helpers
136
136
137 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
137 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
138
138
139 base_response = HTTPInternalServerError()
139 base_response = HTTPInternalServerError()
140 # prefer original exception for the response since it may have headers set
140 # prefer original exception for the response since it may have headers set
141 if isinstance(exception, HTTPException):
141 if isinstance(exception, HTTPException):
142 base_response = exception
142 base_response = exception
143 elif isinstance(exception, VCSCommunicationError):
143 elif isinstance(exception, VCSCommunicationError):
144 base_response = VCSServerUnavailable()
144 base_response = VCSServerUnavailable()
145
145
146 if is_http_error(base_response):
146 if is_http_error(base_response):
147 log.exception(
147 log.exception(
148 'error occurred handling this request for path: %s', request.path)
148 'error occurred handling this request for path: %s', request.path)
149
149
150 error_explanation = base_response.explanation or str(base_response)
150 error_explanation = base_response.explanation or str(base_response)
151 if base_response.status_code == 404:
151 if base_response.status_code == 404:
152 error_explanation += " Or you don't have permission to access it."
152 error_explanation += " Or you don't have permission to access it."
153 c = AttributeDict()
153 c = AttributeDict()
154 c.error_message = base_response.status
154 c.error_message = base_response.status
155 c.error_explanation = error_explanation
155 c.error_explanation = error_explanation
156 c.visual = AttributeDict()
156 c.visual = AttributeDict()
157
157
158 c.visual.rhodecode_support_url = (
158 c.visual.rhodecode_support_url = (
159 request.registry.settings.get('rhodecode_support_url') or
159 request.registry.settings.get('rhodecode_support_url') or
160 request.route_url('rhodecode_support')
160 request.route_url('rhodecode_support')
161 )
161 )
162 c.redirect_time = 0
162 c.redirect_time = 0
163 c.rhodecode_name = rhodecode_title
163 c.rhodecode_name = rhodecode_title
164 if not c.rhodecode_name:
164 if not c.rhodecode_name:
165 c.rhodecode_name = 'Rhodecode'
165 c.rhodecode_name = 'Rhodecode'
166
166
167 c.causes = []
167 c.causes = []
168 if is_http_error(base_response):
168 if is_http_error(base_response):
169 c.causes.append('Server is overloaded.')
169 c.causes.append('Server is overloaded.')
170 c.causes.append('Server database connection is lost.')
170 c.causes.append('Server database connection is lost.')
171 c.causes.append('Server expected unhandled error.')
171 c.causes.append('Server expected unhandled error.')
172
172
173 if hasattr(base_response, 'causes'):
173 if hasattr(base_response, 'causes'):
174 c.causes = base_response.causes
174 c.causes = base_response.causes
175
175
176 c.messages = helpers.flash.pop_messages(request=request)
176 c.messages = helpers.flash.pop_messages(request=request)
177
177
178 exc_info = sys.exc_info()
178 exc_info = sys.exc_info()
179 c.exception_id = id(exc_info)
179 c.exception_id = id(exc_info)
180 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
180 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
181 or base_response.status_code > 499
181 or base_response.status_code > 499
182 c.exception_id_url = request.route_url(
182 c.exception_id_url = request.route_url(
183 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
183 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
184
184
185 if c.show_exception_id:
185 if c.show_exception_id:
186 store_exception(c.exception_id, exc_info)
186 store_exception(c.exception_id, exc_info)
187
187
188 response = render_to_response(
188 response = render_to_response(
189 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
189 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
190 response=base_response)
190 response=base_response)
191
191
192 return response
192 return response
193
193
194
194
195 def includeme_first(config):
195 def includeme_first(config):
196 # redirect automatic browser favicon.ico requests to correct place
196 # redirect automatic browser favicon.ico requests to correct place
197 def favicon_redirect(context, request):
197 def favicon_redirect(context, request):
198 return HTTPFound(
198 return HTTPFound(
199 request.static_path('rhodecode:public/images/favicon.ico'))
199 request.static_path('rhodecode:public/images/favicon.ico'))
200
200
201 config.add_view(favicon_redirect, route_name='favicon')
201 config.add_view(favicon_redirect, route_name='favicon')
202 config.add_route('favicon', '/favicon.ico')
202 config.add_route('favicon', '/favicon.ico')
203
203
204 def robots_redirect(context, request):
204 def robots_redirect(context, request):
205 return HTTPFound(
205 return HTTPFound(
206 request.static_path('rhodecode:public/robots.txt'))
206 request.static_path('rhodecode:public/robots.txt'))
207
207
208 config.add_view(robots_redirect, route_name='robots')
208 config.add_view(robots_redirect, route_name='robots')
209 config.add_route('robots', '/robots.txt')
209 config.add_route('robots', '/robots.txt')
210
210
211 config.add_static_view(
211 config.add_static_view(
212 '_static/deform', 'deform:static')
212 '_static/deform', 'deform:static')
213 config.add_static_view(
213 config.add_static_view(
214 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
214 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
215
215
216
216
217 def includeme(config):
217 def includeme(config):
218 settings = config.registry.settings
218 settings = config.registry.settings
219 config.set_request_factory(Request)
219 config.set_request_factory(Request)
220
220
221 # plugin information
221 # plugin information
222 config.registry.rhodecode_plugins = collections.OrderedDict()
222 config.registry.rhodecode_plugins = collections.OrderedDict()
223
223
224 config.add_directive(
224 config.add_directive(
225 'register_rhodecode_plugin', register_rhodecode_plugin)
225 'register_rhodecode_plugin', register_rhodecode_plugin)
226
226
227 config.add_directive('configure_celery', configure_celery)
227 config.add_directive('configure_celery', configure_celery)
228
228
229 if asbool(settings.get('appenlight', 'false')):
229 if asbool(settings.get('appenlight', 'false')):
230 config.include('appenlight_client.ext.pyramid_tween')
230 config.include('appenlight_client.ext.pyramid_tween')
231
231
232 # Includes which are required. The application would fail without them.
232 # Includes which are required. The application would fail without them.
233 config.include('pyramid_mako')
233 config.include('pyramid_mako')
234 config.include('pyramid_beaker')
234 config.include('pyramid_beaker')
235 config.include('rhodecode.lib.caches')
236 config.include('rhodecode.lib.rc_cache')
235 config.include('rhodecode.lib.rc_cache')
237
236
238 config.include('rhodecode.authentication')
237 config.include('rhodecode.authentication')
239 config.include('rhodecode.integrations')
238 config.include('rhodecode.integrations')
240
239
241 # apps
240 # apps
242 config.include('rhodecode.apps._base')
241 config.include('rhodecode.apps._base')
243 config.include('rhodecode.apps.ops')
242 config.include('rhodecode.apps.ops')
244
243
245 config.include('rhodecode.apps.admin')
244 config.include('rhodecode.apps.admin')
246 config.include('rhodecode.apps.channelstream')
245 config.include('rhodecode.apps.channelstream')
247 config.include('rhodecode.apps.login')
246 config.include('rhodecode.apps.login')
248 config.include('rhodecode.apps.home')
247 config.include('rhodecode.apps.home')
249 config.include('rhodecode.apps.journal')
248 config.include('rhodecode.apps.journal')
250 config.include('rhodecode.apps.repository')
249 config.include('rhodecode.apps.repository')
251 config.include('rhodecode.apps.repo_group')
250 config.include('rhodecode.apps.repo_group')
252 config.include('rhodecode.apps.user_group')
251 config.include('rhodecode.apps.user_group')
253 config.include('rhodecode.apps.search')
252 config.include('rhodecode.apps.search')
254 config.include('rhodecode.apps.user_profile')
253 config.include('rhodecode.apps.user_profile')
255 config.include('rhodecode.apps.user_group_profile')
254 config.include('rhodecode.apps.user_group_profile')
256 config.include('rhodecode.apps.my_account')
255 config.include('rhodecode.apps.my_account')
257 config.include('rhodecode.apps.svn_support')
256 config.include('rhodecode.apps.svn_support')
258 config.include('rhodecode.apps.ssh_support')
257 config.include('rhodecode.apps.ssh_support')
259 config.include('rhodecode.apps.gist')
258 config.include('rhodecode.apps.gist')
260
259
261 config.include('rhodecode.apps.debug_style')
260 config.include('rhodecode.apps.debug_style')
262 config.include('rhodecode.tweens')
261 config.include('rhodecode.tweens')
263 config.include('rhodecode.api')
262 config.include('rhodecode.api')
264
263
265 config.add_route(
264 config.add_route(
266 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
265 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
267
266
268 config.add_translation_dirs('rhodecode:i18n/')
267 config.add_translation_dirs('rhodecode:i18n/')
269 settings['default_locale_name'] = settings.get('lang', 'en')
268 settings['default_locale_name'] = settings.get('lang', 'en')
270
269
271 # Add subscribers.
270 # Add subscribers.
272 config.add_subscriber(inject_app_settings,
271 config.add_subscriber(inject_app_settings,
273 pyramid.events.ApplicationCreated)
272 pyramid.events.ApplicationCreated)
274 config.add_subscriber(scan_repositories_if_enabled,
273 config.add_subscriber(scan_repositories_if_enabled,
275 pyramid.events.ApplicationCreated)
274 pyramid.events.ApplicationCreated)
276 config.add_subscriber(write_metadata_if_needed,
275 config.add_subscriber(write_metadata_if_needed,
277 pyramid.events.ApplicationCreated)
276 pyramid.events.ApplicationCreated)
278 config.add_subscriber(write_js_routes_if_enabled,
277 config.add_subscriber(write_js_routes_if_enabled,
279 pyramid.events.ApplicationCreated)
278 pyramid.events.ApplicationCreated)
280
279
281 # request custom methods
280 # request custom methods
282 config.add_request_method(
281 config.add_request_method(
283 'rhodecode.lib.partial_renderer.get_partial_renderer',
282 'rhodecode.lib.partial_renderer.get_partial_renderer',
284 'get_partial_renderer')
283 'get_partial_renderer')
285
284
286 # Set the authorization policy.
285 # Set the authorization policy.
287 authz_policy = ACLAuthorizationPolicy()
286 authz_policy = ACLAuthorizationPolicy()
288 config.set_authorization_policy(authz_policy)
287 config.set_authorization_policy(authz_policy)
289
288
290 # Set the default renderer for HTML templates to mako.
289 # Set the default renderer for HTML templates to mako.
291 config.add_mako_renderer('.html')
290 config.add_mako_renderer('.html')
292
291
293 config.add_renderer(
292 config.add_renderer(
294 name='json_ext',
293 name='json_ext',
295 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
294 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
296
295
297 # include RhodeCode plugins
296 # include RhodeCode plugins
298 includes = aslist(settings.get('rhodecode.includes', []))
297 includes = aslist(settings.get('rhodecode.includes', []))
299 for inc in includes:
298 for inc in includes:
300 config.include(inc)
299 config.include(inc)
301
300
302 # custom not found view, if our pyramid app doesn't know how to handle
301 # custom not found view, if our pyramid app doesn't know how to handle
303 # the request pass it to potential VCS handling ap
302 # the request pass it to potential VCS handling ap
304 config.add_notfound_view(not_found_view)
303 config.add_notfound_view(not_found_view)
305 if not settings.get('debugtoolbar.enabled', False):
304 if not settings.get('debugtoolbar.enabled', False):
306 # disabled debugtoolbar handle all exceptions via the error_handlers
305 # disabled debugtoolbar handle all exceptions via the error_handlers
307 config.add_view(error_handler, context=Exception)
306 config.add_view(error_handler, context=Exception)
308
307
309 # all errors including 403/404/50X
308 # all errors including 403/404/50X
310 config.add_view(error_handler, context=HTTPError)
309 config.add_view(error_handler, context=HTTPError)
311
310
312
311
313 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
312 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
314 """
313 """
315 Apply outer WSGI middlewares around the application.
314 Apply outer WSGI middlewares around the application.
316 """
315 """
317 registry = config.registry
316 registry = config.registry
318 settings = registry.settings
317 settings = registry.settings
319
318
320 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
319 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
321 pyramid_app = HttpsFixup(pyramid_app, settings)
320 pyramid_app = HttpsFixup(pyramid_app, settings)
322
321
323 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
322 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
324 pyramid_app, settings)
323 pyramid_app, settings)
325 registry.ae_client = _ae_client
324 registry.ae_client = _ae_client
326
325
327 if settings['gzip_responses']:
326 if settings['gzip_responses']:
328 pyramid_app = make_gzip_middleware(
327 pyramid_app = make_gzip_middleware(
329 pyramid_app, settings, compress_level=1)
328 pyramid_app, settings, compress_level=1)
330
329
331 # this should be the outer most middleware in the wsgi stack since
330 # this should be the outer most middleware in the wsgi stack since
332 # middleware like Routes make database calls
331 # middleware like Routes make database calls
333 def pyramid_app_with_cleanup(environ, start_response):
332 def pyramid_app_with_cleanup(environ, start_response):
334 try:
333 try:
335 return pyramid_app(environ, start_response)
334 return pyramid_app(environ, start_response)
336 finally:
335 finally:
337 # Dispose current database session and rollback uncommitted
336 # Dispose current database session and rollback uncommitted
338 # transactions.
337 # transactions.
339 meta.Session.remove()
338 meta.Session.remove()
340
339
341 # In a single threaded mode server, on non sqlite db we should have
340 # In a single threaded mode server, on non sqlite db we should have
342 # '0 Current Checked out connections' at the end of a request,
341 # '0 Current Checked out connections' at the end of a request,
343 # if not, then something, somewhere is leaving a connection open
342 # if not, then something, somewhere is leaving a connection open
344 pool = meta.Base.metadata.bind.engine.pool
343 pool = meta.Base.metadata.bind.engine.pool
345 log.debug('sa pool status: %s', pool.status())
344 log.debug('sa pool status: %s', pool.status())
346 log.debug('Request processing finalized')
345 log.debug('Request processing finalized')
347
346
348 return pyramid_app_with_cleanup
347 return pyramid_app_with_cleanup
349
348
350
349
351 def sanitize_settings_and_apply_defaults(settings):
350 def sanitize_settings_and_apply_defaults(settings):
352 """
351 """
353 Applies settings defaults and does all type conversion.
352 Applies settings defaults and does all type conversion.
354
353
355 We would move all settings parsing and preparation into this place, so that
354 We would move all settings parsing and preparation into this place, so that
356 we have only one place left which deals with this part. The remaining parts
355 we have only one place left which deals with this part. The remaining parts
357 of the application would start to rely fully on well prepared settings.
356 of the application would start to rely fully on well prepared settings.
358
357
359 This piece would later be split up per topic to avoid a big fat monster
358 This piece would later be split up per topic to avoid a big fat monster
360 function.
359 function.
361 """
360 """
362
361
363 settings.setdefault('rhodecode.edition', 'Community Edition')
362 settings.setdefault('rhodecode.edition', 'Community Edition')
364
363
365 if 'mako.default_filters' not in settings:
364 if 'mako.default_filters' not in settings:
366 # set custom default filters if we don't have it defined
365 # set custom default filters if we don't have it defined
367 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
366 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
368 settings['mako.default_filters'] = 'h_filter'
367 settings['mako.default_filters'] = 'h_filter'
369
368
370 if 'mako.directories' not in settings:
369 if 'mako.directories' not in settings:
371 mako_directories = settings.setdefault('mako.directories', [
370 mako_directories = settings.setdefault('mako.directories', [
372 # Base templates of the original application
371 # Base templates of the original application
373 'rhodecode:templates',
372 'rhodecode:templates',
374 ])
373 ])
375 log.debug(
374 log.debug(
376 "Using the following Mako template directories: %s",
375 "Using the following Mako template directories: %s",
377 mako_directories)
376 mako_directories)
378
377
379 # Default includes, possible to change as a user
378 # Default includes, possible to change as a user
380 pyramid_includes = settings.setdefault('pyramid.includes', [
379 pyramid_includes = settings.setdefault('pyramid.includes', [
381 'rhodecode.lib.middleware.request_wrapper',
380 'rhodecode.lib.middleware.request_wrapper',
382 ])
381 ])
383 log.debug(
382 log.debug(
384 "Using the following pyramid.includes: %s",
383 "Using the following pyramid.includes: %s",
385 pyramid_includes)
384 pyramid_includes)
386
385
387 # TODO: johbo: Re-think this, usually the call to config.include
386 # TODO: johbo: Re-think this, usually the call to config.include
388 # should allow to pass in a prefix.
387 # should allow to pass in a prefix.
389 settings.setdefault('rhodecode.api.url', '/_admin/api')
388 settings.setdefault('rhodecode.api.url', '/_admin/api')
390
389
391 # Sanitize generic settings.
390 # Sanitize generic settings.
392 _list_setting(settings, 'default_encoding', 'UTF-8')
391 _list_setting(settings, 'default_encoding', 'UTF-8')
393 _bool_setting(settings, 'is_test', 'false')
392 _bool_setting(settings, 'is_test', 'false')
394 _bool_setting(settings, 'gzip_responses', 'false')
393 _bool_setting(settings, 'gzip_responses', 'false')
395
394
396 # Call split out functions that sanitize settings for each topic.
395 # Call split out functions that sanitize settings for each topic.
397 _sanitize_appenlight_settings(settings)
396 _sanitize_appenlight_settings(settings)
398 _sanitize_vcs_settings(settings)
397 _sanitize_vcs_settings(settings)
399 _sanitize_cache_settings(settings)
398 _sanitize_cache_settings(settings)
400
399
401 # configure instance id
400 # configure instance id
402 config_utils.set_instance_id(settings)
401 config_utils.set_instance_id(settings)
403
402
404 return settings
403 return settings
405
404
406
405
407 def _sanitize_appenlight_settings(settings):
406 def _sanitize_appenlight_settings(settings):
408 _bool_setting(settings, 'appenlight', 'false')
407 _bool_setting(settings, 'appenlight', 'false')
409
408
410
409
411 def _sanitize_vcs_settings(settings):
410 def _sanitize_vcs_settings(settings):
412 """
411 """
413 Applies settings defaults and does type conversion for all VCS related
412 Applies settings defaults and does type conversion for all VCS related
414 settings.
413 settings.
415 """
414 """
416 _string_setting(settings, 'vcs.svn.compatible_version', '')
415 _string_setting(settings, 'vcs.svn.compatible_version', '')
417 _string_setting(settings, 'git_rev_filter', '--all')
416 _string_setting(settings, 'git_rev_filter', '--all')
418 _string_setting(settings, 'vcs.hooks.protocol', 'http')
417 _string_setting(settings, 'vcs.hooks.protocol', 'http')
419 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
418 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
420 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
419 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
421 _string_setting(settings, 'vcs.server', '')
420 _string_setting(settings, 'vcs.server', '')
422 _string_setting(settings, 'vcs.server.log_level', 'debug')
421 _string_setting(settings, 'vcs.server.log_level', 'debug')
423 _string_setting(settings, 'vcs.server.protocol', 'http')
422 _string_setting(settings, 'vcs.server.protocol', 'http')
424 _bool_setting(settings, 'startup.import_repos', 'false')
423 _bool_setting(settings, 'startup.import_repos', 'false')
425 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
424 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
426 _bool_setting(settings, 'vcs.server.enable', 'true')
425 _bool_setting(settings, 'vcs.server.enable', 'true')
427 _bool_setting(settings, 'vcs.start_server', 'false')
426 _bool_setting(settings, 'vcs.start_server', 'false')
428 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
427 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
429 _int_setting(settings, 'vcs.connection_timeout', 3600)
428 _int_setting(settings, 'vcs.connection_timeout', 3600)
430
429
431 # Support legacy values of vcs.scm_app_implementation. Legacy
430 # Support legacy values of vcs.scm_app_implementation. Legacy
432 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
431 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
433 # which is now mapped to 'http'.
432 # which is now mapped to 'http'.
434 scm_app_impl = settings['vcs.scm_app_implementation']
433 scm_app_impl = settings['vcs.scm_app_implementation']
435 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
434 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
436 settings['vcs.scm_app_implementation'] = 'http'
435 settings['vcs.scm_app_implementation'] = 'http'
437
436
438
437
439 def _sanitize_cache_settings(settings):
438 def _sanitize_cache_settings(settings):
440 _string_setting(settings, 'cache_dir',
439 _string_setting(settings, 'cache_dir',
441 os.path.join(tempfile.gettempdir(), 'rc_cache'))
440 os.path.join(tempfile.gettempdir(), 'rc_cache'))
442 # cache_perms
441 # cache_perms
443 _string_setting(
442 _string_setting(
444 settings,
443 settings,
445 'rc_cache.cache_perms.backend',
444 'rc_cache.cache_perms.backend',
446 'dogpile.cache.rc.file_namespace')
445 'dogpile.cache.rc.file_namespace')
447 _int_setting(
446 _int_setting(
448 settings,
447 settings,
449 'rc_cache.cache_perms.expiration_time',
448 'rc_cache.cache_perms.expiration_time',
450 60)
449 60)
451 _string_setting(
450 _string_setting(
452 settings,
451 settings,
453 'rc_cache.cache_perms.arguments.filename',
452 'rc_cache.cache_perms.arguments.filename',
454 os.path.join(tempfile.gettempdir(), 'rc_cache_1'))
453 os.path.join(tempfile.gettempdir(), 'rc_cache_1'))
455
454
456 # cache_repo
455 # cache_repo
457 _string_setting(
456 _string_setting(
458 settings,
457 settings,
459 'rc_cache.cache_repo.backend',
458 'rc_cache.cache_repo.backend',
460 'dogpile.cache.rc.file_namespace')
459 'dogpile.cache.rc.file_namespace')
461 _int_setting(
460 _int_setting(
462 settings,
461 settings,
463 'rc_cache.cache_repo.expiration_time',
462 'rc_cache.cache_repo.expiration_time',
464 60)
463 60)
465 _string_setting(
464 _string_setting(
466 settings,
465 settings,
467 'rc_cache.cache_repo.arguments.filename',
466 'rc_cache.cache_repo.arguments.filename',
468 os.path.join(tempfile.gettempdir(), 'rc_cache_2'))
467 os.path.join(tempfile.gettempdir(), 'rc_cache_2'))
469
468
469 # cache_repo_longterm memory, 96H
470 _string_setting(
471 settings,
472 'rc_cache.cache_repo_longterm.backend',
473 'dogpile.cache.rc.memory_lru')
474 _int_setting(
475 settings,
476 'rc_cache.cache_repo_longterm.expiration_time',
477 345600)
478 _int_setting(
479 settings,
480 'rc_cache.cache_repo_longterm.max_size',
481 10000)
482
470 # sql_cache_short
483 # sql_cache_short
471 _string_setting(
484 _string_setting(
472 settings,
485 settings,
473 'rc_cache.sql_cache_short.backend',
486 'rc_cache.sql_cache_short.backend',
474 'dogpile.cache.rc.memory_lru')
487 'dogpile.cache.rc.memory_lru')
475 _int_setting(
488 _int_setting(
476 settings,
489 settings,
477 'rc_cache.sql_cache_short.expiration_time',
490 'rc_cache.sql_cache_short.expiration_time',
478 30)
491 30)
479 _int_setting(
492 _int_setting(
480 settings,
493 settings,
481 'rc_cache.sql_cache_short.max_size',
494 'rc_cache.sql_cache_short.max_size',
482 10000)
495 10000)
483
496
484
497
485 def _int_setting(settings, name, default):
498 def _int_setting(settings, name, default):
486 settings[name] = int(settings.get(name, default))
499 settings[name] = int(settings.get(name, default))
487
500
488
501
489 def _bool_setting(settings, name, default):
502 def _bool_setting(settings, name, default):
490 input_val = settings.get(name, default)
503 input_val = settings.get(name, default)
491 if isinstance(input_val, unicode):
504 if isinstance(input_val, unicode):
492 input_val = input_val.encode('utf8')
505 input_val = input_val.encode('utf8')
493 settings[name] = asbool(input_val)
506 settings[name] = asbool(input_val)
494
507
495
508
496 def _list_setting(settings, name, default):
509 def _list_setting(settings, name, default):
497 raw_value = settings.get(name, default)
510 raw_value = settings.get(name, default)
498
511
499 old_separator = ','
512 old_separator = ','
500 if old_separator in raw_value:
513 if old_separator in raw_value:
501 # If we get a comma separated list, pass it to our own function.
514 # If we get a comma separated list, pass it to our own function.
502 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
515 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
503 else:
516 else:
504 # Otherwise we assume it uses pyramids space/newline separation.
517 # Otherwise we assume it uses pyramids space/newline separation.
505 settings[name] = aslist(raw_value)
518 settings[name] = aslist(raw_value)
506
519
507
520
508 def _string_setting(settings, name, default, lower=True):
521 def _string_setting(settings, name, default, lower=True):
509 value = settings.get(name, default)
522 value = settings.get(name, default)
510 if lower:
523 if lower:
511 value = value.lower()
524 value = value.lower()
512 settings[name] = value
525 settings[name] = value
513
526
514
527
515 def _substitute_values(mapping, substitutions):
528 def _substitute_values(mapping, substitutions):
516 result = {
529 result = {
517 # Note: Cannot use regular replacements, since they would clash
530 # Note: Cannot use regular replacements, since they would clash
518 # with the implementation of ConfigParser. Using "format" instead.
531 # with the implementation of ConfigParser. Using "format" instead.
519 key: value.format(**substitutions)
532 key: value.format(**substitutions)
520 for key, value in mapping.items()
533 for key, value in mapping.items()
521 }
534 }
522 return result
535 return result
@@ -1,547 +1,546 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.authentication.base import VCS_TYPE
38 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.lib import auth, utils2
39 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils2 import (
44 from rhodecode.lib.utils2 import (
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 from rhodecode.model.db import Repository, User, ChangesetComment
46 from rhodecode.model.db import Repository, User, ChangesetComment
47 from rhodecode.model.notification import NotificationModel
47 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 def _filter_proxy(ip):
53 def _filter_proxy(ip):
54 """
54 """
55 Passed in IP addresses in HEADERS can be in a special format of multiple
55 Passed in IP addresses in HEADERS can be in a special format of multiple
56 ips. Those comma separated IPs are passed from various proxies in the
56 ips. Those comma separated IPs are passed from various proxies in the
57 chain of request processing. The left-most being the original client.
57 chain of request processing. The left-most being the original client.
58 We only care about the first IP which came from the org. client.
58 We only care about the first IP which came from the org. client.
59
59
60 :param ip: ip string from headers
60 :param ip: ip string from headers
61 """
61 """
62 if ',' in ip:
62 if ',' in ip:
63 _ips = ip.split(',')
63 _ips = ip.split(',')
64 _first_ip = _ips[0].strip()
64 _first_ip = _ips[0].strip()
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 return _first_ip
66 return _first_ip
67 return ip
67 return ip
68
68
69
69
70 def _filter_port(ip):
70 def _filter_port(ip):
71 """
71 """
72 Removes a port from ip, there are 4 main cases to handle here.
72 Removes a port from ip, there are 4 main cases to handle here.
73 - ipv4 eg. 127.0.0.1
73 - ipv4 eg. 127.0.0.1
74 - ipv6 eg. ::1
74 - ipv6 eg. ::1
75 - ipv4+port eg. 127.0.0.1:8080
75 - ipv4+port eg. 127.0.0.1:8080
76 - ipv6+port eg. [::1]:8080
76 - ipv6+port eg. [::1]:8080
77
77
78 :param ip:
78 :param ip:
79 """
79 """
80 def is_ipv6(ip_addr):
80 def is_ipv6(ip_addr):
81 if hasattr(socket, 'inet_pton'):
81 if hasattr(socket, 'inet_pton'):
82 try:
82 try:
83 socket.inet_pton(socket.AF_INET6, ip_addr)
83 socket.inet_pton(socket.AF_INET6, ip_addr)
84 except socket.error:
84 except socket.error:
85 return False
85 return False
86 else:
86 else:
87 # fallback to ipaddress
87 # fallback to ipaddress
88 try:
88 try:
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 except Exception:
90 except Exception:
91 return False
91 return False
92 return True
92 return True
93
93
94 if ':' not in ip: # must be ipv4 pure ip
94 if ':' not in ip: # must be ipv4 pure ip
95 return ip
95 return ip
96
96
97 if '[' in ip and ']' in ip: # ipv6 with port
97 if '[' in ip and ']' in ip: # ipv6 with port
98 return ip.split(']')[0][1:].lower()
98 return ip.split(']')[0][1:].lower()
99
99
100 # must be ipv6 or ipv4 with port
100 # must be ipv6 or ipv4 with port
101 if is_ipv6(ip):
101 if is_ipv6(ip):
102 return ip
102 return ip
103 else:
103 else:
104 ip, _port = ip.split(':')[:2] # means ipv4+port
104 ip, _port = ip.split(':')[:2] # means ipv4+port
105 return ip
105 return ip
106
106
107
107
108 def get_ip_addr(environ):
108 def get_ip_addr(environ):
109 proxy_key = 'HTTP_X_REAL_IP'
109 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 def_key = 'REMOTE_ADDR'
111 def_key = 'REMOTE_ADDR'
112 _filters = lambda x: _filter_port(_filter_proxy(x))
112 _filters = lambda x: _filter_port(_filter_proxy(x))
113
113
114 ip = environ.get(proxy_key)
114 ip = environ.get(proxy_key)
115 if ip:
115 if ip:
116 return _filters(ip)
116 return _filters(ip)
117
117
118 ip = environ.get(proxy_key2)
118 ip = environ.get(proxy_key2)
119 if ip:
119 if ip:
120 return _filters(ip)
120 return _filters(ip)
121
121
122 ip = environ.get(def_key, '0.0.0.0')
122 ip = environ.get(def_key, '0.0.0.0')
123 return _filters(ip)
123 return _filters(ip)
124
124
125
125
126 def get_server_ip_addr(environ, log_errors=True):
126 def get_server_ip_addr(environ, log_errors=True):
127 hostname = environ.get('SERVER_NAME')
127 hostname = environ.get('SERVER_NAME')
128 try:
128 try:
129 return socket.gethostbyname(hostname)
129 return socket.gethostbyname(hostname)
130 except Exception as e:
130 except Exception as e:
131 if log_errors:
131 if log_errors:
132 # in some cases this lookup is not possible, and we don't want to
132 # in some cases this lookup is not possible, and we don't want to
133 # make it an exception in logs
133 # make it an exception in logs
134 log.exception('Could not retrieve server ip address: %s', e)
134 log.exception('Could not retrieve server ip address: %s', e)
135 return hostname
135 return hostname
136
136
137
137
138 def get_server_port(environ):
138 def get_server_port(environ):
139 return environ.get('SERVER_PORT')
139 return environ.get('SERVER_PORT')
140
140
141
141
142 def get_access_path(environ):
142 def get_access_path(environ):
143 path = environ.get('PATH_INFO')
143 path = environ.get('PATH_INFO')
144 org_req = environ.get('pylons.original_request')
144 org_req = environ.get('pylons.original_request')
145 if org_req:
145 if org_req:
146 path = org_req.environ.get('PATH_INFO')
146 path = org_req.environ.get('PATH_INFO')
147 return path
147 return path
148
148
149
149
150 def get_user_agent(environ):
150 def get_user_agent(environ):
151 return environ.get('HTTP_USER_AGENT')
151 return environ.get('HTTP_USER_AGENT')
152
152
153
153
154 def vcs_operation_context(
154 def vcs_operation_context(
155 environ, repo_name, username, action, scm, check_locking=True,
155 environ, repo_name, username, action, scm, check_locking=True,
156 is_shadow_repo=False):
156 is_shadow_repo=False):
157 """
157 """
158 Generate the context for a vcs operation, e.g. push or pull.
158 Generate the context for a vcs operation, e.g. push or pull.
159
159
160 This context is passed over the layers so that hooks triggered by the
160 This context is passed over the layers so that hooks triggered by the
161 vcs operation know details like the user, the user's IP address etc.
161 vcs operation know details like the user, the user's IP address etc.
162
162
163 :param check_locking: Allows to switch of the computation of the locking
163 :param check_locking: Allows to switch of the computation of the locking
164 data. This serves mainly the need of the simplevcs middleware to be
164 data. This serves mainly the need of the simplevcs middleware to be
165 able to disable this for certain operations.
165 able to disable this for certain operations.
166
166
167 """
167 """
168 # Tri-state value: False: unlock, None: nothing, True: lock
168 # Tri-state value: False: unlock, None: nothing, True: lock
169 make_lock = None
169 make_lock = None
170 locked_by = [None, None, None]
170 locked_by = [None, None, None]
171 is_anonymous = username == User.DEFAULT_USER
171 is_anonymous = username == User.DEFAULT_USER
172 user = User.get_by_username(username)
172 user = User.get_by_username(username)
173 if not is_anonymous and check_locking:
173 if not is_anonymous and check_locking:
174 log.debug('Checking locking on repository "%s"', repo_name)
174 log.debug('Checking locking on repository "%s"', repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
176 make_lock, __, locked_by = repo.get_locking_state(
176 make_lock, __, locked_by = repo.get_locking_state(
177 action, user.user_id)
177 action, user.user_id)
178 user_id = user.user_id
178 user_id = user.user_id
179 settings_model = VcsSettingsModel(repo=repo_name)
179 settings_model = VcsSettingsModel(repo=repo_name)
180 ui_settings = settings_model.get_ui_settings()
180 ui_settings = settings_model.get_ui_settings()
181
181
182 extras = {
182 extras = {
183 'ip': get_ip_addr(environ),
183 'ip': get_ip_addr(environ),
184 'username': username,
184 'username': username,
185 'user_id': user_id,
185 'user_id': user_id,
186 'action': action,
186 'action': action,
187 'repository': repo_name,
187 'repository': repo_name,
188 'scm': scm,
188 'scm': scm,
189 'config': rhodecode.CONFIG['__file__'],
189 'config': rhodecode.CONFIG['__file__'],
190 'make_lock': make_lock,
190 'make_lock': make_lock,
191 'locked_by': locked_by,
191 'locked_by': locked_by,
192 'server_url': utils2.get_server_url(environ),
192 'server_url': utils2.get_server_url(environ),
193 'user_agent': get_user_agent(environ),
193 'user_agent': get_user_agent(environ),
194 'hooks': get_enabled_hook_classes(ui_settings),
194 'hooks': get_enabled_hook_classes(ui_settings),
195 'is_shadow_repo': is_shadow_repo,
195 'is_shadow_repo': is_shadow_repo,
196 }
196 }
197 return extras
197 return extras
198
198
199
199
200 class BasicAuth(AuthBasicAuthenticator):
200 class BasicAuth(AuthBasicAuthenticator):
201
201
202 def __init__(self, realm, authfunc, registry, auth_http_code=None,
202 def __init__(self, realm, authfunc, registry, auth_http_code=None,
203 initial_call_detection=False, acl_repo_name=None):
203 initial_call_detection=False, acl_repo_name=None):
204 self.realm = realm
204 self.realm = realm
205 self.initial_call = initial_call_detection
205 self.initial_call = initial_call_detection
206 self.authfunc = authfunc
206 self.authfunc = authfunc
207 self.registry = registry
207 self.registry = registry
208 self.acl_repo_name = acl_repo_name
208 self.acl_repo_name = acl_repo_name
209 self._rc_auth_http_code = auth_http_code
209 self._rc_auth_http_code = auth_http_code
210
210
211 def _get_response_from_code(self, http_code):
211 def _get_response_from_code(self, http_code):
212 try:
212 try:
213 return get_exception(safe_int(http_code))
213 return get_exception(safe_int(http_code))
214 except Exception:
214 except Exception:
215 log.exception('Failed to fetch response for code %s' % http_code)
215 log.exception('Failed to fetch response for code %s' % http_code)
216 return HTTPForbidden
216 return HTTPForbidden
217
217
218 def get_rc_realm(self):
218 def get_rc_realm(self):
219 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
219 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
220
220
221 def build_authentication(self):
221 def build_authentication(self):
222 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
222 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
223 if self._rc_auth_http_code and not self.initial_call:
223 if self._rc_auth_http_code and not self.initial_call:
224 # return alternative HTTP code if alternative http return code
224 # return alternative HTTP code if alternative http return code
225 # is specified in RhodeCode config, but ONLY if it's not the
225 # is specified in RhodeCode config, but ONLY if it's not the
226 # FIRST call
226 # FIRST call
227 custom_response_klass = self._get_response_from_code(
227 custom_response_klass = self._get_response_from_code(
228 self._rc_auth_http_code)
228 self._rc_auth_http_code)
229 return custom_response_klass(headers=head)
229 return custom_response_klass(headers=head)
230 return HTTPUnauthorized(headers=head)
230 return HTTPUnauthorized(headers=head)
231
231
232 def authenticate(self, environ):
232 def authenticate(self, environ):
233 authorization = AUTHORIZATION(environ)
233 authorization = AUTHORIZATION(environ)
234 if not authorization:
234 if not authorization:
235 return self.build_authentication()
235 return self.build_authentication()
236 (authmeth, auth) = authorization.split(' ', 1)
236 (authmeth, auth) = authorization.split(' ', 1)
237 if 'basic' != authmeth.lower():
237 if 'basic' != authmeth.lower():
238 return self.build_authentication()
238 return self.build_authentication()
239 auth = auth.strip().decode('base64')
239 auth = auth.strip().decode('base64')
240 _parts = auth.split(':', 1)
240 _parts = auth.split(':', 1)
241 if len(_parts) == 2:
241 if len(_parts) == 2:
242 username, password = _parts
242 username, password = _parts
243 auth_data = self.authfunc(
243 auth_data = self.authfunc(
244 username, password, environ, VCS_TYPE,
244 username, password, environ, VCS_TYPE,
245 registry=self.registry, acl_repo_name=self.acl_repo_name)
245 registry=self.registry, acl_repo_name=self.acl_repo_name)
246 if auth_data:
246 if auth_data:
247 return {'username': username, 'auth_data': auth_data}
247 return {'username': username, 'auth_data': auth_data}
248 if username and password:
248 if username and password:
249 # we mark that we actually executed authentication once, at
249 # we mark that we actually executed authentication once, at
250 # that point we can use the alternative auth code
250 # that point we can use the alternative auth code
251 self.initial_call = False
251 self.initial_call = False
252
252
253 return self.build_authentication()
253 return self.build_authentication()
254
254
255 __call__ = authenticate
255 __call__ = authenticate
256
256
257
257
258 def calculate_version_hash(config):
258 def calculate_version_hash(config):
259 return sha1(
259 return sha1(
260 config.get('beaker.session.secret', '') +
260 config.get('beaker.session.secret', '') +
261 rhodecode.__version__)[:8]
261 rhodecode.__version__)[:8]
262
262
263
263
264 def get_current_lang(request):
264 def get_current_lang(request):
265 # NOTE(marcink): remove after pyramid move
265 # NOTE(marcink): remove after pyramid move
266 try:
266 try:
267 return translation.get_lang()[0]
267 return translation.get_lang()[0]
268 except:
268 except:
269 pass
269 pass
270
270
271 return getattr(request, '_LOCALE_', request.locale_name)
271 return getattr(request, '_LOCALE_', request.locale_name)
272
272
273
273
274 def attach_context_attributes(context, request, user_id):
274 def attach_context_attributes(context, request, user_id):
275 """
275 """
276 Attach variables into template context called `c`.
276 Attach variables into template context called `c`.
277 """
277 """
278 config = request.registry.settings
278 config = request.registry.settings
279
279
280
280
281 rc_config = SettingsModel().get_all_settings(cache=True)
281 rc_config = SettingsModel().get_all_settings(cache=True)
282
282
283 context.rhodecode_version = rhodecode.__version__
283 context.rhodecode_version = rhodecode.__version__
284 context.rhodecode_edition = config.get('rhodecode.edition')
284 context.rhodecode_edition = config.get('rhodecode.edition')
285 # unique secret + version does not leak the version but keep consistency
285 # unique secret + version does not leak the version but keep consistency
286 context.rhodecode_version_hash = calculate_version_hash(config)
286 context.rhodecode_version_hash = calculate_version_hash(config)
287
287
288 # Default language set for the incoming request
288 # Default language set for the incoming request
289 context.language = get_current_lang(request)
289 context.language = get_current_lang(request)
290
290
291 # Visual options
291 # Visual options
292 context.visual = AttributeDict({})
292 context.visual = AttributeDict({})
293
293
294 # DB stored Visual Items
294 # DB stored Visual Items
295 context.visual.show_public_icon = str2bool(
295 context.visual.show_public_icon = str2bool(
296 rc_config.get('rhodecode_show_public_icon'))
296 rc_config.get('rhodecode_show_public_icon'))
297 context.visual.show_private_icon = str2bool(
297 context.visual.show_private_icon = str2bool(
298 rc_config.get('rhodecode_show_private_icon'))
298 rc_config.get('rhodecode_show_private_icon'))
299 context.visual.stylify_metatags = str2bool(
299 context.visual.stylify_metatags = str2bool(
300 rc_config.get('rhodecode_stylify_metatags'))
300 rc_config.get('rhodecode_stylify_metatags'))
301 context.visual.dashboard_items = safe_int(
301 context.visual.dashboard_items = safe_int(
302 rc_config.get('rhodecode_dashboard_items', 100))
302 rc_config.get('rhodecode_dashboard_items', 100))
303 context.visual.admin_grid_items = safe_int(
303 context.visual.admin_grid_items = safe_int(
304 rc_config.get('rhodecode_admin_grid_items', 100))
304 rc_config.get('rhodecode_admin_grid_items', 100))
305 context.visual.repository_fields = str2bool(
305 context.visual.repository_fields = str2bool(
306 rc_config.get('rhodecode_repository_fields'))
306 rc_config.get('rhodecode_repository_fields'))
307 context.visual.show_version = str2bool(
307 context.visual.show_version = str2bool(
308 rc_config.get('rhodecode_show_version'))
308 rc_config.get('rhodecode_show_version'))
309 context.visual.use_gravatar = str2bool(
309 context.visual.use_gravatar = str2bool(
310 rc_config.get('rhodecode_use_gravatar'))
310 rc_config.get('rhodecode_use_gravatar'))
311 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
311 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
312 context.visual.default_renderer = rc_config.get(
312 context.visual.default_renderer = rc_config.get(
313 'rhodecode_markup_renderer', 'rst')
313 'rhodecode_markup_renderer', 'rst')
314 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
314 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
315 context.visual.rhodecode_support_url = \
315 context.visual.rhodecode_support_url = \
316 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
316 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
317
317
318 context.visual.affected_files_cut_off = 60
318 context.visual.affected_files_cut_off = 60
319
319
320 context.pre_code = rc_config.get('rhodecode_pre_code')
320 context.pre_code = rc_config.get('rhodecode_pre_code')
321 context.post_code = rc_config.get('rhodecode_post_code')
321 context.post_code = rc_config.get('rhodecode_post_code')
322 context.rhodecode_name = rc_config.get('rhodecode_title')
322 context.rhodecode_name = rc_config.get('rhodecode_title')
323 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
323 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
324 # if we have specified default_encoding in the request, it has more
324 # if we have specified default_encoding in the request, it has more
325 # priority
325 # priority
326 if request.GET.get('default_encoding'):
326 if request.GET.get('default_encoding'):
327 context.default_encodings.insert(0, request.GET.get('default_encoding'))
327 context.default_encodings.insert(0, request.GET.get('default_encoding'))
328 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
328 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
329 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
329 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
330
330
331 # INI stored
331 # INI stored
332 context.labs_active = str2bool(
332 context.labs_active = str2bool(
333 config.get('labs_settings_active', 'false'))
333 config.get('labs_settings_active', 'false'))
334 context.ssh_enabled = str2bool(
334 context.ssh_enabled = str2bool(
335 config.get('ssh.generate_authorized_keyfile', 'false'))
335 config.get('ssh.generate_authorized_keyfile', 'false'))
336
336
337 context.visual.allow_repo_location_change = str2bool(
337 context.visual.allow_repo_location_change = str2bool(
338 config.get('allow_repo_location_change', True))
338 config.get('allow_repo_location_change', True))
339 context.visual.allow_custom_hooks_settings = str2bool(
339 context.visual.allow_custom_hooks_settings = str2bool(
340 config.get('allow_custom_hooks_settings', True))
340 config.get('allow_custom_hooks_settings', True))
341 context.debug_style = str2bool(config.get('debug_style', False))
341 context.debug_style = str2bool(config.get('debug_style', False))
342
342
343 context.rhodecode_instanceid = config.get('instance_id')
343 context.rhodecode_instanceid = config.get('instance_id')
344
344
345 context.visual.cut_off_limit_diff = safe_int(
345 context.visual.cut_off_limit_diff = safe_int(
346 config.get('cut_off_limit_diff'))
346 config.get('cut_off_limit_diff'))
347 context.visual.cut_off_limit_file = safe_int(
347 context.visual.cut_off_limit_file = safe_int(
348 config.get('cut_off_limit_file'))
348 config.get('cut_off_limit_file'))
349
349
350 # AppEnlight
350 # AppEnlight
351 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
351 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
352 context.appenlight_api_public_key = config.get(
352 context.appenlight_api_public_key = config.get(
353 'appenlight.api_public_key', '')
353 'appenlight.api_public_key', '')
354 context.appenlight_server_url = config.get('appenlight.server_url', '')
354 context.appenlight_server_url = config.get('appenlight.server_url', '')
355
355
356 # JS template context
356 # JS template context
357 context.template_context = {
357 context.template_context = {
358 'repo_name': None,
358 'repo_name': None,
359 'repo_type': None,
359 'repo_type': None,
360 'repo_landing_commit': None,
360 'repo_landing_commit': None,
361 'rhodecode_user': {
361 'rhodecode_user': {
362 'username': None,
362 'username': None,
363 'email': None,
363 'email': None,
364 'notification_status': False
364 'notification_status': False
365 },
365 },
366 'visual': {
366 'visual': {
367 'default_renderer': None
367 'default_renderer': None
368 },
368 },
369 'commit_data': {
369 'commit_data': {
370 'commit_id': None
370 'commit_id': None
371 },
371 },
372 'pull_request_data': {'pull_request_id': None},
372 'pull_request_data': {'pull_request_id': None},
373 'timeago': {
373 'timeago': {
374 'refresh_time': 120 * 1000,
374 'refresh_time': 120 * 1000,
375 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
375 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
376 },
376 },
377 'pyramid_dispatch': {
377 'pyramid_dispatch': {
378
378
379 },
379 },
380 'extra': {'plugins': {}}
380 'extra': {'plugins': {}}
381 }
381 }
382 # END CONFIG VARS
382 # END CONFIG VARS
383
383
384 diffmode = 'sideside'
384 diffmode = 'sideside'
385 if request.GET.get('diffmode'):
385 if request.GET.get('diffmode'):
386 if request.GET['diffmode'] == 'unified':
386 if request.GET['diffmode'] == 'unified':
387 diffmode = 'unified'
387 diffmode = 'unified'
388 elif request.session.get('diffmode'):
388 elif request.session.get('diffmode'):
389 diffmode = request.session['diffmode']
389 diffmode = request.session['diffmode']
390
390
391 context.diffmode = diffmode
391 context.diffmode = diffmode
392
392
393 if request.session.get('diffmode') != diffmode:
393 if request.session.get('diffmode') != diffmode:
394 request.session['diffmode'] = diffmode
394 request.session['diffmode'] = diffmode
395
395
396 context.csrf_token = auth.get_csrf_token(session=request.session)
396 context.csrf_token = auth.get_csrf_token(session=request.session)
397 context.backends = rhodecode.BACKENDS.keys()
397 context.backends = rhodecode.BACKENDS.keys()
398 context.backends.sort()
398 context.backends.sort()
399 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
399 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
400
400
401 # web case
401 # web case
402 if hasattr(request, 'user'):
402 if hasattr(request, 'user'):
403 context.auth_user = request.user
403 context.auth_user = request.user
404 context.rhodecode_user = request.user
404 context.rhodecode_user = request.user
405
405
406 # api case
406 # api case
407 if hasattr(request, 'rpc_user'):
407 if hasattr(request, 'rpc_user'):
408 context.auth_user = request.rpc_user
408 context.auth_user = request.rpc_user
409 context.rhodecode_user = request.rpc_user
409 context.rhodecode_user = request.rpc_user
410
410
411 # attach the whole call context to the request
411 # attach the whole call context to the request
412 request.call_context = context
412 request.call_context = context
413
413
414
414
415 def get_auth_user(request):
415 def get_auth_user(request):
416 environ = request.environ
416 environ = request.environ
417 session = request.session
417 session = request.session
418
418
419 ip_addr = get_ip_addr(environ)
419 ip_addr = get_ip_addr(environ)
420 # make sure that we update permissions each time we call controller
420 # make sure that we update permissions each time we call controller
421 _auth_token = (request.GET.get('auth_token', '') or
421 _auth_token = (request.GET.get('auth_token', '') or
422 request.GET.get('api_key', ''))
422 request.GET.get('api_key', ''))
423
423
424 if _auth_token:
424 if _auth_token:
425 # when using API_KEY we assume user exists, and
425 # when using API_KEY we assume user exists, and
426 # doesn't need auth based on cookies.
426 # doesn't need auth based on cookies.
427 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
427 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
428 authenticated = False
428 authenticated = False
429 else:
429 else:
430 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
430 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
431 try:
431 try:
432 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
432 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
433 ip_addr=ip_addr)
433 ip_addr=ip_addr)
434 except UserCreationError as e:
434 except UserCreationError as e:
435 h.flash(e, 'error')
435 h.flash(e, 'error')
436 # container auth or other auth functions that create users
436 # container auth or other auth functions that create users
437 # on the fly can throw this exception signaling that there's
437 # on the fly can throw this exception signaling that there's
438 # issue with user creation, explanation should be provided
438 # issue with user creation, explanation should be provided
439 # in Exception itself. We then create a simple blank
439 # in Exception itself. We then create a simple blank
440 # AuthUser
440 # AuthUser
441 auth_user = AuthUser(ip_addr=ip_addr)
441 auth_user = AuthUser(ip_addr=ip_addr)
442
442
443 # in case someone changes a password for user it triggers session
443 # in case someone changes a password for user it triggers session
444 # flush and forces a re-login
444 # flush and forces a re-login
445 if password_changed(auth_user, session):
445 if password_changed(auth_user, session):
446 session.invalidate()
446 session.invalidate()
447 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
447 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
448 auth_user = AuthUser(ip_addr=ip_addr)
448 auth_user = AuthUser(ip_addr=ip_addr)
449
449
450 authenticated = cookie_store.get('is_authenticated')
450 authenticated = cookie_store.get('is_authenticated')
451
451
452 if not auth_user.is_authenticated and auth_user.is_user_object:
452 if not auth_user.is_authenticated and auth_user.is_user_object:
453 # user is not authenticated and not empty
453 # user is not authenticated and not empty
454 auth_user.set_authenticated(authenticated)
454 auth_user.set_authenticated(authenticated)
455
455
456 return auth_user
456 return auth_user
457
457
458
458
459 def h_filter(s):
459 def h_filter(s):
460 """
460 """
461 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
461 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
462 we wrap this with additional functionality that converts None to empty
462 we wrap this with additional functionality that converts None to empty
463 strings
463 strings
464 """
464 """
465 if s is None:
465 if s is None:
466 return markupsafe.Markup()
466 return markupsafe.Markup()
467 return markupsafe.escape(s)
467 return markupsafe.escape(s)
468
468
469
469
470 def add_events_routes(config):
470 def add_events_routes(config):
471 """
471 """
472 Adds routing that can be used in events. Because some events are triggered
472 Adds routing that can be used in events. Because some events are triggered
473 outside of pyramid context, we need to bootstrap request with some
473 outside of pyramid context, we need to bootstrap request with some
474 routing registered
474 routing registered
475 """
475 """
476
476
477 from rhodecode.apps._base import ADMIN_PREFIX
477 from rhodecode.apps._base import ADMIN_PREFIX
478
478
479 config.add_route(name='home', pattern='/')
479 config.add_route(name='home', pattern='/')
480
480
481 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
481 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
482 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
482 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
483 config.add_route(name='repo_summary', pattern='/{repo_name}')
483 config.add_route(name='repo_summary', pattern='/{repo_name}')
484 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
484 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
485 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
485 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
486
486
487 config.add_route(name='pullrequest_show',
487 config.add_route(name='pullrequest_show',
488 pattern='/{repo_name}/pull-request/{pull_request_id}')
488 pattern='/{repo_name}/pull-request/{pull_request_id}')
489 config.add_route(name='pull_requests_global',
489 config.add_route(name='pull_requests_global',
490 pattern='/pull-request/{pull_request_id}')
490 pattern='/pull-request/{pull_request_id}')
491 config.add_route(name='repo_commit',
491 config.add_route(name='repo_commit',
492 pattern='/{repo_name}/changeset/{commit_id}')
492 pattern='/{repo_name}/changeset/{commit_id}')
493
493
494 config.add_route(name='repo_files',
494 config.add_route(name='repo_files',
495 pattern='/{repo_name}/files/{commit_id}/{f_path}')
495 pattern='/{repo_name}/files/{commit_id}/{f_path}')
496
496
497
497
498 def bootstrap_config(request):
498 def bootstrap_config(request):
499 import pyramid.testing
499 import pyramid.testing
500 registry = pyramid.testing.Registry('RcTestRegistry')
500 registry = pyramid.testing.Registry('RcTestRegistry')
501
501
502 config = pyramid.testing.setUp(registry=registry, request=request)
502 config = pyramid.testing.setUp(registry=registry, request=request)
503
503
504 # allow pyramid lookup in testing
504 # allow pyramid lookup in testing
505 config.include('pyramid_mako')
505 config.include('pyramid_mako')
506 config.include('pyramid_beaker')
506 config.include('pyramid_beaker')
507 config.include('rhodecode.lib.caches')
508 config.include('rhodecode.lib.rc_cache')
507 config.include('rhodecode.lib.rc_cache')
509
508
510 add_events_routes(config)
509 add_events_routes(config)
511
510
512 return config
511 return config
513
512
514
513
515 def bootstrap_request(**kwargs):
514 def bootstrap_request(**kwargs):
516 import pyramid.testing
515 import pyramid.testing
517
516
518 class TestRequest(pyramid.testing.DummyRequest):
517 class TestRequest(pyramid.testing.DummyRequest):
519 application_url = kwargs.pop('application_url', 'http://example.com')
518 application_url = kwargs.pop('application_url', 'http://example.com')
520 host = kwargs.pop('host', 'example.com:80')
519 host = kwargs.pop('host', 'example.com:80')
521 domain = kwargs.pop('domain', 'example.com')
520 domain = kwargs.pop('domain', 'example.com')
522
521
523 def translate(self, msg):
522 def translate(self, msg):
524 return msg
523 return msg
525
524
526 def plularize(self, singular, plural, n):
525 def plularize(self, singular, plural, n):
527 return singular
526 return singular
528
527
529 def get_partial_renderer(self, tmpl_name):
528 def get_partial_renderer(self, tmpl_name):
530
529
531 from rhodecode.lib.partial_renderer import get_partial_renderer
530 from rhodecode.lib.partial_renderer import get_partial_renderer
532 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
531 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
533
532
534 _call_context = {}
533 _call_context = {}
535 @property
534 @property
536 def call_context(self):
535 def call_context(self):
537 return self._call_context
536 return self._call_context
538
537
539 class TestDummySession(pyramid.testing.DummySession):
538 class TestDummySession(pyramid.testing.DummySession):
540 def save(*arg, **kw):
539 def save(*arg, **kw):
541 pass
540 pass
542
541
543 request = TestRequest(**kwargs)
542 request = TestRequest(**kwargs)
544 request.session = TestDummySession()
543 request.session = TestDummySession()
545
544
546 return request
545 return request
547
546
@@ -1,1053 +1,1043 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import logging
22 import logging
23 import datetime
23 import datetime
24 import traceback
24 import traceback
25 from datetime import date
25 from datetime import date
26
26
27 from sqlalchemy import *
27 from sqlalchemy import *
28 from sqlalchemy.ext.hybrid import hybrid_property
28 from sqlalchemy.ext.hybrid import hybrid_property
29 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
29 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
30 from beaker.cache import cache_region, region_invalidate
30 from beaker.cache import cache_region, region_invalidate
31
31
32 from rhodecode.lib.vcs import get_backend
32 from rhodecode.lib.vcs import get_backend
33 from rhodecode.lib.vcs.utils.helpers import get_scm
33 from rhodecode.lib.vcs.utils.helpers import get_scm
34 from rhodecode.lib.vcs.exceptions import VCSError
34 from rhodecode.lib.vcs.exceptions import VCSError
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 from rhodecode.lib.auth import generate_auth_token
36 from rhodecode.lib.auth import generate_auth_token
37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, safe_unicode
37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, safe_unicode
38 from rhodecode.lib.exceptions import UserGroupAssignedException
38 from rhodecode.lib.exceptions import UserGroupAssignedException
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40
40
41 from rhodecode.model.meta import Base, Session
41 from rhodecode.model.meta import Base, Session
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 #==============================================================================
47 #==============================================================================
48 # BASE CLASSES
48 # BASE CLASSES
49 #==============================================================================
49 #==============================================================================
50
50
51 class ModelSerializer(json.JSONEncoder):
51 class ModelSerializer(json.JSONEncoder):
52 """
52 """
53 Simple Serializer for JSON,
53 Simple Serializer for JSON,
54
54
55 usage::
55 usage::
56
56
57 to make object customized for serialization implement a __json__
57 to make object customized for serialization implement a __json__
58 method that will return a dict for serialization into json
58 method that will return a dict for serialization into json
59
59
60 example::
60 example::
61
61
62 class Task(object):
62 class Task(object):
63
63
64 def __init__(self, name, value):
64 def __init__(self, name, value):
65 self.name = name
65 self.name = name
66 self.value = value
66 self.value = value
67
67
68 def __json__(self):
68 def __json__(self):
69 return dict(name=self.name,
69 return dict(name=self.name,
70 value=self.value)
70 value=self.value)
71
71
72 """
72 """
73
73
74 def default(self, obj):
74 def default(self, obj):
75
75
76 if hasattr(obj, '__json__'):
76 if hasattr(obj, '__json__'):
77 return obj.__json__()
77 return obj.__json__()
78 else:
78 else:
79 return json.JSONEncoder.default(self, obj)
79 return json.JSONEncoder.default(self, obj)
80
80
81 class BaseModel(object):
81 class BaseModel(object):
82 """Base Model for all classess
82 """Base Model for all classess
83
83
84 """
84 """
85
85
86 @classmethod
86 @classmethod
87 def _get_keys(cls):
87 def _get_keys(cls):
88 """return column names for this model """
88 """return column names for this model """
89 return class_mapper(cls).c.keys()
89 return class_mapper(cls).c.keys()
90
90
91 def get_dict(self):
91 def get_dict(self):
92 """return dict with keys and values corresponding
92 """return dict with keys and values corresponding
93 to this model data """
93 to this model data """
94
94
95 d = {}
95 d = {}
96 for k in self._get_keys():
96 for k in self._get_keys():
97 d[k] = getattr(self, k)
97 d[k] = getattr(self, k)
98 return d
98 return d
99
99
100 def get_appstruct(self):
100 def get_appstruct(self):
101 """return list with keys and values tupples corresponding
101 """return list with keys and values tupples corresponding
102 to this model data """
102 to this model data """
103
103
104 l = []
104 l = []
105 for k in self._get_keys():
105 for k in self._get_keys():
106 l.append((k, getattr(self, k),))
106 l.append((k, getattr(self, k),))
107 return l
107 return l
108
108
109 def populate_obj(self, populate_dict):
109 def populate_obj(self, populate_dict):
110 """populate model with data from given populate_dict"""
110 """populate model with data from given populate_dict"""
111
111
112 for k in self._get_keys():
112 for k in self._get_keys():
113 if k in populate_dict:
113 if k in populate_dict:
114 setattr(self, k, populate_dict[k])
114 setattr(self, k, populate_dict[k])
115
115
116 @classmethod
116 @classmethod
117 def query(cls):
117 def query(cls):
118 return Session.query(cls)
118 return Session.query(cls)
119
119
120 @classmethod
120 @classmethod
121 def get(cls, id_):
121 def get(cls, id_):
122 if id_:
122 if id_:
123 return cls.query().get(id_)
123 return cls.query().get(id_)
124
124
125 @classmethod
125 @classmethod
126 def getAll(cls):
126 def getAll(cls):
127 return cls.query().all()
127 return cls.query().all()
128
128
129 @classmethod
129 @classmethod
130 def delete(cls, id_):
130 def delete(cls, id_):
131 obj = cls.query().get(id_)
131 obj = cls.query().get(id_)
132 Session.delete(obj)
132 Session.delete(obj)
133 Session.commit()
133 Session.commit()
134
134
135
135
136 class RhodeCodeSetting(Base, BaseModel):
136 class RhodeCodeSetting(Base, BaseModel):
137 __tablename__ = 'rhodecode_settings'
137 __tablename__ = 'rhodecode_settings'
138 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
138 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
139 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
139 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
140 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
140 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
141 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
141 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
142
142
143 def __init__(self, k='', v=''):
143 def __init__(self, k='', v=''):
144 self.app_settings_name = k
144 self.app_settings_name = k
145 self.app_settings_value = v
145 self.app_settings_value = v
146
146
147
147
148 @validates('_app_settings_value')
148 @validates('_app_settings_value')
149 def validate_settings_value(self, key, val):
149 def validate_settings_value(self, key, val):
150 assert type(val) == unicode
150 assert type(val) == unicode
151 return val
151 return val
152
152
153 @hybrid_property
153 @hybrid_property
154 def app_settings_value(self):
154 def app_settings_value(self):
155 v = self._app_settings_value
155 v = self._app_settings_value
156 if v == 'ldap_active':
156 if v == 'ldap_active':
157 v = str2bool(v)
157 v = str2bool(v)
158 return v
158 return v
159
159
160 @app_settings_value.setter
160 @app_settings_value.setter
161 def app_settings_value(self, val):
161 def app_settings_value(self, val):
162 """
162 """
163 Setter that will always make sure we use unicode in app_settings_value
163 Setter that will always make sure we use unicode in app_settings_value
164
164
165 :param val:
165 :param val:
166 """
166 """
167 self._app_settings_value = safe_unicode(val)
167 self._app_settings_value = safe_unicode(val)
168
168
169 def __repr__(self):
169 def __repr__(self):
170 return "<%s('%s:%s')>" % (self.__class__.__name__,
170 return "<%s('%s:%s')>" % (self.__class__.__name__,
171 self.app_settings_name, self.app_settings_value)
171 self.app_settings_name, self.app_settings_value)
172
172
173
173
174 @classmethod
174 @classmethod
175 def get_by_name(cls, ldap_key):
175 def get_by_name(cls, ldap_key):
176 return cls.query()\
176 return cls.query()\
177 .filter(cls.app_settings_name == ldap_key).scalar()
177 .filter(cls.app_settings_name == ldap_key).scalar()
178
178
179 @classmethod
179 @classmethod
180 def get_app_settings(cls, cache=False):
180 def get_app_settings(cls, cache=False):
181
181
182 ret = cls.query()
182 ret = cls.query()
183
183
184 if cache:
184 if cache:
185 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
185 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
186
186
187 if not ret:
187 if not ret:
188 raise Exception('Could not get application settings !')
188 raise Exception('Could not get application settings !')
189 settings = {}
189 settings = {}
190 for each in ret:
190 for each in ret:
191 settings['rhodecode_' + each.app_settings_name] = \
191 settings['rhodecode_' + each.app_settings_name] = \
192 each.app_settings_value
192 each.app_settings_value
193
193
194 return settings
194 return settings
195
195
196 @classmethod
196 @classmethod
197 def get_ldap_settings(cls, cache=False):
197 def get_ldap_settings(cls, cache=False):
198 ret = cls.query()\
198 ret = cls.query()\
199 .filter(cls.app_settings_name.startswith('ldap_')).all()
199 .filter(cls.app_settings_name.startswith('ldap_')).all()
200 fd = {}
200 fd = {}
201 for row in ret:
201 for row in ret:
202 fd.update({row.app_settings_name:row.app_settings_value})
202 fd.update({row.app_settings_name:row.app_settings_value})
203
203
204 return fd
204 return fd
205
205
206
206
207 class RhodeCodeUi(Base, BaseModel):
207 class RhodeCodeUi(Base, BaseModel):
208 __tablename__ = 'rhodecode_ui'
208 __tablename__ = 'rhodecode_ui'
209 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
209 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
210
210
211 HOOK_REPO_SIZE = 'changegroup.repo_size'
211 HOOK_REPO_SIZE = 'changegroup.repo_size'
212 HOOK_PUSH = 'pretxnchangegroup.push_logger'
212 HOOK_PUSH = 'pretxnchangegroup.push_logger'
213 HOOK_PULL = 'preoutgoing.pull_logger'
213 HOOK_PULL = 'preoutgoing.pull_logger'
214
214
215 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
215 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
216 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
216 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
217 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
217 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
218 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
218 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
219 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
219 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
220
220
221
221
222 @classmethod
222 @classmethod
223 def get_by_key(cls, key):
223 def get_by_key(cls, key):
224 return cls.query().filter(cls.ui_key == key)
224 return cls.query().filter(cls.ui_key == key)
225
225
226
226
227 @classmethod
227 @classmethod
228 def get_builtin_hooks(cls):
228 def get_builtin_hooks(cls):
229 q = cls.query()
229 q = cls.query()
230 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
230 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
231 cls.HOOK_PUSH, cls.HOOK_PULL]))
231 cls.HOOK_PUSH, cls.HOOK_PULL]))
232 return q.all()
232 return q.all()
233
233
234 @classmethod
234 @classmethod
235 def get_custom_hooks(cls):
235 def get_custom_hooks(cls):
236 q = cls.query()
236 q = cls.query()
237 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
237 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
238 cls.HOOK_PUSH, cls.HOOK_PULL]))
238 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 q = q.filter(cls.ui_section == 'hooks')
239 q = q.filter(cls.ui_section == 'hooks')
240 return q.all()
240 return q.all()
241
241
242 @classmethod
242 @classmethod
243 def create_or_update_hook(cls, key, val):
243 def create_or_update_hook(cls, key, val):
244 new_ui = cls.get_by_key(key).scalar() or cls()
244 new_ui = cls.get_by_key(key).scalar() or cls()
245 new_ui.ui_section = 'hooks'
245 new_ui.ui_section = 'hooks'
246 new_ui.ui_active = True
246 new_ui.ui_active = True
247 new_ui.ui_key = key
247 new_ui.ui_key = key
248 new_ui.ui_value = val
248 new_ui.ui_value = val
249
249
250 Session.add(new_ui)
250 Session.add(new_ui)
251 Session.commit()
251 Session.commit()
252
252
253
253
254 class User(Base, BaseModel):
254 class User(Base, BaseModel):
255 __tablename__ = 'users'
255 __tablename__ = 'users'
256 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
256 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
257 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
257 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
258 username = Column("username", String(255), nullable=True, unique=None, default=None)
258 username = Column("username", String(255), nullable=True, unique=None, default=None)
259 password = Column("password", String(255), nullable=True, unique=None, default=None)
259 password = Column("password", String(255), nullable=True, unique=None, default=None)
260 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
260 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
261 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
261 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
262 name = Column("name", String(255), nullable=True, unique=None, default=None)
262 name = Column("name", String(255), nullable=True, unique=None, default=None)
263 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
263 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
264 email = Column("email", String(255), nullable=True, unique=None, default=None)
264 email = Column("email", String(255), nullable=True, unique=None, default=None)
265 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
265 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
266 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
266 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
267 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
267 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
268
268
269 user_log = relationship('UserLog', cascade='all')
269 user_log = relationship('UserLog', cascade='all')
270 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
270 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
271
271
272 repositories = relationship('Repository')
272 repositories = relationship('Repository')
273 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
273 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
274 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
274 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
275
275
276 group_member = relationship('UserGroupMember', cascade='all')
276 group_member = relationship('UserGroupMember', cascade='all')
277
277
278 @property
278 @property
279 def full_contact(self):
279 def full_contact(self):
280 return '%s %s <%s>' % (self.name, self.lastname, self.email)
280 return '%s %s <%s>' % (self.name, self.lastname, self.email)
281
281
282 @property
282 @property
283 def short_contact(self):
283 def short_contact(self):
284 return '%s %s' % (self.name, self.lastname)
284 return '%s %s' % (self.name, self.lastname)
285
285
286 @property
286 @property
287 def is_admin(self):
287 def is_admin(self):
288 return self.admin
288 return self.admin
289
289
290 def __repr__(self):
290 def __repr__(self):
291 try:
291 try:
292 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
292 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
293 self.user_id, self.username)
293 self.user_id, self.username)
294 except:
294 except:
295 return self.__class__.__name__
295 return self.__class__.__name__
296
296
297 @classmethod
297 @classmethod
298 def get_by_username(cls, username, case_insensitive=False):
298 def get_by_username(cls, username, case_insensitive=False):
299 if case_insensitive:
299 if case_insensitive:
300 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
300 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
301 else:
301 else:
302 return Session.query(cls).filter(cls.username == username).scalar()
302 return Session.query(cls).filter(cls.username == username).scalar()
303
303
304 @classmethod
304 @classmethod
305 def get_by_auth_token(cls, auth_token):
305 def get_by_auth_token(cls, auth_token):
306 return cls.query().filter(cls.api_key == auth_token).one()
306 return cls.query().filter(cls.api_key == auth_token).one()
307
307
308 def update_lastlogin(self):
308 def update_lastlogin(self):
309 """Update user lastlogin"""
309 """Update user lastlogin"""
310
310
311 self.last_login = datetime.datetime.now()
311 self.last_login = datetime.datetime.now()
312 Session.add(self)
312 Session.add(self)
313 Session.commit()
313 Session.commit()
314 log.debug('updated user %s lastlogin' % self.username)
314 log.debug('updated user %s lastlogin' % self.username)
315
315
316 @classmethod
316 @classmethod
317 def create(cls, form_data):
317 def create(cls, form_data):
318 from rhodecode.lib.auth import get_crypt_password
318 from rhodecode.lib.auth import get_crypt_password
319
319
320 try:
320 try:
321 new_user = cls()
321 new_user = cls()
322 for k, v in form_data.items():
322 for k, v in form_data.items():
323 if k == 'password':
323 if k == 'password':
324 v = get_crypt_password(v)
324 v = get_crypt_password(v)
325 setattr(new_user, k, v)
325 setattr(new_user, k, v)
326
326
327 new_user.api_key = generate_auth_token(form_data['username'])
327 new_user.api_key = generate_auth_token(form_data['username'])
328 Session.add(new_user)
328 Session.add(new_user)
329 Session.commit()
329 Session.commit()
330 return new_user
330 return new_user
331 except:
331 except:
332 log.error(traceback.format_exc())
332 log.error(traceback.format_exc())
333 Session.rollback()
333 Session.rollback()
334 raise
334 raise
335
335
336 class UserLog(Base, BaseModel):
336 class UserLog(Base, BaseModel):
337 __tablename__ = 'user_logs'
337 __tablename__ = 'user_logs'
338 __table_args__ = {'extend_existing':True}
338 __table_args__ = {'extend_existing':True}
339 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
339 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
340 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
340 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
341 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
341 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
342 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
342 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
343 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
343 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
344 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
344 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
345 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
345 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
346
346
347 @property
347 @property
348 def action_as_day(self):
348 def action_as_day(self):
349 return date(*self.action_date.timetuple()[:3])
349 return date(*self.action_date.timetuple()[:3])
350
350
351 user = relationship('User')
351 user = relationship('User')
352 repository = relationship('Repository')
352 repository = relationship('Repository')
353
353
354
354
355 class UserGroup(Base, BaseModel):
355 class UserGroup(Base, BaseModel):
356 __tablename__ = 'users_groups'
356 __tablename__ = 'users_groups'
357 __table_args__ = {'extend_existing':True}
357 __table_args__ = {'extend_existing':True}
358
358
359 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
360 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
360 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
361 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
361 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
362
362
363 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
363 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
364
364
365 def __repr__(self):
365 def __repr__(self):
366 return '<userGroup(%s)>' % (self.users_group_name)
366 return '<userGroup(%s)>' % (self.users_group_name)
367
367
368 @classmethod
368 @classmethod
369 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
369 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
370 if case_insensitive:
370 if case_insensitive:
371 gr = cls.query()\
371 gr = cls.query()\
372 .filter(cls.users_group_name.ilike(group_name))
372 .filter(cls.users_group_name.ilike(group_name))
373 else:
373 else:
374 gr = cls.query()\
374 gr = cls.query()\
375 .filter(cls.users_group_name == group_name)
375 .filter(cls.users_group_name == group_name)
376 if cache:
376 if cache:
377 gr = gr.options(FromCache("sql_cache_short",
377 gr = gr.options(FromCache("sql_cache_short",
378 "get_user_%s" % group_name))
378 "get_user_%s" % group_name))
379 return gr.scalar()
379 return gr.scalar()
380
380
381 @classmethod
381 @classmethod
382 def get(cls, users_group_id, cache=False):
382 def get(cls, users_group_id, cache=False):
383 users_group = cls.query()
383 users_group = cls.query()
384 if cache:
384 if cache:
385 users_group = users_group.options(FromCache("sql_cache_short",
385 users_group = users_group.options(FromCache("sql_cache_short",
386 "get_users_group_%s" % users_group_id))
386 "get_users_group_%s" % users_group_id))
387 return users_group.get(users_group_id)
387 return users_group.get(users_group_id)
388
388
389 @classmethod
389 @classmethod
390 def create(cls, form_data):
390 def create(cls, form_data):
391 try:
391 try:
392 new_user_group = cls()
392 new_user_group = cls()
393 for k, v in form_data.items():
393 for k, v in form_data.items():
394 setattr(new_user_group, k, v)
394 setattr(new_user_group, k, v)
395
395
396 Session.add(new_user_group)
396 Session.add(new_user_group)
397 Session.commit()
397 Session.commit()
398 return new_user_group
398 return new_user_group
399 except:
399 except:
400 log.error(traceback.format_exc())
400 log.error(traceback.format_exc())
401 Session.rollback()
401 Session.rollback()
402 raise
402 raise
403
403
404 @classmethod
404 @classmethod
405 def update(cls, users_group_id, form_data):
405 def update(cls, users_group_id, form_data):
406
406
407 try:
407 try:
408 users_group = cls.get(users_group_id, cache=False)
408 users_group = cls.get(users_group_id, cache=False)
409
409
410 for k, v in form_data.items():
410 for k, v in form_data.items():
411 if k == 'users_group_members':
411 if k == 'users_group_members':
412 users_group.members = []
412 users_group.members = []
413 Session.flush()
413 Session.flush()
414 members_list = []
414 members_list = []
415 if v:
415 if v:
416 v = [v] if isinstance(v, basestring) else v
416 v = [v] if isinstance(v, basestring) else v
417 for u_id in set(v):
417 for u_id in set(v):
418 member = UserGroupMember(users_group_id, u_id)
418 member = UserGroupMember(users_group_id, u_id)
419 members_list.append(member)
419 members_list.append(member)
420 setattr(users_group, 'members', members_list)
420 setattr(users_group, 'members', members_list)
421 setattr(users_group, k, v)
421 setattr(users_group, k, v)
422
422
423 Session.add(users_group)
423 Session.add(users_group)
424 Session.commit()
424 Session.commit()
425 except:
425 except:
426 log.error(traceback.format_exc())
426 log.error(traceback.format_exc())
427 Session.rollback()
427 Session.rollback()
428 raise
428 raise
429
429
430 @classmethod
430 @classmethod
431 def delete(cls, user_group_id):
431 def delete(cls, user_group_id):
432 try:
432 try:
433
433
434 # check if this group is not assigned to repo
434 # check if this group is not assigned to repo
435 assigned_groups = UserGroupRepoToPerm.query()\
435 assigned_groups = UserGroupRepoToPerm.query()\
436 .filter(UserGroupRepoToPerm.users_group_id ==
436 .filter(UserGroupRepoToPerm.users_group_id ==
437 user_group_id).all()
437 user_group_id).all()
438
438
439 if assigned_groups:
439 if assigned_groups:
440 raise UserGroupAssignedException(
440 raise UserGroupAssignedException(
441 'UserGroup assigned to %s' % assigned_groups)
441 'UserGroup assigned to %s' % assigned_groups)
442
442
443 users_group = cls.get(user_group_id, cache=False)
443 users_group = cls.get(user_group_id, cache=False)
444 Session.delete(users_group)
444 Session.delete(users_group)
445 Session.commit()
445 Session.commit()
446 except:
446 except:
447 log.error(traceback.format_exc())
447 log.error(traceback.format_exc())
448 Session.rollback()
448 Session.rollback()
449 raise
449 raise
450
450
451 class UserGroupMember(Base, BaseModel):
451 class UserGroupMember(Base, BaseModel):
452 __tablename__ = 'users_groups_members'
452 __tablename__ = 'users_groups_members'
453 __table_args__ = {'extend_existing':True}
453 __table_args__ = {'extend_existing':True}
454
454
455 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
455 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
456 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
456 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
458
458
459 user = relationship('User', lazy='joined')
459 user = relationship('User', lazy='joined')
460 users_group = relationship('UserGroup')
460 users_group = relationship('UserGroup')
461
461
462 def __init__(self, gr_id='', u_id=''):
462 def __init__(self, gr_id='', u_id=''):
463 self.users_group_id = gr_id
463 self.users_group_id = gr_id
464 self.user_id = u_id
464 self.user_id = u_id
465
465
466 @staticmethod
466 @staticmethod
467 def add_user_to_group(group, user):
467 def add_user_to_group(group, user):
468 ugm = UserGroupMember()
468 ugm = UserGroupMember()
469 ugm.users_group = group
469 ugm.users_group = group
470 ugm.user = user
470 ugm.user = user
471 Session.add(ugm)
471 Session.add(ugm)
472 Session.commit()
472 Session.commit()
473 return ugm
473 return ugm
474
474
475 class Repository(Base, BaseModel):
475 class Repository(Base, BaseModel):
476 __tablename__ = 'repositories'
476 __tablename__ = 'repositories'
477 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
477 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
478
478
479 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
479 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
480 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
480 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
481 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
481 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
482 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
482 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
484 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
484 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
485 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
485 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
486 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
486 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
487 description = Column("description", String(10000), nullable=True, unique=None, default=None)
487 description = Column("description", String(10000), nullable=True, unique=None, default=None)
488 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
488 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
489
489
490 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
490 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
491 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
491 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
492
492
493
493
494 user = relationship('User')
494 user = relationship('User')
495 fork = relationship('Repository', remote_side=repo_id)
495 fork = relationship('Repository', remote_side=repo_id)
496 group = relationship('RepoGroup')
496 group = relationship('RepoGroup')
497 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
497 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
498 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
498 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
499 stats = relationship('Statistics', cascade='all', uselist=False)
499 stats = relationship('Statistics', cascade='all', uselist=False)
500
500
501 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
501 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
502
502
503 logs = relationship('UserLog', cascade='all')
503 logs = relationship('UserLog', cascade='all')
504
504
505 def __repr__(self):
505 def __repr__(self):
506 return "<%s('%s:%s')>" % (self.__class__.__name__,
506 return "<%s('%s:%s')>" % (self.__class__.__name__,
507 self.repo_id, self.repo_name)
507 self.repo_id, self.repo_name)
508
508
509 @classmethod
509 @classmethod
510 def url_sep(cls):
510 def url_sep(cls):
511 return '/'
511 return '/'
512
512
513 @classmethod
513 @classmethod
514 def get_by_repo_name(cls, repo_name):
514 def get_by_repo_name(cls, repo_name):
515 q = Session.query(cls).filter(cls.repo_name == repo_name)
515 q = Session.query(cls).filter(cls.repo_name == repo_name)
516 q = q.options(joinedload(Repository.fork))\
516 q = q.options(joinedload(Repository.fork))\
517 .options(joinedload(Repository.user))\
517 .options(joinedload(Repository.user))\
518 .options(joinedload(Repository.group))
518 .options(joinedload(Repository.group))
519 return q.one()
519 return q.one()
520
520
521 @classmethod
521 @classmethod
522 def get_repo_forks(cls, repo_id):
522 def get_repo_forks(cls, repo_id):
523 return cls.query().filter(Repository.fork_id == repo_id)
523 return cls.query().filter(Repository.fork_id == repo_id)
524
524
525 @classmethod
525 @classmethod
526 def base_path(cls):
526 def base_path(cls):
527 """
527 """
528 Returns base path when all repos are stored
528 Returns base path when all repos are stored
529
529
530 :param cls:
530 :param cls:
531 """
531 """
532 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
532 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
533 cls.url_sep())
533 cls.url_sep())
534 q.options(FromCache("sql_cache_short", "repository_repo_path"))
534 q.options(FromCache("sql_cache_short", "repository_repo_path"))
535 return q.one().ui_value
535 return q.one().ui_value
536
536
537 @property
537 @property
538 def just_name(self):
538 def just_name(self):
539 return self.repo_name.split(Repository.url_sep())[-1]
539 return self.repo_name.split(Repository.url_sep())[-1]
540
540
541 @property
541 @property
542 def groups_with_parents(self):
542 def groups_with_parents(self):
543 groups = []
543 groups = []
544 if self.group is None:
544 if self.group is None:
545 return groups
545 return groups
546
546
547 cur_gr = self.group
547 cur_gr = self.group
548 groups.insert(0, cur_gr)
548 groups.insert(0, cur_gr)
549 while 1:
549 while 1:
550 gr = getattr(cur_gr, 'parent_group', None)
550 gr = getattr(cur_gr, 'parent_group', None)
551 cur_gr = cur_gr.parent_group
551 cur_gr = cur_gr.parent_group
552 if gr is None:
552 if gr is None:
553 break
553 break
554 groups.insert(0, gr)
554 groups.insert(0, gr)
555
555
556 return groups
556 return groups
557
557
558 @property
558 @property
559 def groups_and_repo(self):
559 def groups_and_repo(self):
560 return self.groups_with_parents, self.just_name
560 return self.groups_with_parents, self.just_name
561
561
562 @LazyProperty
562 @LazyProperty
563 def repo_path(self):
563 def repo_path(self):
564 """
564 """
565 Returns base full path for that repository means where it actually
565 Returns base full path for that repository means where it actually
566 exists on a filesystem
566 exists on a filesystem
567 """
567 """
568 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
568 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
569 Repository.url_sep())
569 Repository.url_sep())
570 q.options(FromCache("sql_cache_short", "repository_repo_path"))
570 q.options(FromCache("sql_cache_short", "repository_repo_path"))
571 return q.one().ui_value
571 return q.one().ui_value
572
572
573 @property
573 @property
574 def repo_full_path(self):
574 def repo_full_path(self):
575 p = [self.repo_path]
575 p = [self.repo_path]
576 # we need to split the name by / since this is how we store the
576 # we need to split the name by / since this is how we store the
577 # names in the database, but that eventually needs to be converted
577 # names in the database, but that eventually needs to be converted
578 # into a valid system path
578 # into a valid system path
579 p += self.repo_name.split(Repository.url_sep())
579 p += self.repo_name.split(Repository.url_sep())
580 return os.path.join(*p)
580 return os.path.join(*p)
581
581
582 def get_new_name(self, repo_name):
582 def get_new_name(self, repo_name):
583 """
583 """
584 returns new full repository name based on assigned group and new new
584 returns new full repository name based on assigned group and new new
585
585
586 :param group_name:
586 :param group_name:
587 """
587 """
588 path_prefix = self.group.full_path_splitted if self.group else []
588 path_prefix = self.group.full_path_splitted if self.group else []
589 return Repository.url_sep().join(path_prefix + [repo_name])
589 return Repository.url_sep().join(path_prefix + [repo_name])
590
590
591 @property
591 @property
592 def _config(self):
592 def _config(self):
593 """
593 """
594 Returns db based config object.
594 Returns db based config object.
595 """
595 """
596 from rhodecode.lib.utils import make_db_config
596 from rhodecode.lib.utils import make_db_config
597 return make_db_config(clear_session=False)
597 return make_db_config(clear_session=False)
598
598
599 @classmethod
599 @classmethod
600 def is_valid(cls, repo_name):
600 def is_valid(cls, repo_name):
601 """
601 """
602 returns True if given repo name is a valid filesystem repository
602 returns True if given repo name is a valid filesystem repository
603
603
604 :param cls:
604 :param cls:
605 :param repo_name:
605 :param repo_name:
606 """
606 """
607 from rhodecode.lib.utils import is_valid_repo
607 from rhodecode.lib.utils import is_valid_repo
608
608
609 return is_valid_repo(repo_name, cls.base_path())
609 return is_valid_repo(repo_name, cls.base_path())
610
610
611
611
612 #==========================================================================
612 #==========================================================================
613 # SCM PROPERTIES
613 # SCM PROPERTIES
614 #==========================================================================
614 #==========================================================================
615
615
616 def get_commit(self, rev):
616 def get_commit(self, rev):
617 return get_commit_safe(self.scm_instance, rev)
617 return get_commit_safe(self.scm_instance, rev)
618
618
619 @property
619 @property
620 def tip(self):
620 def tip(self):
621 return self.get_commit('tip')
621 return self.get_commit('tip')
622
622
623 @property
623 @property
624 def author(self):
624 def author(self):
625 return self.tip.author
625 return self.tip.author
626
626
627 @property
627 @property
628 def last_change(self):
628 def last_change(self):
629 return self.scm_instance.last_change
629 return self.scm_instance.last_change
630
630
631 #==========================================================================
631 #==========================================================================
632 # SCM CACHE INSTANCE
632 # SCM CACHE INSTANCE
633 #==========================================================================
633 #==========================================================================
634
634
635 @property
635 @property
636 def invalidate(self):
636 def invalidate(self):
637 return CacheInvalidation.invalidate(self.repo_name)
637 return CacheInvalidation.invalidate(self.repo_name)
638
638
639 def set_invalidate(self):
639 def set_invalidate(self):
640 """
640 """
641 set a cache for invalidation for this instance
641 set a cache for invalidation for this instance
642 """
642 """
643 CacheInvalidation.set_invalidate(self.repo_name)
643 CacheInvalidation.set_invalidate(self.repo_name)
644
644
645 @LazyProperty
645 @LazyProperty
646 def scm_instance(self):
646 def scm_instance(self):
647 return self.__get_instance()
647 return self.__get_instance()
648
648
649 @property
649 @property
650 def scm_instance_cached(self):
650 def scm_instance_cached(self):
651 @cache_region('long_term')
651 return self.__get_instance()
652 def _c(repo_name):
653 return self.__get_instance()
654 rn = self.repo_name
655
656 inv = self.invalidate
657 if inv is not None:
658 region_invalidate(_c, None, rn)
659 # update our cache
660 CacheInvalidation.set_valid(inv.cache_key)
661 return _c(rn)
662
652
663 def __get_instance(self):
653 def __get_instance(self):
664
654
665 repo_full_path = self.repo_full_path
655 repo_full_path = self.repo_full_path
666
656
667 try:
657 try:
668 alias = get_scm(repo_full_path)[0]
658 alias = get_scm(repo_full_path)[0]
669 log.debug('Creating instance of %s repository' % alias)
659 log.debug('Creating instance of %s repository' % alias)
670 backend = get_backend(alias)
660 backend = get_backend(alias)
671 except VCSError:
661 except VCSError:
672 log.error(traceback.format_exc())
662 log.error(traceback.format_exc())
673 log.error('Perhaps this repository is in db and not in '
663 log.error('Perhaps this repository is in db and not in '
674 'filesystem run rescan repositories with '
664 'filesystem run rescan repositories with '
675 '"destroy old data " option from admin panel')
665 '"destroy old data " option from admin panel')
676 return
666 return
677
667
678 if alias == 'hg':
668 if alias == 'hg':
679
669
680 repo = backend(safe_str(repo_full_path), create=False,
670 repo = backend(safe_str(repo_full_path), create=False,
681 config=self._config)
671 config=self._config)
682
672
683 else:
673 else:
684 repo = backend(repo_full_path, create=False)
674 repo = backend(repo_full_path, create=False)
685
675
686 return repo
676 return repo
687
677
688
678
689 class Group(Base, BaseModel):
679 class Group(Base, BaseModel):
690 __tablename__ = 'groups'
680 __tablename__ = 'groups'
691 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
681 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
692 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
682 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
693 __mapper_args__ = {'order_by':'group_name'}
683 __mapper_args__ = {'order_by':'group_name'}
694
684
695 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
685 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
696 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
686 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
697 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
687 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
698 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
688 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
699
689
700 parent_group = relationship('Group', remote_side=group_id)
690 parent_group = relationship('Group', remote_side=group_id)
701
691
702 def __init__(self, group_name='', parent_group=None):
692 def __init__(self, group_name='', parent_group=None):
703 self.group_name = group_name
693 self.group_name = group_name
704 self.parent_group = parent_group
694 self.parent_group = parent_group
705
695
706 def __repr__(self):
696 def __repr__(self):
707 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
697 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
708 self.group_name)
698 self.group_name)
709
699
710 @classmethod
700 @classmethod
711 def url_sep(cls):
701 def url_sep(cls):
712 return '/'
702 return '/'
713
703
714 @classmethod
704 @classmethod
715 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
705 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
716 if case_insensitive:
706 if case_insensitive:
717 gr = cls.query()\
707 gr = cls.query()\
718 .filter(cls.group_name.ilike(group_name))
708 .filter(cls.group_name.ilike(group_name))
719 else:
709 else:
720 gr = cls.query()\
710 gr = cls.query()\
721 .filter(cls.group_name == group_name)
711 .filter(cls.group_name == group_name)
722 if cache:
712 if cache:
723 gr = gr.options(FromCache("sql_cache_short",
713 gr = gr.options(FromCache("sql_cache_short",
724 "get_group_%s" % group_name))
714 "get_group_%s" % group_name))
725 return gr.scalar()
715 return gr.scalar()
726
716
727 @property
717 @property
728 def parents(self):
718 def parents(self):
729 parents_recursion_limit = 5
719 parents_recursion_limit = 5
730 groups = []
720 groups = []
731 if self.parent_group is None:
721 if self.parent_group is None:
732 return groups
722 return groups
733 cur_gr = self.parent_group
723 cur_gr = self.parent_group
734 groups.insert(0, cur_gr)
724 groups.insert(0, cur_gr)
735 cnt = 0
725 cnt = 0
736 while 1:
726 while 1:
737 cnt += 1
727 cnt += 1
738 gr = getattr(cur_gr, 'parent_group', None)
728 gr = getattr(cur_gr, 'parent_group', None)
739 cur_gr = cur_gr.parent_group
729 cur_gr = cur_gr.parent_group
740 if gr is None:
730 if gr is None:
741 break
731 break
742 if cnt == parents_recursion_limit:
732 if cnt == parents_recursion_limit:
743 # this will prevent accidental infinit loops
733 # this will prevent accidental infinit loops
744 log.error('group nested more than %s' %
734 log.error('group nested more than %s' %
745 parents_recursion_limit)
735 parents_recursion_limit)
746 break
736 break
747
737
748 groups.insert(0, gr)
738 groups.insert(0, gr)
749 return groups
739 return groups
750
740
751 @property
741 @property
752 def children(self):
742 def children(self):
753 return Group.query().filter(Group.parent_group == self)
743 return Group.query().filter(Group.parent_group == self)
754
744
755 @property
745 @property
756 def name(self):
746 def name(self):
757 return self.group_name.split(Group.url_sep())[-1]
747 return self.group_name.split(Group.url_sep())[-1]
758
748
759 @property
749 @property
760 def full_path(self):
750 def full_path(self):
761 return self.group_name
751 return self.group_name
762
752
763 @property
753 @property
764 def full_path_splitted(self):
754 def full_path_splitted(self):
765 return self.group_name.split(Group.url_sep())
755 return self.group_name.split(Group.url_sep())
766
756
767 @property
757 @property
768 def repositories(self):
758 def repositories(self):
769 return Repository.query().filter(Repository.group == self)
759 return Repository.query().filter(Repository.group == self)
770
760
771 @property
761 @property
772 def repositories_recursive_count(self):
762 def repositories_recursive_count(self):
773 cnt = self.repositories.count()
763 cnt = self.repositories.count()
774
764
775 def children_count(group):
765 def children_count(group):
776 cnt = 0
766 cnt = 0
777 for child in group.children:
767 for child in group.children:
778 cnt += child.repositories.count()
768 cnt += child.repositories.count()
779 cnt += children_count(child)
769 cnt += children_count(child)
780 return cnt
770 return cnt
781
771
782 return cnt + children_count(self)
772 return cnt + children_count(self)
783
773
784
774
785 def get_new_name(self, group_name):
775 def get_new_name(self, group_name):
786 """
776 """
787 returns new full group name based on parent and new name
777 returns new full group name based on parent and new name
788
778
789 :param group_name:
779 :param group_name:
790 """
780 """
791 path_prefix = (self.parent_group.full_path_splitted if
781 path_prefix = (self.parent_group.full_path_splitted if
792 self.parent_group else [])
782 self.parent_group else [])
793 return Group.url_sep().join(path_prefix + [group_name])
783 return Group.url_sep().join(path_prefix + [group_name])
794
784
795
785
796 class Permission(Base, BaseModel):
786 class Permission(Base, BaseModel):
797 __tablename__ = 'permissions'
787 __tablename__ = 'permissions'
798 __table_args__ = {'extend_existing':True}
788 __table_args__ = {'extend_existing':True}
799 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
789 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
800 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
790 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
801 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
791 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
802
792
803 def __repr__(self):
793 def __repr__(self):
804 return "<%s('%s:%s')>" % (self.__class__.__name__,
794 return "<%s('%s:%s')>" % (self.__class__.__name__,
805 self.permission_id, self.permission_name)
795 self.permission_id, self.permission_name)
806
796
807 @classmethod
797 @classmethod
808 def get_by_key(cls, key):
798 def get_by_key(cls, key):
809 return cls.query().filter(cls.permission_name == key).scalar()
799 return cls.query().filter(cls.permission_name == key).scalar()
810
800
811 class UserRepoToPerm(Base, BaseModel):
801 class UserRepoToPerm(Base, BaseModel):
812 __tablename__ = 'repo_to_perm'
802 __tablename__ = 'repo_to_perm'
813 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
803 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
814 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
804 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
815 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
805 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
816 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
806 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
817 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
807 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
818
808
819 user = relationship('User')
809 user = relationship('User')
820 permission = relationship('Permission')
810 permission = relationship('Permission')
821 repository = relationship('Repository')
811 repository = relationship('Repository')
822
812
823 class UserToPerm(Base, BaseModel):
813 class UserToPerm(Base, BaseModel):
824 __tablename__ = 'user_to_perm'
814 __tablename__ = 'user_to_perm'
825 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
815 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
826 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
816 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
827 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
817 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
828 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
818 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
829
819
830 user = relationship('User')
820 user = relationship('User')
831 permission = relationship('Permission')
821 permission = relationship('Permission')
832
822
833 @classmethod
823 @classmethod
834 def has_perm(cls, user_id, perm):
824 def has_perm(cls, user_id, perm):
835 if not isinstance(perm, Permission):
825 if not isinstance(perm, Permission):
836 raise Exception('perm needs to be an instance of Permission class')
826 raise Exception('perm needs to be an instance of Permission class')
837
827
838 return cls.query().filter(cls.user_id == user_id)\
828 return cls.query().filter(cls.user_id == user_id)\
839 .filter(cls.permission == perm).scalar() is not None
829 .filter(cls.permission == perm).scalar() is not None
840
830
841 @classmethod
831 @classmethod
842 def grant_perm(cls, user_id, perm):
832 def grant_perm(cls, user_id, perm):
843 if not isinstance(perm, Permission):
833 if not isinstance(perm, Permission):
844 raise Exception('perm needs to be an instance of Permission class')
834 raise Exception('perm needs to be an instance of Permission class')
845
835
846 new = cls()
836 new = cls()
847 new.user_id = user_id
837 new.user_id = user_id
848 new.permission = perm
838 new.permission = perm
849 try:
839 try:
850 Session.add(new)
840 Session.add(new)
851 Session.commit()
841 Session.commit()
852 except:
842 except:
853 Session.rollback()
843 Session.rollback()
854
844
855
845
856 @classmethod
846 @classmethod
857 def revoke_perm(cls, user_id, perm):
847 def revoke_perm(cls, user_id, perm):
858 if not isinstance(perm, Permission):
848 if not isinstance(perm, Permission):
859 raise Exception('perm needs to be an instance of Permission class')
849 raise Exception('perm needs to be an instance of Permission class')
860
850
861 try:
851 try:
862 cls.query().filter(cls.user_id == user_id) \
852 cls.query().filter(cls.user_id == user_id) \
863 .filter(cls.permission == perm).delete()
853 .filter(cls.permission == perm).delete()
864 Session.commit()
854 Session.commit()
865 except:
855 except:
866 Session.rollback()
856 Session.rollback()
867
857
868 class UserGroupRepoToPerm(Base, BaseModel):
858 class UserGroupRepoToPerm(Base, BaseModel):
869 __tablename__ = 'users_group_repo_to_perm'
859 __tablename__ = 'users_group_repo_to_perm'
870 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
860 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
871 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
861 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
872 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
862 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
863 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
874 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
864 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
875
865
876 users_group = relationship('UserGroup')
866 users_group = relationship('UserGroup')
877 permission = relationship('Permission')
867 permission = relationship('Permission')
878 repository = relationship('Repository')
868 repository = relationship('Repository')
879
869
880 def __repr__(self):
870 def __repr__(self):
881 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
871 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
882
872
883 class UserGroupToPerm(Base, BaseModel):
873 class UserGroupToPerm(Base, BaseModel):
884 __tablename__ = 'users_group_to_perm'
874 __tablename__ = 'users_group_to_perm'
885 __table_args__ = {'extend_existing':True}
875 __table_args__ = {'extend_existing':True}
886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
876 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
877 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
878 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
889
879
890 users_group = relationship('UserGroup')
880 users_group = relationship('UserGroup')
891 permission = relationship('Permission')
881 permission = relationship('Permission')
892
882
893
883
894 @classmethod
884 @classmethod
895 def has_perm(cls, users_group_id, perm):
885 def has_perm(cls, users_group_id, perm):
896 if not isinstance(perm, Permission):
886 if not isinstance(perm, Permission):
897 raise Exception('perm needs to be an instance of Permission class')
887 raise Exception('perm needs to be an instance of Permission class')
898
888
899 return cls.query().filter(cls.users_group_id ==
889 return cls.query().filter(cls.users_group_id ==
900 users_group_id)\
890 users_group_id)\
901 .filter(cls.permission == perm)\
891 .filter(cls.permission == perm)\
902 .scalar() is not None
892 .scalar() is not None
903
893
904 @classmethod
894 @classmethod
905 def grant_perm(cls, users_group_id, perm):
895 def grant_perm(cls, users_group_id, perm):
906 if not isinstance(perm, Permission):
896 if not isinstance(perm, Permission):
907 raise Exception('perm needs to be an instance of Permission class')
897 raise Exception('perm needs to be an instance of Permission class')
908
898
909 new = cls()
899 new = cls()
910 new.users_group_id = users_group_id
900 new.users_group_id = users_group_id
911 new.permission = perm
901 new.permission = perm
912 try:
902 try:
913 Session.add(new)
903 Session.add(new)
914 Session.commit()
904 Session.commit()
915 except:
905 except:
916 Session.rollback()
906 Session.rollback()
917
907
918
908
919 @classmethod
909 @classmethod
920 def revoke_perm(cls, users_group_id, perm):
910 def revoke_perm(cls, users_group_id, perm):
921 if not isinstance(perm, Permission):
911 if not isinstance(perm, Permission):
922 raise Exception('perm needs to be an instance of Permission class')
912 raise Exception('perm needs to be an instance of Permission class')
923
913
924 try:
914 try:
925 cls.query().filter(cls.users_group_id == users_group_id) \
915 cls.query().filter(cls.users_group_id == users_group_id) \
926 .filter(cls.permission == perm).delete()
916 .filter(cls.permission == perm).delete()
927 Session.commit()
917 Session.commit()
928 except:
918 except:
929 Session.rollback()
919 Session.rollback()
930
920
931
921
932 class UserRepoGroupToPerm(Base, BaseModel):
922 class UserRepoGroupToPerm(Base, BaseModel):
933 __tablename__ = 'group_to_perm'
923 __tablename__ = 'group_to_perm'
934 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
924 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
935
925
936 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
926 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
927 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
938 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
928 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
939 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
929 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
940
930
941 user = relationship('User')
931 user = relationship('User')
942 permission = relationship('Permission')
932 permission = relationship('Permission')
943 group = relationship('RepoGroup')
933 group = relationship('RepoGroup')
944
934
945 class Statistics(Base, BaseModel):
935 class Statistics(Base, BaseModel):
946 __tablename__ = 'statistics'
936 __tablename__ = 'statistics'
947 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
937 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
948 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
938 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
939 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
950 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
940 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
951 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
941 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
952 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
942 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
953 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
943 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
954
944
955 repository = relationship('Repository', single_parent=True)
945 repository = relationship('Repository', single_parent=True)
956
946
957 class UserFollowing(Base, BaseModel):
947 class UserFollowing(Base, BaseModel):
958 __tablename__ = 'user_followings'
948 __tablename__ = 'user_followings'
959 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
949 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
960 UniqueConstraint('user_id', 'follows_user_id')
950 UniqueConstraint('user_id', 'follows_user_id')
961 , {'extend_existing':True})
951 , {'extend_existing':True})
962
952
963 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
953 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
964 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
954 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
965 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
955 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
966 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
956 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
967 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
957 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
968
958
969 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
959 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
970
960
971 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
961 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
972 follows_repository = relationship('Repository', order_by='Repository.repo_name')
962 follows_repository = relationship('Repository', order_by='Repository.repo_name')
973
963
974
964
975 @classmethod
965 @classmethod
976 def get_repo_followers(cls, repo_id):
966 def get_repo_followers(cls, repo_id):
977 return cls.query().filter(cls.follows_repo_id == repo_id)
967 return cls.query().filter(cls.follows_repo_id == repo_id)
978
968
979 class CacheInvalidation(Base, BaseModel):
969 class CacheInvalidation(Base, BaseModel):
980 __tablename__ = 'cache_invalidation'
970 __tablename__ = 'cache_invalidation'
981 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
971 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
982 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
972 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
983 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
973 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
984 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
974 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
985 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
975 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
986
976
987
977
988 def __init__(self, cache_key, cache_args=''):
978 def __init__(self, cache_key, cache_args=''):
989 self.cache_key = cache_key
979 self.cache_key = cache_key
990 self.cache_args = cache_args
980 self.cache_args = cache_args
991 self.cache_active = False
981 self.cache_active = False
992
982
993 def __repr__(self):
983 def __repr__(self):
994 return "<%s('%s:%s')>" % (self.__class__.__name__,
984 return "<%s('%s:%s')>" % (self.__class__.__name__,
995 self.cache_id, self.cache_key)
985 self.cache_id, self.cache_key)
996
986
997 @classmethod
987 @classmethod
998 def invalidate(cls, key):
988 def invalidate(cls, key):
999 """
989 """
1000 Returns Invalidation object if this given key should be invalidated
990 Returns Invalidation object if this given key should be invalidated
1001 None otherwise. `cache_active = False` means that this cache
991 None otherwise. `cache_active = False` means that this cache
1002 state is not valid and needs to be invalidated
992 state is not valid and needs to be invalidated
1003
993
1004 :param key:
994 :param key:
1005 """
995 """
1006 return cls.query()\
996 return cls.query()\
1007 .filter(CacheInvalidation.cache_key == key)\
997 .filter(CacheInvalidation.cache_key == key)\
1008 .filter(CacheInvalidation.cache_active == False)\
998 .filter(CacheInvalidation.cache_active == False)\
1009 .scalar()
999 .scalar()
1010
1000
1011 @classmethod
1001 @classmethod
1012 def set_invalidate(cls, key):
1002 def set_invalidate(cls, key):
1013 """
1003 """
1014 Mark this Cache key for invalidation
1004 Mark this Cache key for invalidation
1015
1005
1016 :param key:
1006 :param key:
1017 """
1007 """
1018
1008
1019 log.debug('marking %s for invalidation' % key)
1009 log.debug('marking %s for invalidation' % key)
1020 inv_obj = Session.query(cls)\
1010 inv_obj = Session.query(cls)\
1021 .filter(cls.cache_key == key).scalar()
1011 .filter(cls.cache_key == key).scalar()
1022 if inv_obj:
1012 if inv_obj:
1023 inv_obj.cache_active = False
1013 inv_obj.cache_active = False
1024 else:
1014 else:
1025 log.debug('cache key not found in invalidation db -> creating one')
1015 log.debug('cache key not found in invalidation db -> creating one')
1026 inv_obj = CacheInvalidation(key)
1016 inv_obj = CacheInvalidation(key)
1027
1017
1028 try:
1018 try:
1029 Session.add(inv_obj)
1019 Session.add(inv_obj)
1030 Session.commit()
1020 Session.commit()
1031 except Exception:
1021 except Exception:
1032 log.error(traceback.format_exc())
1022 log.error(traceback.format_exc())
1033 Session.rollback()
1023 Session.rollback()
1034
1024
1035 @classmethod
1025 @classmethod
1036 def set_valid(cls, key):
1026 def set_valid(cls, key):
1037 """
1027 """
1038 Mark this cache key as active and currently cached
1028 Mark this cache key as active and currently cached
1039
1029
1040 :param key:
1030 :param key:
1041 """
1031 """
1042 inv_obj = Session.query(CacheInvalidation)\
1032 inv_obj = Session.query(CacheInvalidation)\
1043 .filter(CacheInvalidation.cache_key == key).scalar()
1033 .filter(CacheInvalidation.cache_key == key).scalar()
1044 inv_obj.cache_active = True
1034 inv_obj.cache_active = True
1045 Session.add(inv_obj)
1035 Session.add(inv_obj)
1046 Session.commit()
1036 Session.commit()
1047
1037
1048 class DbMigrateVersion(Base, BaseModel):
1038 class DbMigrateVersion(Base, BaseModel):
1049 __tablename__ = 'db_migrate_version'
1039 __tablename__ = 'db_migrate_version'
1050 __table_args__ = {'extend_existing':True}
1040 __table_args__ = {'extend_existing':True}
1051 repository_id = Column('repository_id', String(250), primary_key=True)
1041 repository_id = Column('repository_id', String(250), primary_key=True)
1052 repository_path = Column('repository_path', Text)
1042 repository_path = Column('repository_path', Text)
1053 version = Column('version', Integer)
1043 version = Column('version', Integer)
@@ -1,1276 +1,1266 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import logging
22 import logging
23 import datetime
23 import datetime
24 import traceback
24 import traceback
25 from collections import defaultdict
25 from collections import defaultdict
26
26
27 from sqlalchemy import *
27 from sqlalchemy import *
28 from sqlalchemy.ext.hybrid import hybrid_property
28 from sqlalchemy.ext.hybrid import hybrid_property
29 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
29 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
30 from beaker.cache import cache_region, region_invalidate
30 from beaker.cache import cache_region, region_invalidate
31
31
32 from rhodecode.lib.vcs import get_backend
32 from rhodecode.lib.vcs import get_backend
33 from rhodecode.lib.vcs.utils.helpers import get_scm
33 from rhodecode.lib.vcs.utils.helpers import get_scm
34 from rhodecode.lib.vcs.exceptions import VCSError
34 from rhodecode.lib.vcs.exceptions import VCSError
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
38 safe_unicode
38 safe_unicode
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.caching_query import FromCache
41
41
42 from rhodecode.model.meta import Base, Session
42 from rhodecode.model.meta import Base, Session
43 import hashlib
43 import hashlib
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48 #==============================================================================
48 #==============================================================================
49 # BASE CLASSES
49 # BASE CLASSES
50 #==============================================================================
50 #==============================================================================
51
51
52 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
52 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
53
53
54
54
55 class ModelSerializer(json.JSONEncoder):
55 class ModelSerializer(json.JSONEncoder):
56 """
56 """
57 Simple Serializer for JSON,
57 Simple Serializer for JSON,
58
58
59 usage::
59 usage::
60
60
61 to make object customized for serialization implement a __json__
61 to make object customized for serialization implement a __json__
62 method that will return a dict for serialization into json
62 method that will return a dict for serialization into json
63
63
64 example::
64 example::
65
65
66 class Task(object):
66 class Task(object):
67
67
68 def __init__(self, name, value):
68 def __init__(self, name, value):
69 self.name = name
69 self.name = name
70 self.value = value
70 self.value = value
71
71
72 def __json__(self):
72 def __json__(self):
73 return dict(name=self.name,
73 return dict(name=self.name,
74 value=self.value)
74 value=self.value)
75
75
76 """
76 """
77
77
78 def default(self, obj):
78 def default(self, obj):
79
79
80 if hasattr(obj, '__json__'):
80 if hasattr(obj, '__json__'):
81 return obj.__json__()
81 return obj.__json__()
82 else:
82 else:
83 return json.JSONEncoder.default(self, obj)
83 return json.JSONEncoder.default(self, obj)
84
84
85
85
86 class BaseModel(object):
86 class BaseModel(object):
87 """
87 """
88 Base Model for all classess
88 Base Model for all classess
89 """
89 """
90
90
91 @classmethod
91 @classmethod
92 def _get_keys(cls):
92 def _get_keys(cls):
93 """return column names for this model """
93 """return column names for this model """
94 return class_mapper(cls).c.keys()
94 return class_mapper(cls).c.keys()
95
95
96 def get_dict(self):
96 def get_dict(self):
97 """
97 """
98 return dict with keys and values corresponding
98 return dict with keys and values corresponding
99 to this model data """
99 to this model data """
100
100
101 d = {}
101 d = {}
102 for k in self._get_keys():
102 for k in self._get_keys():
103 d[k] = getattr(self, k)
103 d[k] = getattr(self, k)
104
104
105 # also use __json__() if present to get additional fields
105 # also use __json__() if present to get additional fields
106 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
106 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
107 d[k] = val
107 d[k] = val
108 return d
108 return d
109
109
110 def get_appstruct(self):
110 def get_appstruct(self):
111 """return list with keys and values tupples corresponding
111 """return list with keys and values tupples corresponding
112 to this model data """
112 to this model data """
113
113
114 l = []
114 l = []
115 for k in self._get_keys():
115 for k in self._get_keys():
116 l.append((k, getattr(self, k),))
116 l.append((k, getattr(self, k),))
117 return l
117 return l
118
118
119 def populate_obj(self, populate_dict):
119 def populate_obj(self, populate_dict):
120 """populate model with data from given populate_dict"""
120 """populate model with data from given populate_dict"""
121
121
122 for k in self._get_keys():
122 for k in self._get_keys():
123 if k in populate_dict:
123 if k in populate_dict:
124 setattr(self, k, populate_dict[k])
124 setattr(self, k, populate_dict[k])
125
125
126 @classmethod
126 @classmethod
127 def query(cls):
127 def query(cls):
128 return Session.query(cls)
128 return Session.query(cls)
129
129
130 @classmethod
130 @classmethod
131 def get(cls, id_):
131 def get(cls, id_):
132 if id_:
132 if id_:
133 return cls.query().get(id_)
133 return cls.query().get(id_)
134
134
135 @classmethod
135 @classmethod
136 def getAll(cls):
136 def getAll(cls):
137 return cls.query().all()
137 return cls.query().all()
138
138
139 @classmethod
139 @classmethod
140 def delete(cls, id_):
140 def delete(cls, id_):
141 obj = cls.query().get(id_)
141 obj = cls.query().get(id_)
142 Session.delete(obj)
142 Session.delete(obj)
143
143
144 def __repr__(self):
144 def __repr__(self):
145 if hasattr(self, '__unicode__'):
145 if hasattr(self, '__unicode__'):
146 # python repr needs to return str
146 # python repr needs to return str
147 return safe_str(self.__unicode__())
147 return safe_str(self.__unicode__())
148 return '<DB:%s>' % (self.__class__.__name__)
148 return '<DB:%s>' % (self.__class__.__name__)
149
149
150
150
151 class RhodeCodeSetting(Base, BaseModel):
151 class RhodeCodeSetting(Base, BaseModel):
152 __tablename__ = 'rhodecode_settings'
152 __tablename__ = 'rhodecode_settings'
153 __table_args__ = (
153 __table_args__ = (
154 UniqueConstraint('app_settings_name'),
154 UniqueConstraint('app_settings_name'),
155 {'extend_existing': True, 'mysql_engine':'InnoDB',
155 {'extend_existing': True, 'mysql_engine':'InnoDB',
156 'mysql_charset': 'utf8'}
156 'mysql_charset': 'utf8'}
157 )
157 )
158 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
158 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
159 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
159 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
160 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
160 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
161
161
162 def __init__(self, k='', v=''):
162 def __init__(self, k='', v=''):
163 self.app_settings_name = k
163 self.app_settings_name = k
164 self.app_settings_value = v
164 self.app_settings_value = v
165
165
166 @validates('_app_settings_value')
166 @validates('_app_settings_value')
167 def validate_settings_value(self, key, val):
167 def validate_settings_value(self, key, val):
168 assert type(val) == unicode
168 assert type(val) == unicode
169 return val
169 return val
170
170
171 @hybrid_property
171 @hybrid_property
172 def app_settings_value(self):
172 def app_settings_value(self):
173 v = self._app_settings_value
173 v = self._app_settings_value
174 if self.app_settings_name == 'ldap_active':
174 if self.app_settings_name == 'ldap_active':
175 v = str2bool(v)
175 v = str2bool(v)
176 return v
176 return v
177
177
178 @app_settings_value.setter
178 @app_settings_value.setter
179 def app_settings_value(self, val):
179 def app_settings_value(self, val):
180 """
180 """
181 Setter that will always make sure we use unicode in app_settings_value
181 Setter that will always make sure we use unicode in app_settings_value
182
182
183 :param val:
183 :param val:
184 """
184 """
185 self._app_settings_value = safe_unicode(val)
185 self._app_settings_value = safe_unicode(val)
186
186
187 def __unicode__(self):
187 def __unicode__(self):
188 return u"<%s('%s:%s')>" % (
188 return u"<%s('%s:%s')>" % (
189 self.__class__.__name__,
189 self.__class__.__name__,
190 self.app_settings_name, self.app_settings_value
190 self.app_settings_name, self.app_settings_value
191 )
191 )
192
192
193 @classmethod
193 @classmethod
194 def get_by_name(cls, ldap_key):
194 def get_by_name(cls, ldap_key):
195 return cls.query()\
195 return cls.query()\
196 .filter(cls.app_settings_name == ldap_key).scalar()
196 .filter(cls.app_settings_name == ldap_key).scalar()
197
197
198 @classmethod
198 @classmethod
199 def get_app_settings(cls, cache=False):
199 def get_app_settings(cls, cache=False):
200
200
201 ret = cls.query()
201 ret = cls.query()
202
202
203 if cache:
203 if cache:
204 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
204 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
205
205
206 if not ret:
206 if not ret:
207 raise Exception('Could not get application settings !')
207 raise Exception('Could not get application settings !')
208 settings = {}
208 settings = {}
209 for each in ret:
209 for each in ret:
210 settings['rhodecode_' + each.app_settings_name] = \
210 settings['rhodecode_' + each.app_settings_name] = \
211 each.app_settings_value
211 each.app_settings_value
212
212
213 return settings
213 return settings
214
214
215 @classmethod
215 @classmethod
216 def get_ldap_settings(cls, cache=False):
216 def get_ldap_settings(cls, cache=False):
217 ret = cls.query()\
217 ret = cls.query()\
218 .filter(cls.app_settings_name.startswith('ldap_')).all()
218 .filter(cls.app_settings_name.startswith('ldap_')).all()
219 fd = {}
219 fd = {}
220 for row in ret:
220 for row in ret:
221 fd.update({row.app_settings_name:row.app_settings_value})
221 fd.update({row.app_settings_name:row.app_settings_value})
222
222
223 return fd
223 return fd
224
224
225
225
226 class RhodeCodeUi(Base, BaseModel):
226 class RhodeCodeUi(Base, BaseModel):
227 __tablename__ = 'rhodecode_ui'
227 __tablename__ = 'rhodecode_ui'
228 __table_args__ = (
228 __table_args__ = (
229 UniqueConstraint('ui_key'),
229 UniqueConstraint('ui_key'),
230 {'extend_existing': True, 'mysql_engine':'InnoDB',
230 {'extend_existing': True, 'mysql_engine':'InnoDB',
231 'mysql_charset': 'utf8'}
231 'mysql_charset': 'utf8'}
232 )
232 )
233
233
234 HOOK_REPO_SIZE = 'changegroup.repo_size'
234 HOOK_REPO_SIZE = 'changegroup.repo_size'
235 HOOK_PUSH = 'pretxnchangegroup.push_logger'
235 HOOK_PUSH = 'pretxnchangegroup.push_logger'
236 HOOK_PULL = 'preoutgoing.pull_logger'
236 HOOK_PULL = 'preoutgoing.pull_logger'
237
237
238 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
238 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
239 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
239 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
240 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
240 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
241 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
241 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
242 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
242 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
243
243
244 @classmethod
244 @classmethod
245 def get_by_key(cls, key):
245 def get_by_key(cls, key):
246 return cls.query().filter(cls.ui_key == key)
246 return cls.query().filter(cls.ui_key == key)
247
247
248 @classmethod
248 @classmethod
249 def get_builtin_hooks(cls):
249 def get_builtin_hooks(cls):
250 q = cls.query()
250 q = cls.query()
251 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
251 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
252 cls.HOOK_PUSH, cls.HOOK_PULL]))
252 cls.HOOK_PUSH, cls.HOOK_PULL]))
253 return q.all()
253 return q.all()
254
254
255 @classmethod
255 @classmethod
256 def get_custom_hooks(cls):
256 def get_custom_hooks(cls):
257 q = cls.query()
257 q = cls.query()
258 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
258 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
259 cls.HOOK_PUSH, cls.HOOK_PULL]))
259 cls.HOOK_PUSH, cls.HOOK_PULL]))
260 q = q.filter(cls.ui_section == 'hooks')
260 q = q.filter(cls.ui_section == 'hooks')
261 return q.all()
261 return q.all()
262
262
263 @classmethod
263 @classmethod
264 def create_or_update_hook(cls, key, val):
264 def create_or_update_hook(cls, key, val):
265 new_ui = cls.get_by_key(key).scalar() or cls()
265 new_ui = cls.get_by_key(key).scalar() or cls()
266 new_ui.ui_section = 'hooks'
266 new_ui.ui_section = 'hooks'
267 new_ui.ui_active = True
267 new_ui.ui_active = True
268 new_ui.ui_key = key
268 new_ui.ui_key = key
269 new_ui.ui_value = val
269 new_ui.ui_value = val
270
270
271 Session.add(new_ui)
271 Session.add(new_ui)
272
272
273
273
274 class User(Base, BaseModel):
274 class User(Base, BaseModel):
275 __tablename__ = 'users'
275 __tablename__ = 'users'
276 __table_args__ = (
276 __table_args__ = (
277 UniqueConstraint('username'), UniqueConstraint('email'),
277 UniqueConstraint('username'), UniqueConstraint('email'),
278 {'extend_existing': True, 'mysql_engine':'InnoDB',
278 {'extend_existing': True, 'mysql_engine':'InnoDB',
279 'mysql_charset': 'utf8'}
279 'mysql_charset': 'utf8'}
280 )
280 )
281 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
281 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
282 username = Column("username", String(255), nullable=True, unique=None, default=None)
282 username = Column("username", String(255), nullable=True, unique=None, default=None)
283 password = Column("password", String(255), nullable=True, unique=None, default=None)
283 password = Column("password", String(255), nullable=True, unique=None, default=None)
284 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
284 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
285 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
285 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
286 name = Column("name", String(255), nullable=True, unique=None, default=None)
286 name = Column("name", String(255), nullable=True, unique=None, default=None)
287 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
287 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
288 _email = Column("email", String(255), nullable=True, unique=None, default=None)
288 _email = Column("email", String(255), nullable=True, unique=None, default=None)
289 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
289 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
290 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
290 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
291 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
291 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
292
292
293 user_log = relationship('UserLog', cascade='all')
293 user_log = relationship('UserLog', cascade='all')
294 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
294 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
295
295
296 repositories = relationship('Repository')
296 repositories = relationship('Repository')
297 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
297 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
298 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
298 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
299 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
299 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
300
300
301 group_member = relationship('UserGroupMember', cascade='all')
301 group_member = relationship('UserGroupMember', cascade='all')
302
302
303 notifications = relationship('UserNotification', cascade='all')
303 notifications = relationship('UserNotification', cascade='all')
304 # notifications assigned to this user
304 # notifications assigned to this user
305 user_created_notifications = relationship('Notification', cascade='all')
305 user_created_notifications = relationship('Notification', cascade='all')
306 # comments created by this user
306 # comments created by this user
307 user_comments = relationship('ChangesetComment', cascade='all')
307 user_comments = relationship('ChangesetComment', cascade='all')
308
308
309 @hybrid_property
309 @hybrid_property
310 def email(self):
310 def email(self):
311 return self._email
311 return self._email
312
312
313 @email.setter
313 @email.setter
314 def email(self, val):
314 def email(self, val):
315 self._email = val.lower() if val else None
315 self._email = val.lower() if val else None
316
316
317 @property
317 @property
318 def full_name(self):
318 def full_name(self):
319 return '%s %s' % (self.name, self.lastname)
319 return '%s %s' % (self.name, self.lastname)
320
320
321 @property
321 @property
322 def full_name_or_username(self):
322 def full_name_or_username(self):
323 return ('%s %s' % (self.name, self.lastname)
323 return ('%s %s' % (self.name, self.lastname)
324 if (self.name and self.lastname) else self.username)
324 if (self.name and self.lastname) else self.username)
325
325
326 @property
326 @property
327 def full_contact(self):
327 def full_contact(self):
328 return '%s %s <%s>' % (self.name, self.lastname, self.email)
328 return '%s %s <%s>' % (self.name, self.lastname, self.email)
329
329
330 @property
330 @property
331 def short_contact(self):
331 def short_contact(self):
332 return '%s %s' % (self.name, self.lastname)
332 return '%s %s' % (self.name, self.lastname)
333
333
334 @property
334 @property
335 def is_admin(self):
335 def is_admin(self):
336 return self.admin
336 return self.admin
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
339 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
340 self.user_id, self.username)
340 self.user_id, self.username)
341
341
342 @classmethod
342 @classmethod
343 def get_by_username(cls, username, case_insensitive=False, cache=False):
343 def get_by_username(cls, username, case_insensitive=False, cache=False):
344 if case_insensitive:
344 if case_insensitive:
345 q = cls.query().filter(cls.username.ilike(username))
345 q = cls.query().filter(cls.username.ilike(username))
346 else:
346 else:
347 q = cls.query().filter(cls.username == username)
347 q = cls.query().filter(cls.username == username)
348
348
349 if cache:
349 if cache:
350 q = q.options(FromCache(
350 q = q.options(FromCache(
351 "sql_cache_short",
351 "sql_cache_short",
352 "get_user_%s" % _hash_key(username)
352 "get_user_%s" % _hash_key(username)
353 )
353 )
354 )
354 )
355 return q.scalar()
355 return q.scalar()
356
356
357 @classmethod
357 @classmethod
358 def get_by_auth_token(cls, auth_token, cache=False):
358 def get_by_auth_token(cls, auth_token, cache=False):
359 q = cls.query().filter(cls.api_key == auth_token)
359 q = cls.query().filter(cls.api_key == auth_token)
360
360
361 if cache:
361 if cache:
362 q = q.options(FromCache("sql_cache_short",
362 q = q.options(FromCache("sql_cache_short",
363 "get_auth_token_%s" % auth_token))
363 "get_auth_token_%s" % auth_token))
364 return q.scalar()
364 return q.scalar()
365
365
366 @classmethod
366 @classmethod
367 def get_by_email(cls, email, case_insensitive=False, cache=False):
367 def get_by_email(cls, email, case_insensitive=False, cache=False):
368 if case_insensitive:
368 if case_insensitive:
369 q = cls.query().filter(cls.email.ilike(email))
369 q = cls.query().filter(cls.email.ilike(email))
370 else:
370 else:
371 q = cls.query().filter(cls.email == email)
371 q = cls.query().filter(cls.email == email)
372
372
373 if cache:
373 if cache:
374 q = q.options(FromCache("sql_cache_short",
374 q = q.options(FromCache("sql_cache_short",
375 "get_auth_token_%s" % email))
375 "get_auth_token_%s" % email))
376 return q.scalar()
376 return q.scalar()
377
377
378 def update_lastlogin(self):
378 def update_lastlogin(self):
379 """Update user lastlogin"""
379 """Update user lastlogin"""
380 self.last_login = datetime.datetime.now()
380 self.last_login = datetime.datetime.now()
381 Session.add(self)
381 Session.add(self)
382 log.debug('updated user %s lastlogin' % self.username)
382 log.debug('updated user %s lastlogin' % self.username)
383
383
384 def __json__(self):
384 def __json__(self):
385 return dict(
385 return dict(
386 user_id=self.user_id,
386 user_id=self.user_id,
387 first_name=self.name,
387 first_name=self.name,
388 last_name=self.lastname,
388 last_name=self.lastname,
389 email=self.email,
389 email=self.email,
390 full_name=self.full_name,
390 full_name=self.full_name,
391 full_name_or_username=self.full_name_or_username,
391 full_name_or_username=self.full_name_or_username,
392 short_contact=self.short_contact,
392 short_contact=self.short_contact,
393 full_contact=self.full_contact
393 full_contact=self.full_contact
394 )
394 )
395
395
396
396
397 class UserLog(Base, BaseModel):
397 class UserLog(Base, BaseModel):
398 __tablename__ = 'user_logs'
398 __tablename__ = 'user_logs'
399 __table_args__ = (
399 __table_args__ = (
400 {'extend_existing': True, 'mysql_engine':'InnoDB',
400 {'extend_existing': True, 'mysql_engine':'InnoDB',
401 'mysql_charset': 'utf8'},
401 'mysql_charset': 'utf8'},
402 )
402 )
403 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
403 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
404 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
404 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
406 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
406 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
407 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
407 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
408 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
408 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
409 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
409 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
410
410
411 @property
411 @property
412 def action_as_day(self):
412 def action_as_day(self):
413 return datetime.date(*self.action_date.timetuple()[:3])
413 return datetime.date(*self.action_date.timetuple()[:3])
414
414
415 user = relationship('User')
415 user = relationship('User')
416 repository = relationship('Repository', cascade='')
416 repository = relationship('Repository', cascade='')
417
417
418
418
419 class UserGroup(Base, BaseModel):
419 class UserGroup(Base, BaseModel):
420 __tablename__ = 'users_groups'
420 __tablename__ = 'users_groups'
421 __table_args__ = (
421 __table_args__ = (
422 {'extend_existing': True, 'mysql_engine':'InnoDB',
422 {'extend_existing': True, 'mysql_engine':'InnoDB',
423 'mysql_charset': 'utf8'},
423 'mysql_charset': 'utf8'},
424 )
424 )
425
425
426 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
426 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
427 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
427 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
428 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
428 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
429
429
430 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
430 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
431 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
431 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
432 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
432 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
433
433
434 def __unicode__(self):
434 def __unicode__(self):
435 return u'<userGroup(%s)>' % (self.users_group_name)
435 return u'<userGroup(%s)>' % (self.users_group_name)
436
436
437 @classmethod
437 @classmethod
438 def get_by_group_name(cls, group_name, cache=False,
438 def get_by_group_name(cls, group_name, cache=False,
439 case_insensitive=False):
439 case_insensitive=False):
440 if case_insensitive:
440 if case_insensitive:
441 q = cls.query().filter(cls.users_group_name.ilike(group_name))
441 q = cls.query().filter(cls.users_group_name.ilike(group_name))
442 else:
442 else:
443 q = cls.query().filter(cls.users_group_name == group_name)
443 q = cls.query().filter(cls.users_group_name == group_name)
444 if cache:
444 if cache:
445 q = q.options(FromCache(
445 q = q.options(FromCache(
446 "sql_cache_short",
446 "sql_cache_short",
447 "get_user_%s" % _hash_key(group_name)
447 "get_user_%s" % _hash_key(group_name)
448 )
448 )
449 )
449 )
450 return q.scalar()
450 return q.scalar()
451
451
452 @classmethod
452 @classmethod
453 def get(cls, users_group_id, cache=False):
453 def get(cls, users_group_id, cache=False):
454 users_group = cls.query()
454 users_group = cls.query()
455 if cache:
455 if cache:
456 users_group = users_group.options(FromCache("sql_cache_short",
456 users_group = users_group.options(FromCache("sql_cache_short",
457 "get_users_group_%s" % users_group_id))
457 "get_users_group_%s" % users_group_id))
458 return users_group.get(users_group_id)
458 return users_group.get(users_group_id)
459
459
460
460
461 class UserGroupMember(Base, BaseModel):
461 class UserGroupMember(Base, BaseModel):
462 __tablename__ = 'users_groups_members'
462 __tablename__ = 'users_groups_members'
463 __table_args__ = (
463 __table_args__ = (
464 {'extend_existing': True, 'mysql_engine':'InnoDB',
464 {'extend_existing': True, 'mysql_engine':'InnoDB',
465 'mysql_charset': 'utf8'},
465 'mysql_charset': 'utf8'},
466 )
466 )
467
467
468 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
468 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
469 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
469 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
471
471
472 user = relationship('User', lazy='joined')
472 user = relationship('User', lazy='joined')
473 users_group = relationship('UserGroup')
473 users_group = relationship('UserGroup')
474
474
475 def __init__(self, gr_id='', u_id=''):
475 def __init__(self, gr_id='', u_id=''):
476 self.users_group_id = gr_id
476 self.users_group_id = gr_id
477 self.user_id = u_id
477 self.user_id = u_id
478
478
479
479
480 class Repository(Base, BaseModel):
480 class Repository(Base, BaseModel):
481 __tablename__ = 'repositories'
481 __tablename__ = 'repositories'
482 __table_args__ = (
482 __table_args__ = (
483 UniqueConstraint('repo_name'),
483 UniqueConstraint('repo_name'),
484 {'extend_existing': True, 'mysql_engine':'InnoDB',
484 {'extend_existing': True, 'mysql_engine':'InnoDB',
485 'mysql_charset': 'utf8'},
485 'mysql_charset': 'utf8'},
486 )
486 )
487
487
488 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
488 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
489 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
490 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
490 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
491 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
491 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
493 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
493 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
494 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
494 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
495 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
495 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
496 description = Column("description", String(10000), nullable=True, unique=None, default=None)
496 description = Column("description", String(10000), nullable=True, unique=None, default=None)
497 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
497 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498
498
499 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
499 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
500 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
500 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
501
501
502 user = relationship('User')
502 user = relationship('User')
503 fork = relationship('Repository', remote_side=repo_id)
503 fork = relationship('Repository', remote_side=repo_id)
504 group = relationship('RepoGroup')
504 group = relationship('RepoGroup')
505 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
505 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
506 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
506 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
507 stats = relationship('Statistics', cascade='all', uselist=False)
507 stats = relationship('Statistics', cascade='all', uselist=False)
508
508
509 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
509 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
510
510
511 logs = relationship('UserLog')
511 logs = relationship('UserLog')
512
512
513 def __unicode__(self):
513 def __unicode__(self):
514 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
514 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
515 self.repo_name)
515 self.repo_name)
516
516
517 @classmethod
517 @classmethod
518 def url_sep(cls):
518 def url_sep(cls):
519 return '/'
519 return '/'
520
520
521 @classmethod
521 @classmethod
522 def get_by_repo_name(cls, repo_name):
522 def get_by_repo_name(cls, repo_name):
523 q = Session.query(cls).filter(cls.repo_name == repo_name)
523 q = Session.query(cls).filter(cls.repo_name == repo_name)
524 q = q.options(joinedload(Repository.fork))\
524 q = q.options(joinedload(Repository.fork))\
525 .options(joinedload(Repository.user))\
525 .options(joinedload(Repository.user))\
526 .options(joinedload(Repository.group))
526 .options(joinedload(Repository.group))
527 return q.scalar()
527 return q.scalar()
528
528
529 @classmethod
529 @classmethod
530 def get_repo_forks(cls, repo_id):
530 def get_repo_forks(cls, repo_id):
531 return cls.query().filter(Repository.fork_id == repo_id)
531 return cls.query().filter(Repository.fork_id == repo_id)
532
532
533 @classmethod
533 @classmethod
534 def base_path(cls):
534 def base_path(cls):
535 """
535 """
536 Returns base path when all repos are stored
536 Returns base path when all repos are stored
537
537
538 :param cls:
538 :param cls:
539 """
539 """
540 q = Session.query(RhodeCodeUi)\
540 q = Session.query(RhodeCodeUi)\
541 .filter(RhodeCodeUi.ui_key == cls.url_sep())
541 .filter(RhodeCodeUi.ui_key == cls.url_sep())
542 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
542 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
543 return q.one().ui_value
543 return q.one().ui_value
544
544
545 @property
545 @property
546 def just_name(self):
546 def just_name(self):
547 return self.repo_name.split(Repository.url_sep())[-1]
547 return self.repo_name.split(Repository.url_sep())[-1]
548
548
549 @property
549 @property
550 def groups_with_parents(self):
550 def groups_with_parents(self):
551 groups = []
551 groups = []
552 if self.group is None:
552 if self.group is None:
553 return groups
553 return groups
554
554
555 cur_gr = self.group
555 cur_gr = self.group
556 groups.insert(0, cur_gr)
556 groups.insert(0, cur_gr)
557 while 1:
557 while 1:
558 gr = getattr(cur_gr, 'parent_group', None)
558 gr = getattr(cur_gr, 'parent_group', None)
559 cur_gr = cur_gr.parent_group
559 cur_gr = cur_gr.parent_group
560 if gr is None:
560 if gr is None:
561 break
561 break
562 groups.insert(0, gr)
562 groups.insert(0, gr)
563
563
564 return groups
564 return groups
565
565
566 @property
566 @property
567 def groups_and_repo(self):
567 def groups_and_repo(self):
568 return self.groups_with_parents, self.just_name
568 return self.groups_with_parents, self.just_name
569
569
570 @LazyProperty
570 @LazyProperty
571 def repo_path(self):
571 def repo_path(self):
572 """
572 """
573 Returns base full path for that repository means where it actually
573 Returns base full path for that repository means where it actually
574 exists on a filesystem
574 exists on a filesystem
575 """
575 """
576 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
576 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
577 Repository.url_sep())
577 Repository.url_sep())
578 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
578 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
579 return q.one().ui_value
579 return q.one().ui_value
580
580
581 @property
581 @property
582 def repo_full_path(self):
582 def repo_full_path(self):
583 p = [self.repo_path]
583 p = [self.repo_path]
584 # we need to split the name by / since this is how we store the
584 # we need to split the name by / since this is how we store the
585 # names in the database, but that eventually needs to be converted
585 # names in the database, but that eventually needs to be converted
586 # into a valid system path
586 # into a valid system path
587 p += self.repo_name.split(Repository.url_sep())
587 p += self.repo_name.split(Repository.url_sep())
588 return os.path.join(*p)
588 return os.path.join(*p)
589
589
590 def get_new_name(self, repo_name):
590 def get_new_name(self, repo_name):
591 """
591 """
592 returns new full repository name based on assigned group and new new
592 returns new full repository name based on assigned group and new new
593
593
594 :param group_name:
594 :param group_name:
595 """
595 """
596 path_prefix = self.group.full_path_splitted if self.group else []
596 path_prefix = self.group.full_path_splitted if self.group else []
597 return Repository.url_sep().join(path_prefix + [repo_name])
597 return Repository.url_sep().join(path_prefix + [repo_name])
598
598
599 @property
599 @property
600 def _config(self):
600 def _config(self):
601 """
601 """
602 Returns db based config object.
602 Returns db based config object.
603 """
603 """
604 from rhodecode.lib.utils import make_db_config
604 from rhodecode.lib.utils import make_db_config
605 return make_db_config(clear_session=False)
605 return make_db_config(clear_session=False)
606
606
607 @classmethod
607 @classmethod
608 def is_valid(cls, repo_name):
608 def is_valid(cls, repo_name):
609 """
609 """
610 returns True if given repo name is a valid filesystem repository
610 returns True if given repo name is a valid filesystem repository
611
611
612 :param cls:
612 :param cls:
613 :param repo_name:
613 :param repo_name:
614 """
614 """
615 from rhodecode.lib.utils import is_valid_repo
615 from rhodecode.lib.utils import is_valid_repo
616
616
617 return is_valid_repo(repo_name, cls.base_path())
617 return is_valid_repo(repo_name, cls.base_path())
618
618
619 #==========================================================================
619 #==========================================================================
620 # SCM PROPERTIES
620 # SCM PROPERTIES
621 #==========================================================================
621 #==========================================================================
622
622
623 def get_commit(self, rev):
623 def get_commit(self, rev):
624 return get_commit_safe(self.scm_instance, rev)
624 return get_commit_safe(self.scm_instance, rev)
625
625
626 @property
626 @property
627 def tip(self):
627 def tip(self):
628 return self.get_commit('tip')
628 return self.get_commit('tip')
629
629
630 @property
630 @property
631 def author(self):
631 def author(self):
632 return self.tip.author
632 return self.tip.author
633
633
634 @property
634 @property
635 def last_change(self):
635 def last_change(self):
636 return self.scm_instance.last_change
636 return self.scm_instance.last_change
637
637
638 def comments(self, revisions=None):
638 def comments(self, revisions=None):
639 """
639 """
640 Returns comments for this repository grouped by revisions
640 Returns comments for this repository grouped by revisions
641
641
642 :param revisions: filter query by revisions only
642 :param revisions: filter query by revisions only
643 """
643 """
644 cmts = ChangesetComment.query()\
644 cmts = ChangesetComment.query()\
645 .filter(ChangesetComment.repo == self)
645 .filter(ChangesetComment.repo == self)
646 if revisions:
646 if revisions:
647 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
647 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
648 grouped = defaultdict(list)
648 grouped = defaultdict(list)
649 for cmt in cmts.all():
649 for cmt in cmts.all():
650 grouped[cmt.revision].append(cmt)
650 grouped[cmt.revision].append(cmt)
651 return grouped
651 return grouped
652
652
653 #==========================================================================
653 #==========================================================================
654 # SCM CACHE INSTANCE
654 # SCM CACHE INSTANCE
655 #==========================================================================
655 #==========================================================================
656
656
657 @property
657 @property
658 def invalidate(self):
658 def invalidate(self):
659 return CacheInvalidation.invalidate(self.repo_name)
659 return CacheInvalidation.invalidate(self.repo_name)
660
660
661 def set_invalidate(self):
661 def set_invalidate(self):
662 """
662 """
663 set a cache for invalidation for this instance
663 set a cache for invalidation for this instance
664 """
664 """
665 CacheInvalidation.set_invalidate(self.repo_name)
665 CacheInvalidation.set_invalidate(self.repo_name)
666
666
667 @LazyProperty
667 @LazyProperty
668 def scm_instance(self):
668 def scm_instance(self):
669 return self.__get_instance()
669 return self.__get_instance()
670
670
671 @property
671 @property
672 def scm_instance_cached(self):
672 def scm_instance_cached(self):
673 @cache_region('long_term')
673 return self.__get_instance()
674 def _c(repo_name):
675 return self.__get_instance()
676 rn = self.repo_name
677 log.debug('Getting cached instance of repo')
678 inv = self.invalidate
679 if inv is not None:
680 region_invalidate(_c, None, rn)
681 # update our cache
682 CacheInvalidation.set_valid(inv.cache_key)
683 return _c(rn)
684
674
685 def __get_instance(self):
675 def __get_instance(self):
686 repo_full_path = self.repo_full_path
676 repo_full_path = self.repo_full_path
687 try:
677 try:
688 alias = get_scm(repo_full_path)[0]
678 alias = get_scm(repo_full_path)[0]
689 log.debug('Creating instance of %s repository' % alias)
679 log.debug('Creating instance of %s repository' % alias)
690 backend = get_backend(alias)
680 backend = get_backend(alias)
691 except VCSError:
681 except VCSError:
692 log.error(traceback.format_exc())
682 log.error(traceback.format_exc())
693 log.error('Perhaps this repository is in db and not in '
683 log.error('Perhaps this repository is in db and not in '
694 'filesystem run rescan repositories with '
684 'filesystem run rescan repositories with '
695 '"destroy old data " option from admin panel')
685 '"destroy old data " option from admin panel')
696 return
686 return
697
687
698 if alias == 'hg':
688 if alias == 'hg':
699
689
700 repo = backend(safe_str(repo_full_path), create=False,
690 repo = backend(safe_str(repo_full_path), create=False,
701 config=self._config)
691 config=self._config)
702 else:
692 else:
703 repo = backend(repo_full_path, create=False)
693 repo = backend(repo_full_path, create=False)
704
694
705 return repo
695 return repo
706
696
707
697
708 class RepoGroup(Base, BaseModel):
698 class RepoGroup(Base, BaseModel):
709 __tablename__ = 'groups'
699 __tablename__ = 'groups'
710 __table_args__ = (
700 __table_args__ = (
711 UniqueConstraint('group_name', 'group_parent_id'),
701 UniqueConstraint('group_name', 'group_parent_id'),
712 CheckConstraint('group_id != group_parent_id'),
702 CheckConstraint('group_id != group_parent_id'),
713 {'extend_existing': True, 'mysql_engine':'InnoDB',
703 {'extend_existing': True, 'mysql_engine':'InnoDB',
714 'mysql_charset': 'utf8'},
704 'mysql_charset': 'utf8'},
715 )
705 )
716 __mapper_args__ = {'order_by': 'group_name'}
706 __mapper_args__ = {'order_by': 'group_name'}
717
707
718 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
708 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
719 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
709 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
720 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
710 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
721 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
711 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
722
712
723 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
713 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
724 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
714 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
725
715
726 parent_group = relationship('RepoGroup', remote_side=group_id)
716 parent_group = relationship('RepoGroup', remote_side=group_id)
727
717
728 def __init__(self, group_name='', parent_group=None):
718 def __init__(self, group_name='', parent_group=None):
729 self.group_name = group_name
719 self.group_name = group_name
730 self.parent_group = parent_group
720 self.parent_group = parent_group
731
721
732 def __unicode__(self):
722 def __unicode__(self):
733 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
723 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
734 self.group_name)
724 self.group_name)
735
725
736 @classmethod
726 @classmethod
737 def url_sep(cls):
727 def url_sep(cls):
738 return '/'
728 return '/'
739
729
740 @classmethod
730 @classmethod
741 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
731 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
742 if case_insensitive:
732 if case_insensitive:
743 gr = cls.query()\
733 gr = cls.query()\
744 .filter(cls.group_name.ilike(group_name))
734 .filter(cls.group_name.ilike(group_name))
745 else:
735 else:
746 gr = cls.query()\
736 gr = cls.query()\
747 .filter(cls.group_name == group_name)
737 .filter(cls.group_name == group_name)
748 if cache:
738 if cache:
749 gr = gr.options(FromCache(
739 gr = gr.options(FromCache(
750 "sql_cache_short",
740 "sql_cache_short",
751 "get_group_%s" % _hash_key(group_name)
741 "get_group_%s" % _hash_key(group_name)
752 )
742 )
753 )
743 )
754 return gr.scalar()
744 return gr.scalar()
755
745
756 @property
746 @property
757 def parents(self):
747 def parents(self):
758 parents_recursion_limit = 5
748 parents_recursion_limit = 5
759 groups = []
749 groups = []
760 if self.parent_group is None:
750 if self.parent_group is None:
761 return groups
751 return groups
762 cur_gr = self.parent_group
752 cur_gr = self.parent_group
763 groups.insert(0, cur_gr)
753 groups.insert(0, cur_gr)
764 cnt = 0
754 cnt = 0
765 while 1:
755 while 1:
766 cnt += 1
756 cnt += 1
767 gr = getattr(cur_gr, 'parent_group', None)
757 gr = getattr(cur_gr, 'parent_group', None)
768 cur_gr = cur_gr.parent_group
758 cur_gr = cur_gr.parent_group
769 if gr is None:
759 if gr is None:
770 break
760 break
771 if cnt == parents_recursion_limit:
761 if cnt == parents_recursion_limit:
772 # this will prevent accidental infinit loops
762 # this will prevent accidental infinit loops
773 log.error('group nested more than %s' %
763 log.error('group nested more than %s' %
774 parents_recursion_limit)
764 parents_recursion_limit)
775 break
765 break
776
766
777 groups.insert(0, gr)
767 groups.insert(0, gr)
778 return groups
768 return groups
779
769
780 @property
770 @property
781 def children(self):
771 def children(self):
782 return RepoGroup.query().filter(RepoGroup.parent_group == self)
772 return RepoGroup.query().filter(RepoGroup.parent_group == self)
783
773
784 @property
774 @property
785 def name(self):
775 def name(self):
786 return self.group_name.split(RepoGroup.url_sep())[-1]
776 return self.group_name.split(RepoGroup.url_sep())[-1]
787
777
788 @property
778 @property
789 def full_path(self):
779 def full_path(self):
790 return self.group_name
780 return self.group_name
791
781
792 @property
782 @property
793 def full_path_splitted(self):
783 def full_path_splitted(self):
794 return self.group_name.split(RepoGroup.url_sep())
784 return self.group_name.split(RepoGroup.url_sep())
795
785
796 @property
786 @property
797 def repositories(self):
787 def repositories(self):
798 return Repository.query()\
788 return Repository.query()\
799 .filter(Repository.group == self)\
789 .filter(Repository.group == self)\
800 .order_by(Repository.repo_name)
790 .order_by(Repository.repo_name)
801
791
802 @property
792 @property
803 def repositories_recursive_count(self):
793 def repositories_recursive_count(self):
804 cnt = self.repositories.count()
794 cnt = self.repositories.count()
805
795
806 def children_count(group):
796 def children_count(group):
807 cnt = 0
797 cnt = 0
808 for child in group.children:
798 for child in group.children:
809 cnt += child.repositories.count()
799 cnt += child.repositories.count()
810 cnt += children_count(child)
800 cnt += children_count(child)
811 return cnt
801 return cnt
812
802
813 return cnt + children_count(self)
803 return cnt + children_count(self)
814
804
815 def get_new_name(self, group_name):
805 def get_new_name(self, group_name):
816 """
806 """
817 returns new full group name based on parent and new name
807 returns new full group name based on parent and new name
818
808
819 :param group_name:
809 :param group_name:
820 """
810 """
821 path_prefix = (self.parent_group.full_path_splitted if
811 path_prefix = (self.parent_group.full_path_splitted if
822 self.parent_group else [])
812 self.parent_group else [])
823 return RepoGroup.url_sep().join(path_prefix + [group_name])
813 return RepoGroup.url_sep().join(path_prefix + [group_name])
824
814
825
815
826 class Permission(Base, BaseModel):
816 class Permission(Base, BaseModel):
827 __tablename__ = 'permissions'
817 __tablename__ = 'permissions'
828 __table_args__ = (
818 __table_args__ = (
829 {'extend_existing': True, 'mysql_engine':'InnoDB',
819 {'extend_existing': True, 'mysql_engine':'InnoDB',
830 'mysql_charset': 'utf8'},
820 'mysql_charset': 'utf8'},
831 )
821 )
832 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
822 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
833 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
823 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
834 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
824 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
835
825
836 def __unicode__(self):
826 def __unicode__(self):
837 return u"<%s('%s:%s')>" % (
827 return u"<%s('%s:%s')>" % (
838 self.__class__.__name__, self.permission_id, self.permission_name
828 self.__class__.__name__, self.permission_id, self.permission_name
839 )
829 )
840
830
841 @classmethod
831 @classmethod
842 def get_by_key(cls, key):
832 def get_by_key(cls, key):
843 return cls.query().filter(cls.permission_name == key).scalar()
833 return cls.query().filter(cls.permission_name == key).scalar()
844
834
845 @classmethod
835 @classmethod
846 def get_default_repo_perms(cls, default_user_id):
836 def get_default_repo_perms(cls, default_user_id):
847 q = Session.query(UserRepoToPerm, Repository, cls)\
837 q = Session.query(UserRepoToPerm, Repository, cls)\
848 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
838 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
849 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
839 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
850 .filter(UserRepoToPerm.user_id == default_user_id)
840 .filter(UserRepoToPerm.user_id == default_user_id)
851
841
852 return q.all()
842 return q.all()
853
843
854 @classmethod
844 @classmethod
855 def get_default_group_perms(cls, default_user_id):
845 def get_default_group_perms(cls, default_user_id):
856 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
846 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
857 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
847 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
858 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
848 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
859 .filter(UserRepoGroupToPerm.user_id == default_user_id)
849 .filter(UserRepoGroupToPerm.user_id == default_user_id)
860
850
861 return q.all()
851 return q.all()
862
852
863
853
864 class UserRepoToPerm(Base, BaseModel):
854 class UserRepoToPerm(Base, BaseModel):
865 __tablename__ = 'repo_to_perm'
855 __tablename__ = 'repo_to_perm'
866 __table_args__ = (
856 __table_args__ = (
867 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
857 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
868 {'extend_existing': True, 'mysql_engine':'InnoDB',
858 {'extend_existing': True, 'mysql_engine':'InnoDB',
869 'mysql_charset': 'utf8'}
859 'mysql_charset': 'utf8'}
870 )
860 )
871 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
861 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
872 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
862 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
863 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
874 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
864 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
875
865
876 user = relationship('User')
866 user = relationship('User')
877 repository = relationship('Repository')
867 repository = relationship('Repository')
878 permission = relationship('Permission')
868 permission = relationship('Permission')
879
869
880 @classmethod
870 @classmethod
881 def create(cls, user, repository, permission):
871 def create(cls, user, repository, permission):
882 n = cls()
872 n = cls()
883 n.user = user
873 n.user = user
884 n.repository = repository
874 n.repository = repository
885 n.permission = permission
875 n.permission = permission
886 Session.add(n)
876 Session.add(n)
887 return n
877 return n
888
878
889 def __unicode__(self):
879 def __unicode__(self):
890 return u'<user:%s => %s >' % (self.user, self.repository)
880 return u'<user:%s => %s >' % (self.user, self.repository)
891
881
892
882
893 class UserToPerm(Base, BaseModel):
883 class UserToPerm(Base, BaseModel):
894 __tablename__ = 'user_to_perm'
884 __tablename__ = 'user_to_perm'
895 __table_args__ = (
885 __table_args__ = (
896 UniqueConstraint('user_id', 'permission_id'),
886 UniqueConstraint('user_id', 'permission_id'),
897 {'extend_existing': True, 'mysql_engine':'InnoDB',
887 {'extend_existing': True, 'mysql_engine':'InnoDB',
898 'mysql_charset': 'utf8'}
888 'mysql_charset': 'utf8'}
899 )
889 )
900 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
890 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
901 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
891 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
902 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
892 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
903
893
904 user = relationship('User')
894 user = relationship('User')
905 permission = relationship('Permission', lazy='joined')
895 permission = relationship('Permission', lazy='joined')
906
896
907
897
908 class UserGroupRepoToPerm(Base, BaseModel):
898 class UserGroupRepoToPerm(Base, BaseModel):
909 __tablename__ = 'users_group_repo_to_perm'
899 __tablename__ = 'users_group_repo_to_perm'
910 __table_args__ = (
900 __table_args__ = (
911 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
901 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
912 {'extend_existing': True, 'mysql_engine':'InnoDB',
902 {'extend_existing': True, 'mysql_engine':'InnoDB',
913 'mysql_charset': 'utf8'}
903 'mysql_charset': 'utf8'}
914 )
904 )
915 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
905 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
916 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
906 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
917 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
907 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
918 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
908 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
919
909
920 users_group = relationship('UserGroup')
910 users_group = relationship('UserGroup')
921 permission = relationship('Permission')
911 permission = relationship('Permission')
922 repository = relationship('Repository')
912 repository = relationship('Repository')
923
913
924 @classmethod
914 @classmethod
925 def create(cls, users_group, repository, permission):
915 def create(cls, users_group, repository, permission):
926 n = cls()
916 n = cls()
927 n.users_group = users_group
917 n.users_group = users_group
928 n.repository = repository
918 n.repository = repository
929 n.permission = permission
919 n.permission = permission
930 Session.add(n)
920 Session.add(n)
931 return n
921 return n
932
922
933 def __unicode__(self):
923 def __unicode__(self):
934 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
924 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
935
925
936
926
937 class UserGroupToPerm(Base, BaseModel):
927 class UserGroupToPerm(Base, BaseModel):
938 __tablename__ = 'users_group_to_perm'
928 __tablename__ = 'users_group_to_perm'
939 __table_args__ = (
929 __table_args__ = (
940 UniqueConstraint('users_group_id', 'permission_id',),
930 UniqueConstraint('users_group_id', 'permission_id',),
941 {'extend_existing': True, 'mysql_engine':'InnoDB',
931 {'extend_existing': True, 'mysql_engine':'InnoDB',
942 'mysql_charset': 'utf8'}
932 'mysql_charset': 'utf8'}
943 )
933 )
944 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
934 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
945 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
935 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
946 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
936 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
947
937
948 users_group = relationship('UserGroup')
938 users_group = relationship('UserGroup')
949 permission = relationship('Permission')
939 permission = relationship('Permission')
950
940
951
941
952 class UserRepoGroupToPerm(Base, BaseModel):
942 class UserRepoGroupToPerm(Base, BaseModel):
953 __tablename__ = 'user_repo_group_to_perm'
943 __tablename__ = 'user_repo_group_to_perm'
954 __table_args__ = (
944 __table_args__ = (
955 UniqueConstraint('user_id', 'group_id', 'permission_id'),
945 UniqueConstraint('user_id', 'group_id', 'permission_id'),
956 {'extend_existing': True, 'mysql_engine':'InnoDB',
946 {'extend_existing': True, 'mysql_engine':'InnoDB',
957 'mysql_charset': 'utf8'}
947 'mysql_charset': 'utf8'}
958 )
948 )
959
949
960 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
950 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
961 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
951 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
962 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
952 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
963 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
953 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
964
954
965 user = relationship('User')
955 user = relationship('User')
966 group = relationship('RepoGroup')
956 group = relationship('RepoGroup')
967 permission = relationship('Permission')
957 permission = relationship('Permission')
968
958
969
959
970 class UserGroupRepoGroupToPerm(Base, BaseModel):
960 class UserGroupRepoGroupToPerm(Base, BaseModel):
971 __tablename__ = 'users_group_repo_group_to_perm'
961 __tablename__ = 'users_group_repo_group_to_perm'
972 __table_args__ = (
962 __table_args__ = (
973 UniqueConstraint('users_group_id', 'group_id'),
963 UniqueConstraint('users_group_id', 'group_id'),
974 {'extend_existing': True, 'mysql_engine':'InnoDB',
964 {'extend_existing': True, 'mysql_engine':'InnoDB',
975 'mysql_charset': 'utf8'}
965 'mysql_charset': 'utf8'}
976 )
966 )
977
967
978 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)
968 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)
979 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
969 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
980 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
970 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
981 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
971 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
982
972
983 users_group = relationship('UserGroup')
973 users_group = relationship('UserGroup')
984 permission = relationship('Permission')
974 permission = relationship('Permission')
985 group = relationship('RepoGroup')
975 group = relationship('RepoGroup')
986
976
987
977
988 class Statistics(Base, BaseModel):
978 class Statistics(Base, BaseModel):
989 __tablename__ = 'statistics'
979 __tablename__ = 'statistics'
990 __table_args__ = (
980 __table_args__ = (
991 UniqueConstraint('repository_id'),
981 UniqueConstraint('repository_id'),
992 {'extend_existing': True, 'mysql_engine':'InnoDB',
982 {'extend_existing': True, 'mysql_engine':'InnoDB',
993 'mysql_charset': 'utf8'}
983 'mysql_charset': 'utf8'}
994 )
984 )
995 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
985 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
986 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
997 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
987 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
998 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
988 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
999 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
989 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1000 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
990 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1001
991
1002 repository = relationship('Repository', single_parent=True)
992 repository = relationship('Repository', single_parent=True)
1003
993
1004
994
1005 class UserFollowing(Base, BaseModel):
995 class UserFollowing(Base, BaseModel):
1006 __tablename__ = 'user_followings'
996 __tablename__ = 'user_followings'
1007 __table_args__ = (
997 __table_args__ = (
1008 UniqueConstraint('user_id', 'follows_repository_id'),
998 UniqueConstraint('user_id', 'follows_repository_id'),
1009 UniqueConstraint('user_id', 'follows_user_id'),
999 UniqueConstraint('user_id', 'follows_user_id'),
1010 {'extend_existing': True, 'mysql_engine':'InnoDB',
1000 {'extend_existing': True, 'mysql_engine':'InnoDB',
1011 'mysql_charset': 'utf8'}
1001 'mysql_charset': 'utf8'}
1012 )
1002 )
1013
1003
1014 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1004 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1015 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1005 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1016 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1006 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1017 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1007 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1018 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1008 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1019
1009
1020 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1010 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1021
1011
1022 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1012 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1023 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1013 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1024
1014
1025 @classmethod
1015 @classmethod
1026 def get_repo_followers(cls, repo_id):
1016 def get_repo_followers(cls, repo_id):
1027 return cls.query().filter(cls.follows_repo_id == repo_id)
1017 return cls.query().filter(cls.follows_repo_id == repo_id)
1028
1018
1029
1019
1030 class CacheInvalidation(Base, BaseModel):
1020 class CacheInvalidation(Base, BaseModel):
1031 __tablename__ = 'cache_invalidation'
1021 __tablename__ = 'cache_invalidation'
1032 __table_args__ = (
1022 __table_args__ = (
1033 UniqueConstraint('cache_key'),
1023 UniqueConstraint('cache_key'),
1034 {'extend_existing': True, 'mysql_engine':'InnoDB',
1024 {'extend_existing': True, 'mysql_engine':'InnoDB',
1035 'mysql_charset': 'utf8'},
1025 'mysql_charset': 'utf8'},
1036 )
1026 )
1037 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1027 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1038 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
1028 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
1039 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
1029 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
1040 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1030 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1041
1031
1042 def __init__(self, cache_key, cache_args=''):
1032 def __init__(self, cache_key, cache_args=''):
1043 self.cache_key = cache_key
1033 self.cache_key = cache_key
1044 self.cache_args = cache_args
1034 self.cache_args = cache_args
1045 self.cache_active = False
1035 self.cache_active = False
1046
1036
1047 def __unicode__(self):
1037 def __unicode__(self):
1048 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1038 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1049 self.cache_id, self.cache_key)
1039 self.cache_id, self.cache_key)
1050
1040
1051 @classmethod
1041 @classmethod
1052 def _get_key(cls, key):
1042 def _get_key(cls, key):
1053 """
1043 """
1054 Wrapper for generating a key, together with a prefix
1044 Wrapper for generating a key, together with a prefix
1055
1045
1056 :param key:
1046 :param key:
1057 """
1047 """
1058 import rhodecode
1048 import rhodecode
1059 prefix = ''
1049 prefix = ''
1060 iid = rhodecode.CONFIG.get('instance_id')
1050 iid = rhodecode.CONFIG.get('instance_id')
1061 if iid:
1051 if iid:
1062 prefix = iid
1052 prefix = iid
1063 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1053 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1064
1054
1065 @classmethod
1055 @classmethod
1066 def get_by_key(cls, key):
1056 def get_by_key(cls, key):
1067 return cls.query().filter(cls.cache_key == key).scalar()
1057 return cls.query().filter(cls.cache_key == key).scalar()
1068
1058
1069 @classmethod
1059 @classmethod
1070 def _get_or_create_key(cls, key, prefix, org_key):
1060 def _get_or_create_key(cls, key, prefix, org_key):
1071 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1061 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1072 if not inv_obj:
1062 if not inv_obj:
1073 try:
1063 try:
1074 inv_obj = CacheInvalidation(key, org_key)
1064 inv_obj = CacheInvalidation(key, org_key)
1075 Session.add(inv_obj)
1065 Session.add(inv_obj)
1076 Session.commit()
1066 Session.commit()
1077 except Exception:
1067 except Exception:
1078 log.error(traceback.format_exc())
1068 log.error(traceback.format_exc())
1079 Session.rollback()
1069 Session.rollback()
1080 return inv_obj
1070 return inv_obj
1081
1071
1082 @classmethod
1072 @classmethod
1083 def invalidate(cls, key):
1073 def invalidate(cls, key):
1084 """
1074 """
1085 Returns Invalidation object if this given key should be invalidated
1075 Returns Invalidation object if this given key should be invalidated
1086 None otherwise. `cache_active = False` means that this cache
1076 None otherwise. `cache_active = False` means that this cache
1087 state is not valid and needs to be invalidated
1077 state is not valid and needs to be invalidated
1088
1078
1089 :param key:
1079 :param key:
1090 """
1080 """
1091
1081
1092 key, _prefix, _org_key = cls._get_key(key)
1082 key, _prefix, _org_key = cls._get_key(key)
1093 inv = cls._get_or_create_key(key, _prefix, _org_key)
1083 inv = cls._get_or_create_key(key, _prefix, _org_key)
1094
1084
1095 if inv and inv.cache_active is False:
1085 if inv and inv.cache_active is False:
1096 return inv
1086 return inv
1097
1087
1098 @classmethod
1088 @classmethod
1099 def set_invalidate(cls, key):
1089 def set_invalidate(cls, key):
1100 """
1090 """
1101 Mark this Cache key for invalidation
1091 Mark this Cache key for invalidation
1102
1092
1103 :param key:
1093 :param key:
1104 """
1094 """
1105
1095
1106 key, _prefix, _org_key = cls._get_key(key)
1096 key, _prefix, _org_key = cls._get_key(key)
1107 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1097 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1108 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1098 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1109 _org_key))
1099 _org_key))
1110 try:
1100 try:
1111 for inv_obj in inv_objs:
1101 for inv_obj in inv_objs:
1112 if inv_obj:
1102 if inv_obj:
1113 inv_obj.cache_active = False
1103 inv_obj.cache_active = False
1114
1104
1115 Session.add(inv_obj)
1105 Session.add(inv_obj)
1116 Session.commit()
1106 Session.commit()
1117 except Exception:
1107 except Exception:
1118 log.error(traceback.format_exc())
1108 log.error(traceback.format_exc())
1119 Session.rollback()
1109 Session.rollback()
1120
1110
1121 @classmethod
1111 @classmethod
1122 def set_valid(cls, key):
1112 def set_valid(cls, key):
1123 """
1113 """
1124 Mark this cache key as active and currently cached
1114 Mark this cache key as active and currently cached
1125
1115
1126 :param key:
1116 :param key:
1127 """
1117 """
1128 inv_obj = cls.get_by_key(key)
1118 inv_obj = cls.get_by_key(key)
1129 inv_obj.cache_active = True
1119 inv_obj.cache_active = True
1130 Session.add(inv_obj)
1120 Session.add(inv_obj)
1131 Session.commit()
1121 Session.commit()
1132
1122
1133
1123
1134 class ChangesetComment(Base, BaseModel):
1124 class ChangesetComment(Base, BaseModel):
1135 __tablename__ = 'changeset_comments'
1125 __tablename__ = 'changeset_comments'
1136 __table_args__ = (
1126 __table_args__ = (
1137 {'extend_existing': True, 'mysql_engine':'InnoDB',
1127 {'extend_existing': True, 'mysql_engine':'InnoDB',
1138 'mysql_charset': 'utf8'},
1128 'mysql_charset': 'utf8'},
1139 )
1129 )
1140 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1130 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1141 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1131 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1142 revision = Column('revision', String(40), nullable=False)
1132 revision = Column('revision', String(40), nullable=False)
1143 line_no = Column('line_no', Unicode(10), nullable=True)
1133 line_no = Column('line_no', Unicode(10), nullable=True)
1144 f_path = Column('f_path', Unicode(1000), nullable=True)
1134 f_path = Column('f_path', Unicode(1000), nullable=True)
1145 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1135 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1146 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
1136 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
1147 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1137 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1148
1138
1149 author = relationship('User', lazy='joined')
1139 author = relationship('User', lazy='joined')
1150 repo = relationship('Repository')
1140 repo = relationship('Repository')
1151
1141
1152 @classmethod
1142 @classmethod
1153 def get_users(cls, revision):
1143 def get_users(cls, revision):
1154 """
1144 """
1155 Returns user associated with this changesetComment. ie those
1145 Returns user associated with this changesetComment. ie those
1156 who actually commented
1146 who actually commented
1157
1147
1158 :param cls:
1148 :param cls:
1159 :param revision:
1149 :param revision:
1160 """
1150 """
1161 return Session.query(User)\
1151 return Session.query(User)\
1162 .filter(cls.revision == revision)\
1152 .filter(cls.revision == revision)\
1163 .join(ChangesetComment.author).all()
1153 .join(ChangesetComment.author).all()
1164
1154
1165
1155
1166 class Notification(Base, BaseModel):
1156 class Notification(Base, BaseModel):
1167 __tablename__ = 'notifications'
1157 __tablename__ = 'notifications'
1168 __table_args__ = (
1158 __table_args__ = (
1169 {'extend_existing': True, 'mysql_engine':'InnoDB',
1159 {'extend_existing': True, 'mysql_engine':'InnoDB',
1170 'mysql_charset': 'utf8'},
1160 'mysql_charset': 'utf8'},
1171 )
1161 )
1172
1162
1173 TYPE_CHANGESET_COMMENT = u'cs_comment'
1163 TYPE_CHANGESET_COMMENT = u'cs_comment'
1174 TYPE_MESSAGE = u'message'
1164 TYPE_MESSAGE = u'message'
1175 TYPE_MENTION = u'mention'
1165 TYPE_MENTION = u'mention'
1176 TYPE_REGISTRATION = u'registration'
1166 TYPE_REGISTRATION = u'registration'
1177
1167
1178 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1168 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1179 subject = Column('subject', Unicode(512), nullable=True)
1169 subject = Column('subject', Unicode(512), nullable=True)
1180 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1170 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1181 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1171 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1182 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1172 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1183 type_ = Column('type', Unicode(256))
1173 type_ = Column('type', Unicode(256))
1184
1174
1185 created_by_user = relationship('User')
1175 created_by_user = relationship('User')
1186 notifications_to_users = relationship('UserNotification', lazy='joined',
1176 notifications_to_users = relationship('UserNotification', lazy='joined',
1187 cascade="all, delete, delete-orphan")
1177 cascade="all, delete, delete-orphan")
1188
1178
1189 @property
1179 @property
1190 def recipients(self):
1180 def recipients(self):
1191 return [x.user for x in UserNotification.query()\
1181 return [x.user for x in UserNotification.query()\
1192 .filter(UserNotification.notification == self).all()]
1182 .filter(UserNotification.notification == self).all()]
1193
1183
1194 @classmethod
1184 @classmethod
1195 def create(cls, created_by, subject, body, recipients, type_=None):
1185 def create(cls, created_by, subject, body, recipients, type_=None):
1196 if type_ is None:
1186 if type_ is None:
1197 type_ = Notification.TYPE_MESSAGE
1187 type_ = Notification.TYPE_MESSAGE
1198
1188
1199 notification = cls()
1189 notification = cls()
1200 notification.created_by_user = created_by
1190 notification.created_by_user = created_by
1201 notification.subject = subject
1191 notification.subject = subject
1202 notification.body = body
1192 notification.body = body
1203 notification.type_ = type_
1193 notification.type_ = type_
1204 notification.created_on = datetime.datetime.now()
1194 notification.created_on = datetime.datetime.now()
1205
1195
1206 for u in recipients:
1196 for u in recipients:
1207 assoc = UserNotification()
1197 assoc = UserNotification()
1208 assoc.notification = notification
1198 assoc.notification = notification
1209 u.notifications.append(assoc)
1199 u.notifications.append(assoc)
1210 Session.add(notification)
1200 Session.add(notification)
1211 return notification
1201 return notification
1212
1202
1213 @property
1203 @property
1214 def description(self):
1204 def description(self):
1215 from rhodecode.model.notification import NotificationModel
1205 from rhodecode.model.notification import NotificationModel
1216 return NotificationModel().make_description(self)
1206 return NotificationModel().make_description(self)
1217
1207
1218
1208
1219 class UserNotification(Base, BaseModel):
1209 class UserNotification(Base, BaseModel):
1220 __tablename__ = 'user_to_notification'
1210 __tablename__ = 'user_to_notification'
1221 __table_args__ = (
1211 __table_args__ = (
1222 UniqueConstraint('user_id', 'notification_id'),
1212 UniqueConstraint('user_id', 'notification_id'),
1223 {'extend_existing': True, 'mysql_engine':'InnoDB',
1213 {'extend_existing': True, 'mysql_engine':'InnoDB',
1224 'mysql_charset': 'utf8'}
1214 'mysql_charset': 'utf8'}
1225 )
1215 )
1226 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1216 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1227 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1217 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1228 read = Column('read', Boolean, default=False)
1218 read = Column('read', Boolean, default=False)
1229 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1219 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1230
1220
1231 user = relationship('User', lazy="joined")
1221 user = relationship('User', lazy="joined")
1232 notification = relationship('Notification', lazy="joined",
1222 notification = relationship('Notification', lazy="joined",
1233 order_by=lambda: Notification.created_on.desc(),)
1223 order_by=lambda: Notification.created_on.desc(),)
1234
1224
1235 def mark_as_read(self):
1225 def mark_as_read(self):
1236 self.read = True
1226 self.read = True
1237 Session.add(self)
1227 Session.add(self)
1238
1228
1239
1229
1240 class DbMigrateVersion(Base, BaseModel):
1230 class DbMigrateVersion(Base, BaseModel):
1241 __tablename__ = 'db_migrate_version'
1231 __tablename__ = 'db_migrate_version'
1242 __table_args__ = (
1232 __table_args__ = (
1243 {'extend_existing': True, 'mysql_engine':'InnoDB',
1233 {'extend_existing': True, 'mysql_engine':'InnoDB',
1244 'mysql_charset': 'utf8'},
1234 'mysql_charset': 'utf8'},
1245 )
1235 )
1246 repository_id = Column('repository_id', String(250), primary_key=True)
1236 repository_id = Column('repository_id', String(250), primary_key=True)
1247 repository_path = Column('repository_path', Text)
1237 repository_path = Column('repository_path', Text)
1248 version = Column('version', Integer)
1238 version = Column('version', Integer)
1249
1239
1250 ## this is migration from 1_4_0, but now it's here to overcome a problem of
1240 ## this is migration from 1_4_0, but now it's here to overcome a problem of
1251 ## attaching a FK to this from 1_3_0 !
1241 ## attaching a FK to this from 1_3_0 !
1252
1242
1253
1243
1254 class PullRequest(Base, BaseModel):
1244 class PullRequest(Base, BaseModel):
1255 __tablename__ = 'pull_requests'
1245 __tablename__ = 'pull_requests'
1256 __table_args__ = (
1246 __table_args__ = (
1257 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1258 'mysql_charset': 'utf8'},
1248 'mysql_charset': 'utf8'},
1259 )
1249 )
1260
1250
1261 STATUS_NEW = u'new'
1251 STATUS_NEW = u'new'
1262 STATUS_OPEN = u'open'
1252 STATUS_OPEN = u'open'
1263 STATUS_CLOSED = u'closed'
1253 STATUS_CLOSED = u'closed'
1264
1254
1265 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1255 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1266 title = Column('title', Unicode(256), nullable=True)
1256 title = Column('title', Unicode(256), nullable=True)
1267 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1257 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1268 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1258 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1269 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1259 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1270 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1260 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1271 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1261 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1272 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql')) # 500 revisions max
1262 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql')) # 500 revisions max
1273 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1263 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1274 org_ref = Column('org_ref', Unicode(256), nullable=False)
1264 org_ref = Column('org_ref', Unicode(256), nullable=False)
1275 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1265 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1276 other_ref = Column('other_ref', Unicode(256), nullable=False)
1266 other_ref = Column('other_ref', Unicode(256), nullable=False)
@@ -1,4366 +1,4334 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
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 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # noqa
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # noqa
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from beaker.cache import cache_region
50 from beaker.cache import cache_region
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52
52
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54
54
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re, StrictAttributeDict, cleaned_uri)
61 glob2re, StrictAttributeDict, cleaned_uri)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 JsonRaw
63 JsonRaw
64 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.encrypt import AESCipher
66 from rhodecode.lib.encrypt import AESCipher
67
67
68 from rhodecode.model.meta import Base, Session
68 from rhodecode.model.meta import Base, Session
69
69
70 URL_SEP = '/'
70 URL_SEP = '/'
71 log = logging.getLogger(__name__)
71 log = logging.getLogger(__name__)
72
72
73 # =============================================================================
73 # =============================================================================
74 # BASE CLASSES
74 # BASE CLASSES
75 # =============================================================================
75 # =============================================================================
76
76
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # beaker.session.secret if first is not set.
78 # beaker.session.secret if first is not set.
79 # and initialized at environment.py
79 # and initialized at environment.py
80 ENCRYPTION_KEY = None
80 ENCRYPTION_KEY = None
81
81
82 # used to sort permissions by types, '#' used here is not allowed to be in
82 # used to sort permissions by types, '#' used here is not allowed to be in
83 # usernames, and it's very early in sorted string.printable table.
83 # usernames, and it's very early in sorted string.printable table.
84 PERMISSION_TYPE_SORT = {
84 PERMISSION_TYPE_SORT = {
85 'admin': '####',
85 'admin': '####',
86 'write': '###',
86 'write': '###',
87 'read': '##',
87 'read': '##',
88 'none': '#',
88 'none': '#',
89 }
89 }
90
90
91
91
92 def display_user_sort(obj):
92 def display_user_sort(obj):
93 """
93 """
94 Sort function used to sort permissions in .permissions() function of
94 Sort function used to sort permissions in .permissions() function of
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 of all other resources
96 of all other resources
97 """
97 """
98
98
99 if obj.username == User.DEFAULT_USER:
99 if obj.username == User.DEFAULT_USER:
100 return '#####'
100 return '#####'
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 return prefix + obj.username
102 return prefix + obj.username
103
103
104
104
105 def display_user_group_sort(obj):
105 def display_user_group_sort(obj):
106 """
106 """
107 Sort function used to sort permissions in .permissions() function of
107 Sort function used to sort permissions in .permissions() function of
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 of all other resources
109 of all other resources
110 """
110 """
111
111
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 return prefix + obj.users_group_name
113 return prefix + obj.users_group_name
114
114
115
115
116 def _hash_key(k):
116 def _hash_key(k):
117 return md5_safe(k)
117 return md5_safe(k)
118
118
119
119
120 def in_filter_generator(qry, items, limit=500):
120 def in_filter_generator(qry, items, limit=500):
121 """
121 """
122 Splits IN() into multiple with OR
122 Splits IN() into multiple with OR
123 e.g.::
123 e.g.::
124 cnt = Repository.query().filter(
124 cnt = Repository.query().filter(
125 or_(
125 or_(
126 *in_filter_generator(Repository.repo_id, range(100000))
126 *in_filter_generator(Repository.repo_id, range(100000))
127 )).count()
127 )).count()
128 """
128 """
129 if not items:
129 if not items:
130 # empty list will cause empty query which might cause security issues
130 # empty list will cause empty query which might cause security issues
131 # this can lead to hidden unpleasant results
131 # this can lead to hidden unpleasant results
132 items = [-1]
132 items = [-1]
133
133
134 parts = []
134 parts = []
135 for chunk in xrange(0, len(items), limit):
135 for chunk in xrange(0, len(items), limit):
136 parts.append(
136 parts.append(
137 qry.in_(items[chunk: chunk + limit])
137 qry.in_(items[chunk: chunk + limit])
138 )
138 )
139
139
140 return parts
140 return parts
141
141
142
142
143 class EncryptedTextValue(TypeDecorator):
143 class EncryptedTextValue(TypeDecorator):
144 """
144 """
145 Special column for encrypted long text data, use like::
145 Special column for encrypted long text data, use like::
146
146
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
148
148
149 This column is intelligent so if value is in unencrypted form it return
149 This column is intelligent so if value is in unencrypted form it return
150 unencrypted form, but on save it always encrypts
150 unencrypted form, but on save it always encrypts
151 """
151 """
152 impl = Text
152 impl = Text
153
153
154 def process_bind_param(self, value, dialect):
154 def process_bind_param(self, value, dialect):
155 if not value:
155 if not value:
156 return value
156 return value
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
158 # protect against double encrypting if someone manually starts
158 # protect against double encrypting if someone manually starts
159 # doing
159 # doing
160 raise ValueError('value needs to be in unencrypted format, ie. '
160 raise ValueError('value needs to be in unencrypted format, ie. '
161 'not starting with enc$aes')
161 'not starting with enc$aes')
162 return 'enc$aes_hmac$%s' % AESCipher(
162 return 'enc$aes_hmac$%s' % AESCipher(
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
164
164
165 def process_result_value(self, value, dialect):
165 def process_result_value(self, value, dialect):
166 import rhodecode
166 import rhodecode
167
167
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 parts = value.split('$', 3)
171 parts = value.split('$', 3)
172 if not len(parts) == 3:
172 if not len(parts) == 3:
173 # probably not encrypted values
173 # probably not encrypted values
174 return value
174 return value
175 else:
175 else:
176 if parts[0] != 'enc':
176 if parts[0] != 'enc':
177 # parts ok but without our header ?
177 # parts ok but without our header ?
178 return value
178 return value
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
180 'rhodecode.encrypted_values.strict') or True)
180 'rhodecode.encrypted_values.strict') or True)
181 # at that stage we know it's our encryption
181 # at that stage we know it's our encryption
182 if parts[1] == 'aes':
182 if parts[1] == 'aes':
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
184 elif parts[1] == 'aes_hmac':
184 elif parts[1] == 'aes_hmac':
185 decrypted_data = AESCipher(
185 decrypted_data = AESCipher(
186 ENCRYPTION_KEY, hmac=True,
186 ENCRYPTION_KEY, hmac=True,
187 strict_verification=enc_strict_mode).decrypt(parts[2])
187 strict_verification=enc_strict_mode).decrypt(parts[2])
188 else:
188 else:
189 raise ValueError(
189 raise ValueError(
190 'Encryption type part is wrong, must be `aes` '
190 'Encryption type part is wrong, must be `aes` '
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
192 return decrypted_data
192 return decrypted_data
193
193
194
194
195 class BaseModel(object):
195 class BaseModel(object):
196 """
196 """
197 Base Model for all classes
197 Base Model for all classes
198 """
198 """
199
199
200 @classmethod
200 @classmethod
201 def _get_keys(cls):
201 def _get_keys(cls):
202 """return column names for this model """
202 """return column names for this model """
203 return class_mapper(cls).c.keys()
203 return class_mapper(cls).c.keys()
204
204
205 def get_dict(self):
205 def get_dict(self):
206 """
206 """
207 return dict with keys and values corresponding
207 return dict with keys and values corresponding
208 to this model data """
208 to this model data """
209
209
210 d = {}
210 d = {}
211 for k in self._get_keys():
211 for k in self._get_keys():
212 d[k] = getattr(self, k)
212 d[k] = getattr(self, k)
213
213
214 # also use __json__() if present to get additional fields
214 # also use __json__() if present to get additional fields
215 _json_attr = getattr(self, '__json__', None)
215 _json_attr = getattr(self, '__json__', None)
216 if _json_attr:
216 if _json_attr:
217 # update with attributes from __json__
217 # update with attributes from __json__
218 if callable(_json_attr):
218 if callable(_json_attr):
219 _json_attr = _json_attr()
219 _json_attr = _json_attr()
220 for k, val in _json_attr.iteritems():
220 for k, val in _json_attr.iteritems():
221 d[k] = val
221 d[k] = val
222 return d
222 return d
223
223
224 def get_appstruct(self):
224 def get_appstruct(self):
225 """return list with keys and values tuples corresponding
225 """return list with keys and values tuples corresponding
226 to this model data """
226 to this model data """
227
227
228 lst = []
228 lst = []
229 for k in self._get_keys():
229 for k in self._get_keys():
230 lst.append((k, getattr(self, k),))
230 lst.append((k, getattr(self, k),))
231 return lst
231 return lst
232
232
233 def populate_obj(self, populate_dict):
233 def populate_obj(self, populate_dict):
234 """populate model with data from given populate_dict"""
234 """populate model with data from given populate_dict"""
235
235
236 for k in self._get_keys():
236 for k in self._get_keys():
237 if k in populate_dict:
237 if k in populate_dict:
238 setattr(self, k, populate_dict[k])
238 setattr(self, k, populate_dict[k])
239
239
240 @classmethod
240 @classmethod
241 def query(cls):
241 def query(cls):
242 return Session().query(cls)
242 return Session().query(cls)
243
243
244 @classmethod
244 @classmethod
245 def get(cls, id_):
245 def get(cls, id_):
246 if id_:
246 if id_:
247 return cls.query().get(id_)
247 return cls.query().get(id_)
248
248
249 @classmethod
249 @classmethod
250 def get_or_404(cls, id_):
250 def get_or_404(cls, id_):
251 from pyramid.httpexceptions import HTTPNotFound
251 from pyramid.httpexceptions import HTTPNotFound
252
252
253 try:
253 try:
254 id_ = int(id_)
254 id_ = int(id_)
255 except (TypeError, ValueError):
255 except (TypeError, ValueError):
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257
257
258 res = cls.query().get(id_)
258 res = cls.query().get(id_)
259 if not res:
259 if not res:
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261 return res
261 return res
262
262
263 @classmethod
263 @classmethod
264 def getAll(cls):
264 def getAll(cls):
265 # deprecated and left for backward compatibility
265 # deprecated and left for backward compatibility
266 return cls.get_all()
266 return cls.get_all()
267
267
268 @classmethod
268 @classmethod
269 def get_all(cls):
269 def get_all(cls):
270 return cls.query().all()
270 return cls.query().all()
271
271
272 @classmethod
272 @classmethod
273 def delete(cls, id_):
273 def delete(cls, id_):
274 obj = cls.query().get(id_)
274 obj = cls.query().get(id_)
275 Session().delete(obj)
275 Session().delete(obj)
276
276
277 @classmethod
277 @classmethod
278 def identity_cache(cls, session, attr_name, value):
278 def identity_cache(cls, session, attr_name, value):
279 exist_in_session = []
279 exist_in_session = []
280 for (item_cls, pkey), instance in session.identity_map.items():
280 for (item_cls, pkey), instance in session.identity_map.items():
281 if cls == item_cls and getattr(instance, attr_name) == value:
281 if cls == item_cls and getattr(instance, attr_name) == value:
282 exist_in_session.append(instance)
282 exist_in_session.append(instance)
283 if exist_in_session:
283 if exist_in_session:
284 if len(exist_in_session) == 1:
284 if len(exist_in_session) == 1:
285 return exist_in_session[0]
285 return exist_in_session[0]
286 log.exception(
286 log.exception(
287 'multiple objects with attr %s and '
287 'multiple objects with attr %s and '
288 'value %s found with same name: %r',
288 'value %s found with same name: %r',
289 attr_name, value, exist_in_session)
289 attr_name, value, exist_in_session)
290
290
291 def __repr__(self):
291 def __repr__(self):
292 if hasattr(self, '__unicode__'):
292 if hasattr(self, '__unicode__'):
293 # python repr needs to return str
293 # python repr needs to return str
294 try:
294 try:
295 return safe_str(self.__unicode__())
295 return safe_str(self.__unicode__())
296 except UnicodeDecodeError:
296 except UnicodeDecodeError:
297 pass
297 pass
298 return '<DB:%s>' % (self.__class__.__name__)
298 return '<DB:%s>' % (self.__class__.__name__)
299
299
300
300
301 class RhodeCodeSetting(Base, BaseModel):
301 class RhodeCodeSetting(Base, BaseModel):
302 __tablename__ = 'rhodecode_settings'
302 __tablename__ = 'rhodecode_settings'
303 __table_args__ = (
303 __table_args__ = (
304 UniqueConstraint('app_settings_name'),
304 UniqueConstraint('app_settings_name'),
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
307 )
307 )
308
308
309 SETTINGS_TYPES = {
309 SETTINGS_TYPES = {
310 'str': safe_str,
310 'str': safe_str,
311 'int': safe_int,
311 'int': safe_int,
312 'unicode': safe_unicode,
312 'unicode': safe_unicode,
313 'bool': str2bool,
313 'bool': str2bool,
314 'list': functools.partial(aslist, sep=',')
314 'list': functools.partial(aslist, sep=',')
315 }
315 }
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
317 GLOBAL_CONF_KEY = 'app_settings'
317 GLOBAL_CONF_KEY = 'app_settings'
318
318
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
323
323
324 def __init__(self, key='', val='', type='unicode'):
324 def __init__(self, key='', val='', type='unicode'):
325 self.app_settings_name = key
325 self.app_settings_name = key
326 self.app_settings_type = type
326 self.app_settings_type = type
327 self.app_settings_value = val
327 self.app_settings_value = val
328
328
329 @validates('_app_settings_value')
329 @validates('_app_settings_value')
330 def validate_settings_value(self, key, val):
330 def validate_settings_value(self, key, val):
331 assert type(val) == unicode
331 assert type(val) == unicode
332 return val
332 return val
333
333
334 @hybrid_property
334 @hybrid_property
335 def app_settings_value(self):
335 def app_settings_value(self):
336 v = self._app_settings_value
336 v = self._app_settings_value
337 _type = self.app_settings_type
337 _type = self.app_settings_type
338 if _type:
338 if _type:
339 _type = self.app_settings_type.split('.')[0]
339 _type = self.app_settings_type.split('.')[0]
340 # decode the encrypted value
340 # decode the encrypted value
341 if 'encrypted' in self.app_settings_type:
341 if 'encrypted' in self.app_settings_type:
342 cipher = EncryptedTextValue()
342 cipher = EncryptedTextValue()
343 v = safe_unicode(cipher.process_result_value(v, None))
343 v = safe_unicode(cipher.process_result_value(v, None))
344
344
345 converter = self.SETTINGS_TYPES.get(_type) or \
345 converter = self.SETTINGS_TYPES.get(_type) or \
346 self.SETTINGS_TYPES['unicode']
346 self.SETTINGS_TYPES['unicode']
347 return converter(v)
347 return converter(v)
348
348
349 @app_settings_value.setter
349 @app_settings_value.setter
350 def app_settings_value(self, val):
350 def app_settings_value(self, val):
351 """
351 """
352 Setter that will always make sure we use unicode in app_settings_value
352 Setter that will always make sure we use unicode in app_settings_value
353
353
354 :param val:
354 :param val:
355 """
355 """
356 val = safe_unicode(val)
356 val = safe_unicode(val)
357 # encode the encrypted value
357 # encode the encrypted value
358 if 'encrypted' in self.app_settings_type:
358 if 'encrypted' in self.app_settings_type:
359 cipher = EncryptedTextValue()
359 cipher = EncryptedTextValue()
360 val = safe_unicode(cipher.process_bind_param(val, None))
360 val = safe_unicode(cipher.process_bind_param(val, None))
361 self._app_settings_value = val
361 self._app_settings_value = val
362
362
363 @hybrid_property
363 @hybrid_property
364 def app_settings_type(self):
364 def app_settings_type(self):
365 return self._app_settings_type
365 return self._app_settings_type
366
366
367 @app_settings_type.setter
367 @app_settings_type.setter
368 def app_settings_type(self, val):
368 def app_settings_type(self, val):
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
370 raise Exception('type must be one of %s got %s'
370 raise Exception('type must be one of %s got %s'
371 % (self.SETTINGS_TYPES.keys(), val))
371 % (self.SETTINGS_TYPES.keys(), val))
372 self._app_settings_type = val
372 self._app_settings_type = val
373
373
374 def __unicode__(self):
374 def __unicode__(self):
375 return u"<%s('%s:%s[%s]')>" % (
375 return u"<%s('%s:%s[%s]')>" % (
376 self.__class__.__name__,
376 self.__class__.__name__,
377 self.app_settings_name, self.app_settings_value,
377 self.app_settings_name, self.app_settings_value,
378 self.app_settings_type
378 self.app_settings_type
379 )
379 )
380
380
381
381
382 class RhodeCodeUi(Base, BaseModel):
382 class RhodeCodeUi(Base, BaseModel):
383 __tablename__ = 'rhodecode_ui'
383 __tablename__ = 'rhodecode_ui'
384 __table_args__ = (
384 __table_args__ = (
385 UniqueConstraint('ui_key'),
385 UniqueConstraint('ui_key'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
391 # HG
391 # HG
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
393 HOOK_PULL = 'outgoing.pull_logger'
393 HOOK_PULL = 'outgoing.pull_logger'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
396 HOOK_PUSH = 'changegroup.push_logger'
396 HOOK_PUSH = 'changegroup.push_logger'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
398
398
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
400 # git part is currently hardcoded.
400 # git part is currently hardcoded.
401
401
402 # SVN PATTERNS
402 # SVN PATTERNS
403 SVN_BRANCH_ID = 'vcs_svn_branch'
403 SVN_BRANCH_ID = 'vcs_svn_branch'
404 SVN_TAG_ID = 'vcs_svn_tag'
404 SVN_TAG_ID = 'vcs_svn_tag'
405
405
406 ui_id = Column(
406 ui_id = Column(
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
408 primary_key=True)
408 primary_key=True)
409 ui_section = Column(
409 ui_section = Column(
410 "ui_section", String(255), nullable=True, unique=None, default=None)
410 "ui_section", String(255), nullable=True, unique=None, default=None)
411 ui_key = Column(
411 ui_key = Column(
412 "ui_key", String(255), nullable=True, unique=None, default=None)
412 "ui_key", String(255), nullable=True, unique=None, default=None)
413 ui_value = Column(
413 ui_value = Column(
414 "ui_value", String(255), nullable=True, unique=None, default=None)
414 "ui_value", String(255), nullable=True, unique=None, default=None)
415 ui_active = Column(
415 ui_active = Column(
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
417
417
418 def __repr__(self):
418 def __repr__(self):
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
420 self.ui_key, self.ui_value)
420 self.ui_key, self.ui_value)
421
421
422
422
423 class RepoRhodeCodeSetting(Base, BaseModel):
423 class RepoRhodeCodeSetting(Base, BaseModel):
424 __tablename__ = 'repo_rhodecode_settings'
424 __tablename__ = 'repo_rhodecode_settings'
425 __table_args__ = (
425 __table_args__ = (
426 UniqueConstraint(
426 UniqueConstraint(
427 'app_settings_name', 'repository_id',
427 'app_settings_name', 'repository_id',
428 name='uq_repo_rhodecode_setting_name_repo_id'),
428 name='uq_repo_rhodecode_setting_name_repo_id'),
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
431 )
431 )
432
432
433 repository_id = Column(
433 repository_id = Column(
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
435 nullable=False)
435 nullable=False)
436 app_settings_id = Column(
436 app_settings_id = Column(
437 "app_settings_id", Integer(), nullable=False, unique=True,
437 "app_settings_id", Integer(), nullable=False, unique=True,
438 default=None, primary_key=True)
438 default=None, primary_key=True)
439 app_settings_name = Column(
439 app_settings_name = Column(
440 "app_settings_name", String(255), nullable=True, unique=None,
440 "app_settings_name", String(255), nullable=True, unique=None,
441 default=None)
441 default=None)
442 _app_settings_value = Column(
442 _app_settings_value = Column(
443 "app_settings_value", String(4096), nullable=True, unique=None,
443 "app_settings_value", String(4096), nullable=True, unique=None,
444 default=None)
444 default=None)
445 _app_settings_type = Column(
445 _app_settings_type = Column(
446 "app_settings_type", String(255), nullable=True, unique=None,
446 "app_settings_type", String(255), nullable=True, unique=None,
447 default=None)
447 default=None)
448
448
449 repository = relationship('Repository')
449 repository = relationship('Repository')
450
450
451 def __init__(self, repository_id, key='', val='', type='unicode'):
451 def __init__(self, repository_id, key='', val='', type='unicode'):
452 self.repository_id = repository_id
452 self.repository_id = repository_id
453 self.app_settings_name = key
453 self.app_settings_name = key
454 self.app_settings_type = type
454 self.app_settings_type = type
455 self.app_settings_value = val
455 self.app_settings_value = val
456
456
457 @validates('_app_settings_value')
457 @validates('_app_settings_value')
458 def validate_settings_value(self, key, val):
458 def validate_settings_value(self, key, val):
459 assert type(val) == unicode
459 assert type(val) == unicode
460 return val
460 return val
461
461
462 @hybrid_property
462 @hybrid_property
463 def app_settings_value(self):
463 def app_settings_value(self):
464 v = self._app_settings_value
464 v = self._app_settings_value
465 type_ = self.app_settings_type
465 type_ = self.app_settings_type
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
468 return converter(v)
468 return converter(v)
469
469
470 @app_settings_value.setter
470 @app_settings_value.setter
471 def app_settings_value(self, val):
471 def app_settings_value(self, val):
472 """
472 """
473 Setter that will always make sure we use unicode in app_settings_value
473 Setter that will always make sure we use unicode in app_settings_value
474
474
475 :param val:
475 :param val:
476 """
476 """
477 self._app_settings_value = safe_unicode(val)
477 self._app_settings_value = safe_unicode(val)
478
478
479 @hybrid_property
479 @hybrid_property
480 def app_settings_type(self):
480 def app_settings_type(self):
481 return self._app_settings_type
481 return self._app_settings_type
482
482
483 @app_settings_type.setter
483 @app_settings_type.setter
484 def app_settings_type(self, val):
484 def app_settings_type(self, val):
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 if val not in SETTINGS_TYPES:
486 if val not in SETTINGS_TYPES:
487 raise Exception('type must be one of %s got %s'
487 raise Exception('type must be one of %s got %s'
488 % (SETTINGS_TYPES.keys(), val))
488 % (SETTINGS_TYPES.keys(), val))
489 self._app_settings_type = val
489 self._app_settings_type = val
490
490
491 def __unicode__(self):
491 def __unicode__(self):
492 return u"<%s('%s:%s:%s[%s]')>" % (
492 return u"<%s('%s:%s:%s[%s]')>" % (
493 self.__class__.__name__, self.repository.repo_name,
493 self.__class__.__name__, self.repository.repo_name,
494 self.app_settings_name, self.app_settings_value,
494 self.app_settings_name, self.app_settings_value,
495 self.app_settings_type
495 self.app_settings_type
496 )
496 )
497
497
498
498
499 class RepoRhodeCodeUi(Base, BaseModel):
499 class RepoRhodeCodeUi(Base, BaseModel):
500 __tablename__ = 'repo_rhodecode_ui'
500 __tablename__ = 'repo_rhodecode_ui'
501 __table_args__ = (
501 __table_args__ = (
502 UniqueConstraint(
502 UniqueConstraint(
503 'repository_id', 'ui_section', 'ui_key',
503 'repository_id', 'ui_section', 'ui_key',
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
507 )
507 )
508
508
509 repository_id = Column(
509 repository_id = Column(
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
511 nullable=False)
511 nullable=False)
512 ui_id = Column(
512 ui_id = Column(
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
514 primary_key=True)
514 primary_key=True)
515 ui_section = Column(
515 ui_section = Column(
516 "ui_section", String(255), nullable=True, unique=None, default=None)
516 "ui_section", String(255), nullable=True, unique=None, default=None)
517 ui_key = Column(
517 ui_key = Column(
518 "ui_key", String(255), nullable=True, unique=None, default=None)
518 "ui_key", String(255), nullable=True, unique=None, default=None)
519 ui_value = Column(
519 ui_value = Column(
520 "ui_value", String(255), nullable=True, unique=None, default=None)
520 "ui_value", String(255), nullable=True, unique=None, default=None)
521 ui_active = Column(
521 ui_active = Column(
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
523
523
524 repository = relationship('Repository')
524 repository = relationship('Repository')
525
525
526 def __repr__(self):
526 def __repr__(self):
527 return '<%s[%s:%s]%s=>%s]>' % (
527 return '<%s[%s:%s]%s=>%s]>' % (
528 self.__class__.__name__, self.repository.repo_name,
528 self.__class__.__name__, self.repository.repo_name,
529 self.ui_section, self.ui_key, self.ui_value)
529 self.ui_section, self.ui_key, self.ui_value)
530
530
531
531
532 class User(Base, BaseModel):
532 class User(Base, BaseModel):
533 __tablename__ = 'users'
533 __tablename__ = 'users'
534 __table_args__ = (
534 __table_args__ = (
535 UniqueConstraint('username'), UniqueConstraint('email'),
535 UniqueConstraint('username'), UniqueConstraint('email'),
536 Index('u_username_idx', 'username'),
536 Index('u_username_idx', 'username'),
537 Index('u_email_idx', 'email'),
537 Index('u_email_idx', 'email'),
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
540 )
540 )
541 DEFAULT_USER = 'default'
541 DEFAULT_USER = 'default'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
544
544
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
555
555
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
562
562
563 user_log = relationship('UserLog')
563 user_log = relationship('UserLog')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
565
565
566 repositories = relationship('Repository')
566 repositories = relationship('Repository')
567 repository_groups = relationship('RepoGroup')
567 repository_groups = relationship('RepoGroup')
568 user_groups = relationship('UserGroup')
568 user_groups = relationship('UserGroup')
569
569
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
572
572
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
576
576
577 group_member = relationship('UserGroupMember', cascade='all')
577 group_member = relationship('UserGroupMember', cascade='all')
578
578
579 notifications = relationship('UserNotification', cascade='all')
579 notifications = relationship('UserNotification', cascade='all')
580 # notifications assigned to this user
580 # notifications assigned to this user
581 user_created_notifications = relationship('Notification', cascade='all')
581 user_created_notifications = relationship('Notification', cascade='all')
582 # comments created by this user
582 # comments created by this user
583 user_comments = relationship('ChangesetComment', cascade='all')
583 user_comments = relationship('ChangesetComment', cascade='all')
584 # user profile extra info
584 # user profile extra info
585 user_emails = relationship('UserEmailMap', cascade='all')
585 user_emails = relationship('UserEmailMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
589
589
590 # gists
590 # gists
591 user_gists = relationship('Gist', cascade='all')
591 user_gists = relationship('Gist', cascade='all')
592 # user pull requests
592 # user pull requests
593 user_pull_requests = relationship('PullRequest', cascade='all')
593 user_pull_requests = relationship('PullRequest', cascade='all')
594 # external identities
594 # external identities
595 extenal_identities = relationship(
595 extenal_identities = relationship(
596 'ExternalIdentity',
596 'ExternalIdentity',
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
598 cascade='all')
598 cascade='all')
599 # review rules
599 # review rules
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
601
601
602 def __unicode__(self):
602 def __unicode__(self):
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
604 self.user_id, self.username)
604 self.user_id, self.username)
605
605
606 @hybrid_property
606 @hybrid_property
607 def email(self):
607 def email(self):
608 return self._email
608 return self._email
609
609
610 @email.setter
610 @email.setter
611 def email(self, val):
611 def email(self, val):
612 self._email = val.lower() if val else None
612 self._email = val.lower() if val else None
613
613
614 @hybrid_property
614 @hybrid_property
615 def first_name(self):
615 def first_name(self):
616 from rhodecode.lib import helpers as h
616 from rhodecode.lib import helpers as h
617 if self.name:
617 if self.name:
618 return h.escape(self.name)
618 return h.escape(self.name)
619 return self.name
619 return self.name
620
620
621 @hybrid_property
621 @hybrid_property
622 def last_name(self):
622 def last_name(self):
623 from rhodecode.lib import helpers as h
623 from rhodecode.lib import helpers as h
624 if self.lastname:
624 if self.lastname:
625 return h.escape(self.lastname)
625 return h.escape(self.lastname)
626 return self.lastname
626 return self.lastname
627
627
628 @hybrid_property
628 @hybrid_property
629 def api_key(self):
629 def api_key(self):
630 """
630 """
631 Fetch if exist an auth-token with role ALL connected to this user
631 Fetch if exist an auth-token with role ALL connected to this user
632 """
632 """
633 user_auth_token = UserApiKeys.query()\
633 user_auth_token = UserApiKeys.query()\
634 .filter(UserApiKeys.user_id == self.user_id)\
634 .filter(UserApiKeys.user_id == self.user_id)\
635 .filter(or_(UserApiKeys.expires == -1,
635 .filter(or_(UserApiKeys.expires == -1,
636 UserApiKeys.expires >= time.time()))\
636 UserApiKeys.expires >= time.time()))\
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
638 if user_auth_token:
638 if user_auth_token:
639 user_auth_token = user_auth_token.api_key
639 user_auth_token = user_auth_token.api_key
640
640
641 return user_auth_token
641 return user_auth_token
642
642
643 @api_key.setter
643 @api_key.setter
644 def api_key(self, val):
644 def api_key(self, val):
645 # don't allow to set API key this is deprecated for now
645 # don't allow to set API key this is deprecated for now
646 self._api_key = None
646 self._api_key = None
647
647
648 @property
648 @property
649 def reviewer_pull_requests(self):
649 def reviewer_pull_requests(self):
650 return PullRequestReviewers.query() \
650 return PullRequestReviewers.query() \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
653 .all()
653 .all()
654
654
655 @property
655 @property
656 def firstname(self):
656 def firstname(self):
657 # alias for future
657 # alias for future
658 return self.name
658 return self.name
659
659
660 @property
660 @property
661 def emails(self):
661 def emails(self):
662 other = UserEmailMap.query()\
662 other = UserEmailMap.query()\
663 .filter(UserEmailMap.user == self) \
663 .filter(UserEmailMap.user == self) \
664 .order_by(UserEmailMap.email_id.asc()) \
664 .order_by(UserEmailMap.email_id.asc()) \
665 .all()
665 .all()
666 return [self.email] + [x.email for x in other]
666 return [self.email] + [x.email for x in other]
667
667
668 @property
668 @property
669 def auth_tokens(self):
669 def auth_tokens(self):
670 auth_tokens = self.get_auth_tokens()
670 auth_tokens = self.get_auth_tokens()
671 return [x.api_key for x in auth_tokens]
671 return [x.api_key for x in auth_tokens]
672
672
673 def get_auth_tokens(self):
673 def get_auth_tokens(self):
674 return UserApiKeys.query()\
674 return UserApiKeys.query()\
675 .filter(UserApiKeys.user == self)\
675 .filter(UserApiKeys.user == self)\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
677 .all()
677 .all()
678
678
679 @property
679 @property
680 def feed_token(self):
680 def feed_token(self):
681 return self.get_feed_token()
681 return self.get_feed_token()
682
682
683 def get_feed_token(self):
683 def get_feed_token(self):
684 feed_tokens = UserApiKeys.query()\
684 feed_tokens = UserApiKeys.query()\
685 .filter(UserApiKeys.user == self)\
685 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
687 .all()
687 .all()
688 if feed_tokens:
688 if feed_tokens:
689 return feed_tokens[0].api_key
689 return feed_tokens[0].api_key
690 return 'NO_FEED_TOKEN_AVAILABLE'
690 return 'NO_FEED_TOKEN_AVAILABLE'
691
691
692 @classmethod
692 @classmethod
693 def get(cls, user_id, cache=False):
693 def get(cls, user_id, cache=False):
694 if not user_id:
694 if not user_id:
695 return
695 return
696
696
697 user = cls.query()
697 user = cls.query()
698 if cache:
698 if cache:
699 user = user.options(
699 user = user.options(
700 FromCache("sql_cache_short", "get_users_%s" % user_id))
700 FromCache("sql_cache_short", "get_users_%s" % user_id))
701 return user.get(user_id)
701 return user.get(user_id)
702
702
703 @classmethod
703 @classmethod
704 def extra_valid_auth_tokens(cls, user, role=None):
704 def extra_valid_auth_tokens(cls, user, role=None):
705 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
705 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
706 .filter(or_(UserApiKeys.expires == -1,
706 .filter(or_(UserApiKeys.expires == -1,
707 UserApiKeys.expires >= time.time()))
707 UserApiKeys.expires >= time.time()))
708 if role:
708 if role:
709 tokens = tokens.filter(or_(UserApiKeys.role == role,
709 tokens = tokens.filter(or_(UserApiKeys.role == role,
710 UserApiKeys.role == UserApiKeys.ROLE_ALL))
710 UserApiKeys.role == UserApiKeys.ROLE_ALL))
711 return tokens.all()
711 return tokens.all()
712
712
713 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
713 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
714 from rhodecode.lib import auth
714 from rhodecode.lib import auth
715
715
716 log.debug('Trying to authenticate user: %s via auth-token, '
716 log.debug('Trying to authenticate user: %s via auth-token, '
717 'and roles: %s', self, roles)
717 'and roles: %s', self, roles)
718
718
719 if not auth_token:
719 if not auth_token:
720 return False
720 return False
721
721
722 crypto_backend = auth.crypto_backend()
722 crypto_backend = auth.crypto_backend()
723
723
724 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
724 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
725 tokens_q = UserApiKeys.query()\
725 tokens_q = UserApiKeys.query()\
726 .filter(UserApiKeys.user_id == self.user_id)\
726 .filter(UserApiKeys.user_id == self.user_id)\
727 .filter(or_(UserApiKeys.expires == -1,
727 .filter(or_(UserApiKeys.expires == -1,
728 UserApiKeys.expires >= time.time()))
728 UserApiKeys.expires >= time.time()))
729
729
730 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
730 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
731
731
732 plain_tokens = []
732 plain_tokens = []
733 hash_tokens = []
733 hash_tokens = []
734
734
735 for token in tokens_q.all():
735 for token in tokens_q.all():
736 # verify scope first
736 # verify scope first
737 if token.repo_id:
737 if token.repo_id:
738 # token has a scope, we need to verify it
738 # token has a scope, we need to verify it
739 if scope_repo_id != token.repo_id:
739 if scope_repo_id != token.repo_id:
740 log.debug(
740 log.debug(
741 'Scope mismatch: token has a set repo scope: %s, '
741 'Scope mismatch: token has a set repo scope: %s, '
742 'and calling scope is:%s, skipping further checks',
742 'and calling scope is:%s, skipping further checks',
743 token.repo, scope_repo_id)
743 token.repo, scope_repo_id)
744 # token has a scope, and it doesn't match, skip token
744 # token has a scope, and it doesn't match, skip token
745 continue
745 continue
746
746
747 if token.api_key.startswith(crypto_backend.ENC_PREF):
747 if token.api_key.startswith(crypto_backend.ENC_PREF):
748 hash_tokens.append(token.api_key)
748 hash_tokens.append(token.api_key)
749 else:
749 else:
750 plain_tokens.append(token.api_key)
750 plain_tokens.append(token.api_key)
751
751
752 is_plain_match = auth_token in plain_tokens
752 is_plain_match = auth_token in plain_tokens
753 if is_plain_match:
753 if is_plain_match:
754 return True
754 return True
755
755
756 for hashed in hash_tokens:
756 for hashed in hash_tokens:
757 # TODO(marcink): this is expensive to calculate, but most secure
757 # TODO(marcink): this is expensive to calculate, but most secure
758 match = crypto_backend.hash_check(auth_token, hashed)
758 match = crypto_backend.hash_check(auth_token, hashed)
759 if match:
759 if match:
760 return True
760 return True
761
761
762 return False
762 return False
763
763
764 @property
764 @property
765 def ip_addresses(self):
765 def ip_addresses(self):
766 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
766 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
767 return [x.ip_addr for x in ret]
767 return [x.ip_addr for x in ret]
768
768
769 @property
769 @property
770 def username_and_name(self):
770 def username_and_name(self):
771 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
771 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
772
772
773 @property
773 @property
774 def username_or_name_or_email(self):
774 def username_or_name_or_email(self):
775 full_name = self.full_name if self.full_name is not ' ' else None
775 full_name = self.full_name if self.full_name is not ' ' else None
776 return self.username or full_name or self.email
776 return self.username or full_name or self.email
777
777
778 @property
778 @property
779 def full_name(self):
779 def full_name(self):
780 return '%s %s' % (self.first_name, self.last_name)
780 return '%s %s' % (self.first_name, self.last_name)
781
781
782 @property
782 @property
783 def full_name_or_username(self):
783 def full_name_or_username(self):
784 return ('%s %s' % (self.first_name, self.last_name)
784 return ('%s %s' % (self.first_name, self.last_name)
785 if (self.first_name and self.last_name) else self.username)
785 if (self.first_name and self.last_name) else self.username)
786
786
787 @property
787 @property
788 def full_contact(self):
788 def full_contact(self):
789 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
789 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
790
790
791 @property
791 @property
792 def short_contact(self):
792 def short_contact(self):
793 return '%s %s' % (self.first_name, self.last_name)
793 return '%s %s' % (self.first_name, self.last_name)
794
794
795 @property
795 @property
796 def is_admin(self):
796 def is_admin(self):
797 return self.admin
797 return self.admin
798
798
799 def AuthUser(self, **kwargs):
799 def AuthUser(self, **kwargs):
800 """
800 """
801 Returns instance of AuthUser for this user
801 Returns instance of AuthUser for this user
802 """
802 """
803 from rhodecode.lib.auth import AuthUser
803 from rhodecode.lib.auth import AuthUser
804 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
804 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
805
805
806 @hybrid_property
806 @hybrid_property
807 def user_data(self):
807 def user_data(self):
808 if not self._user_data:
808 if not self._user_data:
809 return {}
809 return {}
810
810
811 try:
811 try:
812 return json.loads(self._user_data)
812 return json.loads(self._user_data)
813 except TypeError:
813 except TypeError:
814 return {}
814 return {}
815
815
816 @user_data.setter
816 @user_data.setter
817 def user_data(self, val):
817 def user_data(self, val):
818 if not isinstance(val, dict):
818 if not isinstance(val, dict):
819 raise Exception('user_data must be dict, got %s' % type(val))
819 raise Exception('user_data must be dict, got %s' % type(val))
820 try:
820 try:
821 self._user_data = json.dumps(val)
821 self._user_data = json.dumps(val)
822 except Exception:
822 except Exception:
823 log.error(traceback.format_exc())
823 log.error(traceback.format_exc())
824
824
825 @classmethod
825 @classmethod
826 def get_by_username(cls, username, case_insensitive=False,
826 def get_by_username(cls, username, case_insensitive=False,
827 cache=False, identity_cache=False):
827 cache=False, identity_cache=False):
828 session = Session()
828 session = Session()
829
829
830 if case_insensitive:
830 if case_insensitive:
831 q = cls.query().filter(
831 q = cls.query().filter(
832 func.lower(cls.username) == func.lower(username))
832 func.lower(cls.username) == func.lower(username))
833 else:
833 else:
834 q = cls.query().filter(cls.username == username)
834 q = cls.query().filter(cls.username == username)
835
835
836 if cache:
836 if cache:
837 if identity_cache:
837 if identity_cache:
838 val = cls.identity_cache(session, 'username', username)
838 val = cls.identity_cache(session, 'username', username)
839 if val:
839 if val:
840 return val
840 return val
841 else:
841 else:
842 cache_key = "get_user_by_name_%s" % _hash_key(username)
842 cache_key = "get_user_by_name_%s" % _hash_key(username)
843 q = q.options(
843 q = q.options(
844 FromCache("sql_cache_short", cache_key))
844 FromCache("sql_cache_short", cache_key))
845
845
846 return q.scalar()
846 return q.scalar()
847
847
848 @classmethod
848 @classmethod
849 def get_by_auth_token(cls, auth_token, cache=False):
849 def get_by_auth_token(cls, auth_token, cache=False):
850 q = UserApiKeys.query()\
850 q = UserApiKeys.query()\
851 .filter(UserApiKeys.api_key == auth_token)\
851 .filter(UserApiKeys.api_key == auth_token)\
852 .filter(or_(UserApiKeys.expires == -1,
852 .filter(or_(UserApiKeys.expires == -1,
853 UserApiKeys.expires >= time.time()))
853 UserApiKeys.expires >= time.time()))
854 if cache:
854 if cache:
855 q = q.options(
855 q = q.options(
856 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
856 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
857
857
858 match = q.first()
858 match = q.first()
859 if match:
859 if match:
860 return match.user
860 return match.user
861
861
862 @classmethod
862 @classmethod
863 def get_by_email(cls, email, case_insensitive=False, cache=False):
863 def get_by_email(cls, email, case_insensitive=False, cache=False):
864
864
865 if case_insensitive:
865 if case_insensitive:
866 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
866 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
867
867
868 else:
868 else:
869 q = cls.query().filter(cls.email == email)
869 q = cls.query().filter(cls.email == email)
870
870
871 email_key = _hash_key(email)
871 email_key = _hash_key(email)
872 if cache:
872 if cache:
873 q = q.options(
873 q = q.options(
874 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
874 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
875
875
876 ret = q.scalar()
876 ret = q.scalar()
877 if ret is None:
877 if ret is None:
878 q = UserEmailMap.query()
878 q = UserEmailMap.query()
879 # try fetching in alternate email map
879 # try fetching in alternate email map
880 if case_insensitive:
880 if case_insensitive:
881 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
881 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
882 else:
882 else:
883 q = q.filter(UserEmailMap.email == email)
883 q = q.filter(UserEmailMap.email == email)
884 q = q.options(joinedload(UserEmailMap.user))
884 q = q.options(joinedload(UserEmailMap.user))
885 if cache:
885 if cache:
886 q = q.options(
886 q = q.options(
887 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
887 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
888 ret = getattr(q.scalar(), 'user', None)
888 ret = getattr(q.scalar(), 'user', None)
889
889
890 return ret
890 return ret
891
891
892 @classmethod
892 @classmethod
893 def get_from_cs_author(cls, author):
893 def get_from_cs_author(cls, author):
894 """
894 """
895 Tries to get User objects out of commit author string
895 Tries to get User objects out of commit author string
896
896
897 :param author:
897 :param author:
898 """
898 """
899 from rhodecode.lib.helpers import email, author_name
899 from rhodecode.lib.helpers import email, author_name
900 # Valid email in the attribute passed, see if they're in the system
900 # Valid email in the attribute passed, see if they're in the system
901 _email = email(author)
901 _email = email(author)
902 if _email:
902 if _email:
903 user = cls.get_by_email(_email, case_insensitive=True)
903 user = cls.get_by_email(_email, case_insensitive=True)
904 if user:
904 if user:
905 return user
905 return user
906 # Maybe we can match by username?
906 # Maybe we can match by username?
907 _author = author_name(author)
907 _author = author_name(author)
908 user = cls.get_by_username(_author, case_insensitive=True)
908 user = cls.get_by_username(_author, case_insensitive=True)
909 if user:
909 if user:
910 return user
910 return user
911
911
912 def update_userdata(self, **kwargs):
912 def update_userdata(self, **kwargs):
913 usr = self
913 usr = self
914 old = usr.user_data
914 old = usr.user_data
915 old.update(**kwargs)
915 old.update(**kwargs)
916 usr.user_data = old
916 usr.user_data = old
917 Session().add(usr)
917 Session().add(usr)
918 log.debug('updated userdata with ', kwargs)
918 log.debug('updated userdata with ', kwargs)
919
919
920 def update_lastlogin(self):
920 def update_lastlogin(self):
921 """Update user lastlogin"""
921 """Update user lastlogin"""
922 self.last_login = datetime.datetime.now()
922 self.last_login = datetime.datetime.now()
923 Session().add(self)
923 Session().add(self)
924 log.debug('updated user %s lastlogin', self.username)
924 log.debug('updated user %s lastlogin', self.username)
925
925
926 def update_lastactivity(self):
926 def update_lastactivity(self):
927 """Update user lastactivity"""
927 """Update user lastactivity"""
928 self.last_activity = datetime.datetime.now()
928 self.last_activity = datetime.datetime.now()
929 Session().add(self)
929 Session().add(self)
930 log.debug('updated user `%s` last activity', self.username)
930 log.debug('updated user `%s` last activity', self.username)
931
931
932 def update_password(self, new_password):
932 def update_password(self, new_password):
933 from rhodecode.lib.auth import get_crypt_password
933 from rhodecode.lib.auth import get_crypt_password
934
934
935 self.password = get_crypt_password(new_password)
935 self.password = get_crypt_password(new_password)
936 Session().add(self)
936 Session().add(self)
937
937
938 @classmethod
938 @classmethod
939 def get_first_super_admin(cls):
939 def get_first_super_admin(cls):
940 user = User.query().filter(User.admin == true()).first()
940 user = User.query().filter(User.admin == true()).first()
941 if user is None:
941 if user is None:
942 raise Exception('FATAL: Missing administrative account!')
942 raise Exception('FATAL: Missing administrative account!')
943 return user
943 return user
944
944
945 @classmethod
945 @classmethod
946 def get_all_super_admins(cls):
946 def get_all_super_admins(cls):
947 """
947 """
948 Returns all admin accounts sorted by username
948 Returns all admin accounts sorted by username
949 """
949 """
950 return User.query().filter(User.admin == true())\
950 return User.query().filter(User.admin == true())\
951 .order_by(User.username.asc()).all()
951 .order_by(User.username.asc()).all()
952
952
953 @classmethod
953 @classmethod
954 def get_default_user(cls, cache=False, refresh=False):
954 def get_default_user(cls, cache=False, refresh=False):
955 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
955 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
956 if user is None:
956 if user is None:
957 raise Exception('FATAL: Missing default account!')
957 raise Exception('FATAL: Missing default account!')
958 if refresh:
958 if refresh:
959 # The default user might be based on outdated state which
959 # The default user might be based on outdated state which
960 # has been loaded from the cache.
960 # has been loaded from the cache.
961 # A call to refresh() ensures that the
961 # A call to refresh() ensures that the
962 # latest state from the database is used.
962 # latest state from the database is used.
963 Session().refresh(user)
963 Session().refresh(user)
964 return user
964 return user
965
965
966 def _get_default_perms(self, user, suffix=''):
966 def _get_default_perms(self, user, suffix=''):
967 from rhodecode.model.permission import PermissionModel
967 from rhodecode.model.permission import PermissionModel
968 return PermissionModel().get_default_perms(user.user_perms, suffix)
968 return PermissionModel().get_default_perms(user.user_perms, suffix)
969
969
970 def get_default_perms(self, suffix=''):
970 def get_default_perms(self, suffix=''):
971 return self._get_default_perms(self, suffix)
971 return self._get_default_perms(self, suffix)
972
972
973 def get_api_data(self, include_secrets=False, details='full'):
973 def get_api_data(self, include_secrets=False, details='full'):
974 """
974 """
975 Common function for generating user related data for API
975 Common function for generating user related data for API
976
976
977 :param include_secrets: By default secrets in the API data will be replaced
977 :param include_secrets: By default secrets in the API data will be replaced
978 by a placeholder value to prevent exposing this data by accident. In case
978 by a placeholder value to prevent exposing this data by accident. In case
979 this data shall be exposed, set this flag to ``True``.
979 this data shall be exposed, set this flag to ``True``.
980
980
981 :param details: details can be 'basic|full' basic gives only a subset of
981 :param details: details can be 'basic|full' basic gives only a subset of
982 the available user information that includes user_id, name and emails.
982 the available user information that includes user_id, name and emails.
983 """
983 """
984 user = self
984 user = self
985 user_data = self.user_data
985 user_data = self.user_data
986 data = {
986 data = {
987 'user_id': user.user_id,
987 'user_id': user.user_id,
988 'username': user.username,
988 'username': user.username,
989 'firstname': user.name,
989 'firstname': user.name,
990 'lastname': user.lastname,
990 'lastname': user.lastname,
991 'email': user.email,
991 'email': user.email,
992 'emails': user.emails,
992 'emails': user.emails,
993 }
993 }
994 if details == 'basic':
994 if details == 'basic':
995 return data
995 return data
996
996
997 auth_token_length = 40
997 auth_token_length = 40
998 auth_token_replacement = '*' * auth_token_length
998 auth_token_replacement = '*' * auth_token_length
999
999
1000 extras = {
1000 extras = {
1001 'auth_tokens': [auth_token_replacement],
1001 'auth_tokens': [auth_token_replacement],
1002 'active': user.active,
1002 'active': user.active,
1003 'admin': user.admin,
1003 'admin': user.admin,
1004 'extern_type': user.extern_type,
1004 'extern_type': user.extern_type,
1005 'extern_name': user.extern_name,
1005 'extern_name': user.extern_name,
1006 'last_login': user.last_login,
1006 'last_login': user.last_login,
1007 'last_activity': user.last_activity,
1007 'last_activity': user.last_activity,
1008 'ip_addresses': user.ip_addresses,
1008 'ip_addresses': user.ip_addresses,
1009 'language': user_data.get('language')
1009 'language': user_data.get('language')
1010 }
1010 }
1011 data.update(extras)
1011 data.update(extras)
1012
1012
1013 if include_secrets:
1013 if include_secrets:
1014 data['auth_tokens'] = user.auth_tokens
1014 data['auth_tokens'] = user.auth_tokens
1015 return data
1015 return data
1016
1016
1017 def __json__(self):
1017 def __json__(self):
1018 data = {
1018 data = {
1019 'full_name': self.full_name,
1019 'full_name': self.full_name,
1020 'full_name_or_username': self.full_name_or_username,
1020 'full_name_or_username': self.full_name_or_username,
1021 'short_contact': self.short_contact,
1021 'short_contact': self.short_contact,
1022 'full_contact': self.full_contact,
1022 'full_contact': self.full_contact,
1023 }
1023 }
1024 data.update(self.get_api_data())
1024 data.update(self.get_api_data())
1025 return data
1025 return data
1026
1026
1027
1027
1028 class UserApiKeys(Base, BaseModel):
1028 class UserApiKeys(Base, BaseModel):
1029 __tablename__ = 'user_api_keys'
1029 __tablename__ = 'user_api_keys'
1030 __table_args__ = (
1030 __table_args__ = (
1031 Index('uak_api_key_idx', 'api_key', unique=True),
1031 Index('uak_api_key_idx', 'api_key', unique=True),
1032 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1032 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1033 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1033 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1034 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1034 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1035 )
1035 )
1036 __mapper_args__ = {}
1036 __mapper_args__ = {}
1037
1037
1038 # ApiKey role
1038 # ApiKey role
1039 ROLE_ALL = 'token_role_all'
1039 ROLE_ALL = 'token_role_all'
1040 ROLE_HTTP = 'token_role_http'
1040 ROLE_HTTP = 'token_role_http'
1041 ROLE_VCS = 'token_role_vcs'
1041 ROLE_VCS = 'token_role_vcs'
1042 ROLE_API = 'token_role_api'
1042 ROLE_API = 'token_role_api'
1043 ROLE_FEED = 'token_role_feed'
1043 ROLE_FEED = 'token_role_feed'
1044 ROLE_PASSWORD_RESET = 'token_password_reset'
1044 ROLE_PASSWORD_RESET = 'token_password_reset'
1045
1045
1046 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1046 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1047
1047
1048 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1048 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1050 api_key = Column("api_key", String(255), nullable=False, unique=True)
1050 api_key = Column("api_key", String(255), nullable=False, unique=True)
1051 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1051 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1052 expires = Column('expires', Float(53), nullable=False)
1052 expires = Column('expires', Float(53), nullable=False)
1053 role = Column('role', String(255), nullable=True)
1053 role = Column('role', String(255), nullable=True)
1054 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1054 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1055
1055
1056 # scope columns
1056 # scope columns
1057 repo_id = Column(
1057 repo_id = Column(
1058 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1058 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1059 nullable=True, unique=None, default=None)
1059 nullable=True, unique=None, default=None)
1060 repo = relationship('Repository', lazy='joined')
1060 repo = relationship('Repository', lazy='joined')
1061
1061
1062 repo_group_id = Column(
1062 repo_group_id = Column(
1063 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1063 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1064 nullable=True, unique=None, default=None)
1064 nullable=True, unique=None, default=None)
1065 repo_group = relationship('RepoGroup', lazy='joined')
1065 repo_group = relationship('RepoGroup', lazy='joined')
1066
1066
1067 user = relationship('User', lazy='joined')
1067 user = relationship('User', lazy='joined')
1068
1068
1069 def __unicode__(self):
1069 def __unicode__(self):
1070 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1070 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1071
1071
1072 def __json__(self):
1072 def __json__(self):
1073 data = {
1073 data = {
1074 'auth_token': self.api_key,
1074 'auth_token': self.api_key,
1075 'role': self.role,
1075 'role': self.role,
1076 'scope': self.scope_humanized,
1076 'scope': self.scope_humanized,
1077 'expired': self.expired
1077 'expired': self.expired
1078 }
1078 }
1079 return data
1079 return data
1080
1080
1081 def get_api_data(self, include_secrets=False):
1081 def get_api_data(self, include_secrets=False):
1082 data = self.__json__()
1082 data = self.__json__()
1083 if include_secrets:
1083 if include_secrets:
1084 return data
1084 return data
1085 else:
1085 else:
1086 data['auth_token'] = self.token_obfuscated
1086 data['auth_token'] = self.token_obfuscated
1087 return data
1087 return data
1088
1088
1089 @hybrid_property
1089 @hybrid_property
1090 def description_safe(self):
1090 def description_safe(self):
1091 from rhodecode.lib import helpers as h
1091 from rhodecode.lib import helpers as h
1092 return h.escape(self.description)
1092 return h.escape(self.description)
1093
1093
1094 @property
1094 @property
1095 def expired(self):
1095 def expired(self):
1096 if self.expires == -1:
1096 if self.expires == -1:
1097 return False
1097 return False
1098 return time.time() > self.expires
1098 return time.time() > self.expires
1099
1099
1100 @classmethod
1100 @classmethod
1101 def _get_role_name(cls, role):
1101 def _get_role_name(cls, role):
1102 return {
1102 return {
1103 cls.ROLE_ALL: _('all'),
1103 cls.ROLE_ALL: _('all'),
1104 cls.ROLE_HTTP: _('http/web interface'),
1104 cls.ROLE_HTTP: _('http/web interface'),
1105 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1105 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1106 cls.ROLE_API: _('api calls'),
1106 cls.ROLE_API: _('api calls'),
1107 cls.ROLE_FEED: _('feed access'),
1107 cls.ROLE_FEED: _('feed access'),
1108 }.get(role, role)
1108 }.get(role, role)
1109
1109
1110 @property
1110 @property
1111 def role_humanized(self):
1111 def role_humanized(self):
1112 return self._get_role_name(self.role)
1112 return self._get_role_name(self.role)
1113
1113
1114 def _get_scope(self):
1114 def _get_scope(self):
1115 if self.repo:
1115 if self.repo:
1116 return repr(self.repo)
1116 return repr(self.repo)
1117 if self.repo_group:
1117 if self.repo_group:
1118 return repr(self.repo_group) + ' (recursive)'
1118 return repr(self.repo_group) + ' (recursive)'
1119 return 'global'
1119 return 'global'
1120
1120
1121 @property
1121 @property
1122 def scope_humanized(self):
1122 def scope_humanized(self):
1123 return self._get_scope()
1123 return self._get_scope()
1124
1124
1125 @property
1125 @property
1126 def token_obfuscated(self):
1126 def token_obfuscated(self):
1127 if self.api_key:
1127 if self.api_key:
1128 return self.api_key[:4] + "****"
1128 return self.api_key[:4] + "****"
1129
1129
1130
1130
1131 class UserEmailMap(Base, BaseModel):
1131 class UserEmailMap(Base, BaseModel):
1132 __tablename__ = 'user_email_map'
1132 __tablename__ = 'user_email_map'
1133 __table_args__ = (
1133 __table_args__ = (
1134 Index('uem_email_idx', 'email'),
1134 Index('uem_email_idx', 'email'),
1135 UniqueConstraint('email'),
1135 UniqueConstraint('email'),
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1137 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1137 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1138 )
1138 )
1139 __mapper_args__ = {}
1139 __mapper_args__ = {}
1140
1140
1141 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1141 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1142 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1142 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1143 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1143 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1144 user = relationship('User', lazy='joined')
1144 user = relationship('User', lazy='joined')
1145
1145
1146 @validates('_email')
1146 @validates('_email')
1147 def validate_email(self, key, email):
1147 def validate_email(self, key, email):
1148 # check if this email is not main one
1148 # check if this email is not main one
1149 main_email = Session().query(User).filter(User.email == email).scalar()
1149 main_email = Session().query(User).filter(User.email == email).scalar()
1150 if main_email is not None:
1150 if main_email is not None:
1151 raise AttributeError('email %s is present is user table' % email)
1151 raise AttributeError('email %s is present is user table' % email)
1152 return email
1152 return email
1153
1153
1154 @hybrid_property
1154 @hybrid_property
1155 def email(self):
1155 def email(self):
1156 return self._email
1156 return self._email
1157
1157
1158 @email.setter
1158 @email.setter
1159 def email(self, val):
1159 def email(self, val):
1160 self._email = val.lower() if val else None
1160 self._email = val.lower() if val else None
1161
1161
1162
1162
1163 class UserIpMap(Base, BaseModel):
1163 class UserIpMap(Base, BaseModel):
1164 __tablename__ = 'user_ip_map'
1164 __tablename__ = 'user_ip_map'
1165 __table_args__ = (
1165 __table_args__ = (
1166 UniqueConstraint('user_id', 'ip_addr'),
1166 UniqueConstraint('user_id', 'ip_addr'),
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1169 )
1169 )
1170 __mapper_args__ = {}
1170 __mapper_args__ = {}
1171
1171
1172 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1174 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1174 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1175 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1175 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1176 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1176 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1177 user = relationship('User', lazy='joined')
1177 user = relationship('User', lazy='joined')
1178
1178
1179 @hybrid_property
1179 @hybrid_property
1180 def description_safe(self):
1180 def description_safe(self):
1181 from rhodecode.lib import helpers as h
1181 from rhodecode.lib import helpers as h
1182 return h.escape(self.description)
1182 return h.escape(self.description)
1183
1183
1184 @classmethod
1184 @classmethod
1185 def _get_ip_range(cls, ip_addr):
1185 def _get_ip_range(cls, ip_addr):
1186 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1186 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1187 return [str(net.network_address), str(net.broadcast_address)]
1187 return [str(net.network_address), str(net.broadcast_address)]
1188
1188
1189 def __json__(self):
1189 def __json__(self):
1190 return {
1190 return {
1191 'ip_addr': self.ip_addr,
1191 'ip_addr': self.ip_addr,
1192 'ip_range': self._get_ip_range(self.ip_addr),
1192 'ip_range': self._get_ip_range(self.ip_addr),
1193 }
1193 }
1194
1194
1195 def __unicode__(self):
1195 def __unicode__(self):
1196 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1196 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1197 self.user_id, self.ip_addr)
1197 self.user_id, self.ip_addr)
1198
1198
1199
1199
1200 class UserSshKeys(Base, BaseModel):
1200 class UserSshKeys(Base, BaseModel):
1201 __tablename__ = 'user_ssh_keys'
1201 __tablename__ = 'user_ssh_keys'
1202 __table_args__ = (
1202 __table_args__ = (
1203 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1203 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1204
1204
1205 UniqueConstraint('ssh_key_fingerprint'),
1205 UniqueConstraint('ssh_key_fingerprint'),
1206
1206
1207 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1207 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1208 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1208 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1209 )
1209 )
1210 __mapper_args__ = {}
1210 __mapper_args__ = {}
1211
1211
1212 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1212 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1213 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1213 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1214 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1214 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1215
1215
1216 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1216 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1217
1217
1218 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1218 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1219 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1219 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1220 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1220 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1221
1221
1222 user = relationship('User', lazy='joined')
1222 user = relationship('User', lazy='joined')
1223
1223
1224 def __json__(self):
1224 def __json__(self):
1225 data = {
1225 data = {
1226 'ssh_fingerprint': self.ssh_key_fingerprint,
1226 'ssh_fingerprint': self.ssh_key_fingerprint,
1227 'description': self.description,
1227 'description': self.description,
1228 'created_on': self.created_on
1228 'created_on': self.created_on
1229 }
1229 }
1230 return data
1230 return data
1231
1231
1232 def get_api_data(self):
1232 def get_api_data(self):
1233 data = self.__json__()
1233 data = self.__json__()
1234 return data
1234 return data
1235
1235
1236
1236
1237 class UserLog(Base, BaseModel):
1237 class UserLog(Base, BaseModel):
1238 __tablename__ = 'user_logs'
1238 __tablename__ = 'user_logs'
1239 __table_args__ = (
1239 __table_args__ = (
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1241 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1241 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1242 )
1242 )
1243 VERSION_1 = 'v1'
1243 VERSION_1 = 'v1'
1244 VERSION_2 = 'v2'
1244 VERSION_2 = 'v2'
1245 VERSIONS = [VERSION_1, VERSION_2]
1245 VERSIONS = [VERSION_1, VERSION_2]
1246
1246
1247 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1247 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1249 username = Column("username", String(255), nullable=True, unique=None, default=None)
1249 username = Column("username", String(255), nullable=True, unique=None, default=None)
1250 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1250 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1251 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1251 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1252 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1252 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1253 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1253 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1254 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1254 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1255
1255
1256 version = Column("version", String(255), nullable=True, default=VERSION_1)
1256 version = Column("version", String(255), nullable=True, default=VERSION_1)
1257 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1257 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1258 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1258 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1259
1259
1260 def __unicode__(self):
1260 def __unicode__(self):
1261 return u"<%s('id:%s:%s')>" % (
1261 return u"<%s('id:%s:%s')>" % (
1262 self.__class__.__name__, self.repository_name, self.action)
1262 self.__class__.__name__, self.repository_name, self.action)
1263
1263
1264 def __json__(self):
1264 def __json__(self):
1265 return {
1265 return {
1266 'user_id': self.user_id,
1266 'user_id': self.user_id,
1267 'username': self.username,
1267 'username': self.username,
1268 'repository_id': self.repository_id,
1268 'repository_id': self.repository_id,
1269 'repository_name': self.repository_name,
1269 'repository_name': self.repository_name,
1270 'user_ip': self.user_ip,
1270 'user_ip': self.user_ip,
1271 'action_date': self.action_date,
1271 'action_date': self.action_date,
1272 'action': self.action,
1272 'action': self.action,
1273 }
1273 }
1274
1274
1275 @hybrid_property
1275 @hybrid_property
1276 def entry_id(self):
1276 def entry_id(self):
1277 return self.user_log_id
1277 return self.user_log_id
1278
1278
1279 @property
1279 @property
1280 def action_as_day(self):
1280 def action_as_day(self):
1281 return datetime.date(*self.action_date.timetuple()[:3])
1281 return datetime.date(*self.action_date.timetuple()[:3])
1282
1282
1283 user = relationship('User')
1283 user = relationship('User')
1284 repository = relationship('Repository', cascade='')
1284 repository = relationship('Repository', cascade='')
1285
1285
1286
1286
1287 class UserGroup(Base, BaseModel):
1287 class UserGroup(Base, BaseModel):
1288 __tablename__ = 'users_groups'
1288 __tablename__ = 'users_groups'
1289 __table_args__ = (
1289 __table_args__ = (
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1292 )
1292 )
1293
1293
1294 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1294 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1295 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1295 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1296 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1296 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1297 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1297 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1298 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1298 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1300 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1300 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1301 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1301 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1302
1302
1303 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1303 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1304 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1304 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1305 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1305 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1306 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1306 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1307 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1307 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1308 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1308 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1309
1309
1310 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1310 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1311 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1311 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1312
1312
1313 @classmethod
1313 @classmethod
1314 def _load_group_data(cls, column):
1314 def _load_group_data(cls, column):
1315 if not column:
1315 if not column:
1316 return {}
1316 return {}
1317
1317
1318 try:
1318 try:
1319 return json.loads(column) or {}
1319 return json.loads(column) or {}
1320 except TypeError:
1320 except TypeError:
1321 return {}
1321 return {}
1322
1322
1323 @hybrid_property
1323 @hybrid_property
1324 def description_safe(self):
1324 def description_safe(self):
1325 from rhodecode.lib import helpers as h
1325 from rhodecode.lib import helpers as h
1326 return h.escape(self.description)
1326 return h.escape(self.description)
1327
1327
1328 @hybrid_property
1328 @hybrid_property
1329 def group_data(self):
1329 def group_data(self):
1330 return self._load_group_data(self._group_data)
1330 return self._load_group_data(self._group_data)
1331
1331
1332 @group_data.expression
1332 @group_data.expression
1333 def group_data(self, **kwargs):
1333 def group_data(self, **kwargs):
1334 return self._group_data
1334 return self._group_data
1335
1335
1336 @group_data.setter
1336 @group_data.setter
1337 def group_data(self, val):
1337 def group_data(self, val):
1338 try:
1338 try:
1339 self._group_data = json.dumps(val)
1339 self._group_data = json.dumps(val)
1340 except Exception:
1340 except Exception:
1341 log.error(traceback.format_exc())
1341 log.error(traceback.format_exc())
1342
1342
1343 def __unicode__(self):
1343 def __unicode__(self):
1344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1345 self.users_group_id,
1345 self.users_group_id,
1346 self.users_group_name)
1346 self.users_group_name)
1347
1347
1348 @classmethod
1348 @classmethod
1349 def get_by_group_name(cls, group_name, cache=False,
1349 def get_by_group_name(cls, group_name, cache=False,
1350 case_insensitive=False):
1350 case_insensitive=False):
1351 if case_insensitive:
1351 if case_insensitive:
1352 q = cls.query().filter(func.lower(cls.users_group_name) ==
1352 q = cls.query().filter(func.lower(cls.users_group_name) ==
1353 func.lower(group_name))
1353 func.lower(group_name))
1354
1354
1355 else:
1355 else:
1356 q = cls.query().filter(cls.users_group_name == group_name)
1356 q = cls.query().filter(cls.users_group_name == group_name)
1357 if cache:
1357 if cache:
1358 q = q.options(
1358 q = q.options(
1359 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1359 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1360 return q.scalar()
1360 return q.scalar()
1361
1361
1362 @classmethod
1362 @classmethod
1363 def get(cls, user_group_id, cache=False):
1363 def get(cls, user_group_id, cache=False):
1364 if not user_group_id:
1364 if not user_group_id:
1365 return
1365 return
1366
1366
1367 user_group = cls.query()
1367 user_group = cls.query()
1368 if cache:
1368 if cache:
1369 user_group = user_group.options(
1369 user_group = user_group.options(
1370 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1370 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1371 return user_group.get(user_group_id)
1371 return user_group.get(user_group_id)
1372
1372
1373 def permissions(self, with_admins=True, with_owner=True):
1373 def permissions(self, with_admins=True, with_owner=True):
1374 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1374 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1375 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1375 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1376 joinedload(UserUserGroupToPerm.user),
1376 joinedload(UserUserGroupToPerm.user),
1377 joinedload(UserUserGroupToPerm.permission),)
1377 joinedload(UserUserGroupToPerm.permission),)
1378
1378
1379 # get owners and admins and permissions. We do a trick of re-writing
1379 # get owners and admins and permissions. We do a trick of re-writing
1380 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1380 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1381 # has a global reference and changing one object propagates to all
1381 # has a global reference and changing one object propagates to all
1382 # others. This means if admin is also an owner admin_row that change
1382 # others. This means if admin is also an owner admin_row that change
1383 # would propagate to both objects
1383 # would propagate to both objects
1384 perm_rows = []
1384 perm_rows = []
1385 for _usr in q.all():
1385 for _usr in q.all():
1386 usr = AttributeDict(_usr.user.get_dict())
1386 usr = AttributeDict(_usr.user.get_dict())
1387 usr.permission = _usr.permission.permission_name
1387 usr.permission = _usr.permission.permission_name
1388 perm_rows.append(usr)
1388 perm_rows.append(usr)
1389
1389
1390 # filter the perm rows by 'default' first and then sort them by
1390 # filter the perm rows by 'default' first and then sort them by
1391 # admin,write,read,none permissions sorted again alphabetically in
1391 # admin,write,read,none permissions sorted again alphabetically in
1392 # each group
1392 # each group
1393 perm_rows = sorted(perm_rows, key=display_user_sort)
1393 perm_rows = sorted(perm_rows, key=display_user_sort)
1394
1394
1395 _admin_perm = 'usergroup.admin'
1395 _admin_perm = 'usergroup.admin'
1396 owner_row = []
1396 owner_row = []
1397 if with_owner:
1397 if with_owner:
1398 usr = AttributeDict(self.user.get_dict())
1398 usr = AttributeDict(self.user.get_dict())
1399 usr.owner_row = True
1399 usr.owner_row = True
1400 usr.permission = _admin_perm
1400 usr.permission = _admin_perm
1401 owner_row.append(usr)
1401 owner_row.append(usr)
1402
1402
1403 super_admin_rows = []
1403 super_admin_rows = []
1404 if with_admins:
1404 if with_admins:
1405 for usr in User.get_all_super_admins():
1405 for usr in User.get_all_super_admins():
1406 # if this admin is also owner, don't double the record
1406 # if this admin is also owner, don't double the record
1407 if usr.user_id == owner_row[0].user_id:
1407 if usr.user_id == owner_row[0].user_id:
1408 owner_row[0].admin_row = True
1408 owner_row[0].admin_row = True
1409 else:
1409 else:
1410 usr = AttributeDict(usr.get_dict())
1410 usr = AttributeDict(usr.get_dict())
1411 usr.admin_row = True
1411 usr.admin_row = True
1412 usr.permission = _admin_perm
1412 usr.permission = _admin_perm
1413 super_admin_rows.append(usr)
1413 super_admin_rows.append(usr)
1414
1414
1415 return super_admin_rows + owner_row + perm_rows
1415 return super_admin_rows + owner_row + perm_rows
1416
1416
1417 def permission_user_groups(self):
1417 def permission_user_groups(self):
1418 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1418 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1419 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1419 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1420 joinedload(UserGroupUserGroupToPerm.target_user_group),
1420 joinedload(UserGroupUserGroupToPerm.target_user_group),
1421 joinedload(UserGroupUserGroupToPerm.permission),)
1421 joinedload(UserGroupUserGroupToPerm.permission),)
1422
1422
1423 perm_rows = []
1423 perm_rows = []
1424 for _user_group in q.all():
1424 for _user_group in q.all():
1425 usr = AttributeDict(_user_group.user_group.get_dict())
1425 usr = AttributeDict(_user_group.user_group.get_dict())
1426 usr.permission = _user_group.permission.permission_name
1426 usr.permission = _user_group.permission.permission_name
1427 perm_rows.append(usr)
1427 perm_rows.append(usr)
1428
1428
1429 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1429 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1430 return perm_rows
1430 return perm_rows
1431
1431
1432 def _get_default_perms(self, user_group, suffix=''):
1432 def _get_default_perms(self, user_group, suffix=''):
1433 from rhodecode.model.permission import PermissionModel
1433 from rhodecode.model.permission import PermissionModel
1434 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1434 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1435
1435
1436 def get_default_perms(self, suffix=''):
1436 def get_default_perms(self, suffix=''):
1437 return self._get_default_perms(self, suffix)
1437 return self._get_default_perms(self, suffix)
1438
1438
1439 def get_api_data(self, with_group_members=True, include_secrets=False):
1439 def get_api_data(self, with_group_members=True, include_secrets=False):
1440 """
1440 """
1441 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1441 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1442 basically forwarded.
1442 basically forwarded.
1443
1443
1444 """
1444 """
1445 user_group = self
1445 user_group = self
1446 data = {
1446 data = {
1447 'users_group_id': user_group.users_group_id,
1447 'users_group_id': user_group.users_group_id,
1448 'group_name': user_group.users_group_name,
1448 'group_name': user_group.users_group_name,
1449 'group_description': user_group.user_group_description,
1449 'group_description': user_group.user_group_description,
1450 'active': user_group.users_group_active,
1450 'active': user_group.users_group_active,
1451 'owner': user_group.user.username,
1451 'owner': user_group.user.username,
1452 'owner_email': user_group.user.email,
1452 'owner_email': user_group.user.email,
1453 }
1453 }
1454
1454
1455 if with_group_members:
1455 if with_group_members:
1456 users = []
1456 users = []
1457 for user in user_group.members:
1457 for user in user_group.members:
1458 user = user.user
1458 user = user.user
1459 users.append(user.get_api_data(include_secrets=include_secrets))
1459 users.append(user.get_api_data(include_secrets=include_secrets))
1460 data['users'] = users
1460 data['users'] = users
1461
1461
1462 return data
1462 return data
1463
1463
1464
1464
1465 class UserGroupMember(Base, BaseModel):
1465 class UserGroupMember(Base, BaseModel):
1466 __tablename__ = 'users_groups_members'
1466 __tablename__ = 'users_groups_members'
1467 __table_args__ = (
1467 __table_args__ = (
1468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1470 )
1470 )
1471
1471
1472 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1472 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1473 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1473 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1474 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1474 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1475
1475
1476 user = relationship('User', lazy='joined')
1476 user = relationship('User', lazy='joined')
1477 users_group = relationship('UserGroup')
1477 users_group = relationship('UserGroup')
1478
1478
1479 def __init__(self, gr_id='', u_id=''):
1479 def __init__(self, gr_id='', u_id=''):
1480 self.users_group_id = gr_id
1480 self.users_group_id = gr_id
1481 self.user_id = u_id
1481 self.user_id = u_id
1482
1482
1483
1483
1484 class RepositoryField(Base, BaseModel):
1484 class RepositoryField(Base, BaseModel):
1485 __tablename__ = 'repositories_fields'
1485 __tablename__ = 'repositories_fields'
1486 __table_args__ = (
1486 __table_args__ = (
1487 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1487 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1489 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1489 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1490 )
1490 )
1491 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1491 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1492
1492
1493 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1493 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1494 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1494 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1495 field_key = Column("field_key", String(250))
1495 field_key = Column("field_key", String(250))
1496 field_label = Column("field_label", String(1024), nullable=False)
1496 field_label = Column("field_label", String(1024), nullable=False)
1497 field_value = Column("field_value", String(10000), nullable=False)
1497 field_value = Column("field_value", String(10000), nullable=False)
1498 field_desc = Column("field_desc", String(1024), nullable=False)
1498 field_desc = Column("field_desc", String(1024), nullable=False)
1499 field_type = Column("field_type", String(255), nullable=False, unique=None)
1499 field_type = Column("field_type", String(255), nullable=False, unique=None)
1500 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1500 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1501
1501
1502 repository = relationship('Repository')
1502 repository = relationship('Repository')
1503
1503
1504 @property
1504 @property
1505 def field_key_prefixed(self):
1505 def field_key_prefixed(self):
1506 return 'ex_%s' % self.field_key
1506 return 'ex_%s' % self.field_key
1507
1507
1508 @classmethod
1508 @classmethod
1509 def un_prefix_key(cls, key):
1509 def un_prefix_key(cls, key):
1510 if key.startswith(cls.PREFIX):
1510 if key.startswith(cls.PREFIX):
1511 return key[len(cls.PREFIX):]
1511 return key[len(cls.PREFIX):]
1512 return key
1512 return key
1513
1513
1514 @classmethod
1514 @classmethod
1515 def get_by_key_name(cls, key, repo):
1515 def get_by_key_name(cls, key, repo):
1516 row = cls.query()\
1516 row = cls.query()\
1517 .filter(cls.repository == repo)\
1517 .filter(cls.repository == repo)\
1518 .filter(cls.field_key == key).scalar()
1518 .filter(cls.field_key == key).scalar()
1519 return row
1519 return row
1520
1520
1521
1521
1522 class Repository(Base, BaseModel):
1522 class Repository(Base, BaseModel):
1523 __tablename__ = 'repositories'
1523 __tablename__ = 'repositories'
1524 __table_args__ = (
1524 __table_args__ = (
1525 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1525 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1527 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1527 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1528 )
1528 )
1529 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1529 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1530 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1530 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1531
1531
1532 STATE_CREATED = 'repo_state_created'
1532 STATE_CREATED = 'repo_state_created'
1533 STATE_PENDING = 'repo_state_pending'
1533 STATE_PENDING = 'repo_state_pending'
1534 STATE_ERROR = 'repo_state_error'
1534 STATE_ERROR = 'repo_state_error'
1535
1535
1536 LOCK_AUTOMATIC = 'lock_auto'
1536 LOCK_AUTOMATIC = 'lock_auto'
1537 LOCK_API = 'lock_api'
1537 LOCK_API = 'lock_api'
1538 LOCK_WEB = 'lock_web'
1538 LOCK_WEB = 'lock_web'
1539 LOCK_PULL = 'lock_pull'
1539 LOCK_PULL = 'lock_pull'
1540
1540
1541 NAME_SEP = URL_SEP
1541 NAME_SEP = URL_SEP
1542
1542
1543 repo_id = Column(
1543 repo_id = Column(
1544 "repo_id", Integer(), nullable=False, unique=True, default=None,
1544 "repo_id", Integer(), nullable=False, unique=True, default=None,
1545 primary_key=True)
1545 primary_key=True)
1546 _repo_name = Column(
1546 _repo_name = Column(
1547 "repo_name", Text(), nullable=False, default=None)
1547 "repo_name", Text(), nullable=False, default=None)
1548 _repo_name_hash = Column(
1548 _repo_name_hash = Column(
1549 "repo_name_hash", String(255), nullable=False, unique=True)
1549 "repo_name_hash", String(255), nullable=False, unique=True)
1550 repo_state = Column("repo_state", String(255), nullable=True)
1550 repo_state = Column("repo_state", String(255), nullable=True)
1551
1551
1552 clone_uri = Column(
1552 clone_uri = Column(
1553 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1553 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1554 default=None)
1554 default=None)
1555 repo_type = Column(
1555 repo_type = Column(
1556 "repo_type", String(255), nullable=False, unique=False, default=None)
1556 "repo_type", String(255), nullable=False, unique=False, default=None)
1557 user_id = Column(
1557 user_id = Column(
1558 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1558 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1559 unique=False, default=None)
1559 unique=False, default=None)
1560 private = Column(
1560 private = Column(
1561 "private", Boolean(), nullable=True, unique=None, default=None)
1561 "private", Boolean(), nullable=True, unique=None, default=None)
1562 enable_statistics = Column(
1562 enable_statistics = Column(
1563 "statistics", Boolean(), nullable=True, unique=None, default=True)
1563 "statistics", Boolean(), nullable=True, unique=None, default=True)
1564 enable_downloads = Column(
1564 enable_downloads = Column(
1565 "downloads", Boolean(), nullable=True, unique=None, default=True)
1565 "downloads", Boolean(), nullable=True, unique=None, default=True)
1566 description = Column(
1566 description = Column(
1567 "description", String(10000), nullable=True, unique=None, default=None)
1567 "description", String(10000), nullable=True, unique=None, default=None)
1568 created_on = Column(
1568 created_on = Column(
1569 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1569 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1570 default=datetime.datetime.now)
1570 default=datetime.datetime.now)
1571 updated_on = Column(
1571 updated_on = Column(
1572 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1572 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1573 default=datetime.datetime.now)
1573 default=datetime.datetime.now)
1574 _landing_revision = Column(
1574 _landing_revision = Column(
1575 "landing_revision", String(255), nullable=False, unique=False,
1575 "landing_revision", String(255), nullable=False, unique=False,
1576 default=None)
1576 default=None)
1577 enable_locking = Column(
1577 enable_locking = Column(
1578 "enable_locking", Boolean(), nullable=False, unique=None,
1578 "enable_locking", Boolean(), nullable=False, unique=None,
1579 default=False)
1579 default=False)
1580 _locked = Column(
1580 _locked = Column(
1581 "locked", String(255), nullable=True, unique=False, default=None)
1581 "locked", String(255), nullable=True, unique=False, default=None)
1582 _changeset_cache = Column(
1582 _changeset_cache = Column(
1583 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1583 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1584
1584
1585 fork_id = Column(
1585 fork_id = Column(
1586 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1586 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1587 nullable=True, unique=False, default=None)
1587 nullable=True, unique=False, default=None)
1588 group_id = Column(
1588 group_id = Column(
1589 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1589 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1590 unique=False, default=None)
1590 unique=False, default=None)
1591
1591
1592 user = relationship('User', lazy='joined')
1592 user = relationship('User', lazy='joined')
1593 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1593 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1594 group = relationship('RepoGroup', lazy='joined')
1594 group = relationship('RepoGroup', lazy='joined')
1595 repo_to_perm = relationship(
1595 repo_to_perm = relationship(
1596 'UserRepoToPerm', cascade='all',
1596 'UserRepoToPerm', cascade='all',
1597 order_by='UserRepoToPerm.repo_to_perm_id')
1597 order_by='UserRepoToPerm.repo_to_perm_id')
1598 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1598 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1599 stats = relationship('Statistics', cascade='all', uselist=False)
1599 stats = relationship('Statistics', cascade='all', uselist=False)
1600
1600
1601 followers = relationship(
1601 followers = relationship(
1602 'UserFollowing',
1602 'UserFollowing',
1603 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1603 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1604 cascade='all')
1604 cascade='all')
1605 extra_fields = relationship(
1605 extra_fields = relationship(
1606 'RepositoryField', cascade="all, delete, delete-orphan")
1606 'RepositoryField', cascade="all, delete, delete-orphan")
1607 logs = relationship('UserLog')
1607 logs = relationship('UserLog')
1608 comments = relationship(
1608 comments = relationship(
1609 'ChangesetComment', cascade="all, delete, delete-orphan")
1609 'ChangesetComment', cascade="all, delete, delete-orphan")
1610 pull_requests_source = relationship(
1610 pull_requests_source = relationship(
1611 'PullRequest',
1611 'PullRequest',
1612 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1612 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1613 cascade="all, delete, delete-orphan")
1613 cascade="all, delete, delete-orphan")
1614 pull_requests_target = relationship(
1614 pull_requests_target = relationship(
1615 'PullRequest',
1615 'PullRequest',
1616 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1616 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1617 cascade="all, delete, delete-orphan")
1617 cascade="all, delete, delete-orphan")
1618 ui = relationship('RepoRhodeCodeUi', cascade="all")
1618 ui = relationship('RepoRhodeCodeUi', cascade="all")
1619 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1619 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1620 integrations = relationship('Integration',
1620 integrations = relationship('Integration',
1621 cascade="all, delete, delete-orphan")
1621 cascade="all, delete, delete-orphan")
1622
1622
1623 def __unicode__(self):
1623 def __unicode__(self):
1624 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1624 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1625 safe_unicode(self.repo_name))
1625 safe_unicode(self.repo_name))
1626
1626
1627 @hybrid_property
1627 @hybrid_property
1628 def description_safe(self):
1628 def description_safe(self):
1629 from rhodecode.lib import helpers as h
1629 from rhodecode.lib import helpers as h
1630 return h.escape(self.description)
1630 return h.escape(self.description)
1631
1631
1632 @hybrid_property
1632 @hybrid_property
1633 def landing_rev(self):
1633 def landing_rev(self):
1634 # always should return [rev_type, rev]
1634 # always should return [rev_type, rev]
1635 if self._landing_revision:
1635 if self._landing_revision:
1636 _rev_info = self._landing_revision.split(':')
1636 _rev_info = self._landing_revision.split(':')
1637 if len(_rev_info) < 2:
1637 if len(_rev_info) < 2:
1638 _rev_info.insert(0, 'rev')
1638 _rev_info.insert(0, 'rev')
1639 return [_rev_info[0], _rev_info[1]]
1639 return [_rev_info[0], _rev_info[1]]
1640 return [None, None]
1640 return [None, None]
1641
1641
1642 @landing_rev.setter
1642 @landing_rev.setter
1643 def landing_rev(self, val):
1643 def landing_rev(self, val):
1644 if ':' not in val:
1644 if ':' not in val:
1645 raise ValueError('value must be delimited with `:` and consist '
1645 raise ValueError('value must be delimited with `:` and consist '
1646 'of <rev_type>:<rev>, got %s instead' % val)
1646 'of <rev_type>:<rev>, got %s instead' % val)
1647 self._landing_revision = val
1647 self._landing_revision = val
1648
1648
1649 @hybrid_property
1649 @hybrid_property
1650 def locked(self):
1650 def locked(self):
1651 if self._locked:
1651 if self._locked:
1652 user_id, timelocked, reason = self._locked.split(':')
1652 user_id, timelocked, reason = self._locked.split(':')
1653 lock_values = int(user_id), timelocked, reason
1653 lock_values = int(user_id), timelocked, reason
1654 else:
1654 else:
1655 lock_values = [None, None, None]
1655 lock_values = [None, None, None]
1656 return lock_values
1656 return lock_values
1657
1657
1658 @locked.setter
1658 @locked.setter
1659 def locked(self, val):
1659 def locked(self, val):
1660 if val and isinstance(val, (list, tuple)):
1660 if val and isinstance(val, (list, tuple)):
1661 self._locked = ':'.join(map(str, val))
1661 self._locked = ':'.join(map(str, val))
1662 else:
1662 else:
1663 self._locked = None
1663 self._locked = None
1664
1664
1665 @hybrid_property
1665 @hybrid_property
1666 def changeset_cache(self):
1666 def changeset_cache(self):
1667 from rhodecode.lib.vcs.backends.base import EmptyCommit
1667 from rhodecode.lib.vcs.backends.base import EmptyCommit
1668 dummy = EmptyCommit().__json__()
1668 dummy = EmptyCommit().__json__()
1669 if not self._changeset_cache:
1669 if not self._changeset_cache:
1670 return dummy
1670 return dummy
1671 try:
1671 try:
1672 return json.loads(self._changeset_cache)
1672 return json.loads(self._changeset_cache)
1673 except TypeError:
1673 except TypeError:
1674 return dummy
1674 return dummy
1675 except Exception:
1675 except Exception:
1676 log.error(traceback.format_exc())
1676 log.error(traceback.format_exc())
1677 return dummy
1677 return dummy
1678
1678
1679 @changeset_cache.setter
1679 @changeset_cache.setter
1680 def changeset_cache(self, val):
1680 def changeset_cache(self, val):
1681 try:
1681 try:
1682 self._changeset_cache = json.dumps(val)
1682 self._changeset_cache = json.dumps(val)
1683 except Exception:
1683 except Exception:
1684 log.error(traceback.format_exc())
1684 log.error(traceback.format_exc())
1685
1685
1686 @hybrid_property
1686 @hybrid_property
1687 def repo_name(self):
1687 def repo_name(self):
1688 return self._repo_name
1688 return self._repo_name
1689
1689
1690 @repo_name.setter
1690 @repo_name.setter
1691 def repo_name(self, value):
1691 def repo_name(self, value):
1692 self._repo_name = value
1692 self._repo_name = value
1693 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1693 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1694
1694
1695 @classmethod
1695 @classmethod
1696 def normalize_repo_name(cls, repo_name):
1696 def normalize_repo_name(cls, repo_name):
1697 """
1697 """
1698 Normalizes os specific repo_name to the format internally stored inside
1698 Normalizes os specific repo_name to the format internally stored inside
1699 database using URL_SEP
1699 database using URL_SEP
1700
1700
1701 :param cls:
1701 :param cls:
1702 :param repo_name:
1702 :param repo_name:
1703 """
1703 """
1704 return cls.NAME_SEP.join(repo_name.split(os.sep))
1704 return cls.NAME_SEP.join(repo_name.split(os.sep))
1705
1705
1706 @classmethod
1706 @classmethod
1707 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1707 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1708 session = Session()
1708 session = Session()
1709 q = session.query(cls).filter(cls.repo_name == repo_name)
1709 q = session.query(cls).filter(cls.repo_name == repo_name)
1710
1710
1711 if cache:
1711 if cache:
1712 if identity_cache:
1712 if identity_cache:
1713 val = cls.identity_cache(session, 'repo_name', repo_name)
1713 val = cls.identity_cache(session, 'repo_name', repo_name)
1714 if val:
1714 if val:
1715 return val
1715 return val
1716 else:
1716 else:
1717 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1717 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1718 q = q.options(
1718 q = q.options(
1719 FromCache("sql_cache_short", cache_key))
1719 FromCache("sql_cache_short", cache_key))
1720
1720
1721 return q.scalar()
1721 return q.scalar()
1722
1722
1723 @classmethod
1723 @classmethod
1724 def get_by_full_path(cls, repo_full_path):
1724 def get_by_full_path(cls, repo_full_path):
1725 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1725 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1726 repo_name = cls.normalize_repo_name(repo_name)
1726 repo_name = cls.normalize_repo_name(repo_name)
1727 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1727 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1728
1728
1729 @classmethod
1729 @classmethod
1730 def get_repo_forks(cls, repo_id):
1730 def get_repo_forks(cls, repo_id):
1731 return cls.query().filter(Repository.fork_id == repo_id)
1731 return cls.query().filter(Repository.fork_id == repo_id)
1732
1732
1733 @classmethod
1733 @classmethod
1734 def base_path(cls):
1734 def base_path(cls):
1735 """
1735 """
1736 Returns base path when all repos are stored
1736 Returns base path when all repos are stored
1737
1737
1738 :param cls:
1738 :param cls:
1739 """
1739 """
1740 q = Session().query(RhodeCodeUi)\
1740 q = Session().query(RhodeCodeUi)\
1741 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1741 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1742 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1742 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1743 return q.one().ui_value
1743 return q.one().ui_value
1744
1744
1745 @classmethod
1745 @classmethod
1746 def is_valid(cls, repo_name):
1746 def is_valid(cls, repo_name):
1747 """
1747 """
1748 returns True if given repo name is a valid filesystem repository
1748 returns True if given repo name is a valid filesystem repository
1749
1749
1750 :param cls:
1750 :param cls:
1751 :param repo_name:
1751 :param repo_name:
1752 """
1752 """
1753 from rhodecode.lib.utils import is_valid_repo
1753 from rhodecode.lib.utils import is_valid_repo
1754
1754
1755 return is_valid_repo(repo_name, cls.base_path())
1755 return is_valid_repo(repo_name, cls.base_path())
1756
1756
1757 @classmethod
1757 @classmethod
1758 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1758 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1759 case_insensitive=True):
1759 case_insensitive=True):
1760 q = Repository.query()
1760 q = Repository.query()
1761
1761
1762 if not isinstance(user_id, Optional):
1762 if not isinstance(user_id, Optional):
1763 q = q.filter(Repository.user_id == user_id)
1763 q = q.filter(Repository.user_id == user_id)
1764
1764
1765 if not isinstance(group_id, Optional):
1765 if not isinstance(group_id, Optional):
1766 q = q.filter(Repository.group_id == group_id)
1766 q = q.filter(Repository.group_id == group_id)
1767
1767
1768 if case_insensitive:
1768 if case_insensitive:
1769 q = q.order_by(func.lower(Repository.repo_name))
1769 q = q.order_by(func.lower(Repository.repo_name))
1770 else:
1770 else:
1771 q = q.order_by(Repository.repo_name)
1771 q = q.order_by(Repository.repo_name)
1772 return q.all()
1772 return q.all()
1773
1773
1774 @property
1774 @property
1775 def forks(self):
1775 def forks(self):
1776 """
1776 """
1777 Return forks of this repo
1777 Return forks of this repo
1778 """
1778 """
1779 return Repository.get_repo_forks(self.repo_id)
1779 return Repository.get_repo_forks(self.repo_id)
1780
1780
1781 @property
1781 @property
1782 def parent(self):
1782 def parent(self):
1783 """
1783 """
1784 Returns fork parent
1784 Returns fork parent
1785 """
1785 """
1786 return self.fork
1786 return self.fork
1787
1787
1788 @property
1788 @property
1789 def just_name(self):
1789 def just_name(self):
1790 return self.repo_name.split(self.NAME_SEP)[-1]
1790 return self.repo_name.split(self.NAME_SEP)[-1]
1791
1791
1792 @property
1792 @property
1793 def groups_with_parents(self):
1793 def groups_with_parents(self):
1794 groups = []
1794 groups = []
1795 if self.group is None:
1795 if self.group is None:
1796 return groups
1796 return groups
1797
1797
1798 cur_gr = self.group
1798 cur_gr = self.group
1799 groups.insert(0, cur_gr)
1799 groups.insert(0, cur_gr)
1800 while 1:
1800 while 1:
1801 gr = getattr(cur_gr, 'parent_group', None)
1801 gr = getattr(cur_gr, 'parent_group', None)
1802 cur_gr = cur_gr.parent_group
1802 cur_gr = cur_gr.parent_group
1803 if gr is None:
1803 if gr is None:
1804 break
1804 break
1805 groups.insert(0, gr)
1805 groups.insert(0, gr)
1806
1806
1807 return groups
1807 return groups
1808
1808
1809 @property
1809 @property
1810 def groups_and_repo(self):
1810 def groups_and_repo(self):
1811 return self.groups_with_parents, self
1811 return self.groups_with_parents, self
1812
1812
1813 @LazyProperty
1813 @LazyProperty
1814 def repo_path(self):
1814 def repo_path(self):
1815 """
1815 """
1816 Returns base full path for that repository means where it actually
1816 Returns base full path for that repository means where it actually
1817 exists on a filesystem
1817 exists on a filesystem
1818 """
1818 """
1819 q = Session().query(RhodeCodeUi).filter(
1819 q = Session().query(RhodeCodeUi).filter(
1820 RhodeCodeUi.ui_key == self.NAME_SEP)
1820 RhodeCodeUi.ui_key == self.NAME_SEP)
1821 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1821 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1822 return q.one().ui_value
1822 return q.one().ui_value
1823
1823
1824 @property
1824 @property
1825 def repo_full_path(self):
1825 def repo_full_path(self):
1826 p = [self.repo_path]
1826 p = [self.repo_path]
1827 # we need to split the name by / since this is how we store the
1827 # we need to split the name by / since this is how we store the
1828 # names in the database, but that eventually needs to be converted
1828 # names in the database, but that eventually needs to be converted
1829 # into a valid system path
1829 # into a valid system path
1830 p += self.repo_name.split(self.NAME_SEP)
1830 p += self.repo_name.split(self.NAME_SEP)
1831 return os.path.join(*map(safe_unicode, p))
1831 return os.path.join(*map(safe_unicode, p))
1832
1832
1833 @property
1833 @property
1834 def cache_keys(self):
1834 def cache_keys(self):
1835 """
1835 """
1836 Returns associated cache keys for that repo
1836 Returns associated cache keys for that repo
1837 """
1837 """
1838 return CacheKey.query()\
1838 return CacheKey.query()\
1839 .filter(CacheKey.cache_args == self.repo_name)\
1839 .filter(CacheKey.cache_args == self.repo_name)\
1840 .order_by(CacheKey.cache_key)\
1840 .order_by(CacheKey.cache_key)\
1841 .all()
1841 .all()
1842
1842
1843 def get_new_name(self, repo_name):
1843 def get_new_name(self, repo_name):
1844 """
1844 """
1845 returns new full repository name based on assigned group and new new
1845 returns new full repository name based on assigned group and new new
1846
1846
1847 :param group_name:
1847 :param group_name:
1848 """
1848 """
1849 path_prefix = self.group.full_path_splitted if self.group else []
1849 path_prefix = self.group.full_path_splitted if self.group else []
1850 return self.NAME_SEP.join(path_prefix + [repo_name])
1850 return self.NAME_SEP.join(path_prefix + [repo_name])
1851
1851
1852 @property
1852 @property
1853 def _config(self):
1853 def _config(self):
1854 """
1854 """
1855 Returns db based config object.
1855 Returns db based config object.
1856 """
1856 """
1857 from rhodecode.lib.utils import make_db_config
1857 from rhodecode.lib.utils import make_db_config
1858 return make_db_config(clear_session=False, repo=self)
1858 return make_db_config(clear_session=False, repo=self)
1859
1859
1860 def permissions(self, with_admins=True, with_owner=True):
1860 def permissions(self, with_admins=True, with_owner=True):
1861 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1861 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1862 q = q.options(joinedload(UserRepoToPerm.repository),
1862 q = q.options(joinedload(UserRepoToPerm.repository),
1863 joinedload(UserRepoToPerm.user),
1863 joinedload(UserRepoToPerm.user),
1864 joinedload(UserRepoToPerm.permission),)
1864 joinedload(UserRepoToPerm.permission),)
1865
1865
1866 # get owners and admins and permissions. We do a trick of re-writing
1866 # get owners and admins and permissions. We do a trick of re-writing
1867 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1867 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1868 # has a global reference and changing one object propagates to all
1868 # has a global reference and changing one object propagates to all
1869 # others. This means if admin is also an owner admin_row that change
1869 # others. This means if admin is also an owner admin_row that change
1870 # would propagate to both objects
1870 # would propagate to both objects
1871 perm_rows = []
1871 perm_rows = []
1872 for _usr in q.all():
1872 for _usr in q.all():
1873 usr = AttributeDict(_usr.user.get_dict())
1873 usr = AttributeDict(_usr.user.get_dict())
1874 usr.permission = _usr.permission.permission_name
1874 usr.permission = _usr.permission.permission_name
1875 perm_rows.append(usr)
1875 perm_rows.append(usr)
1876
1876
1877 # filter the perm rows by 'default' first and then sort them by
1877 # filter the perm rows by 'default' first and then sort them by
1878 # admin,write,read,none permissions sorted again alphabetically in
1878 # admin,write,read,none permissions sorted again alphabetically in
1879 # each group
1879 # each group
1880 perm_rows = sorted(perm_rows, key=display_user_sort)
1880 perm_rows = sorted(perm_rows, key=display_user_sort)
1881
1881
1882 _admin_perm = 'repository.admin'
1882 _admin_perm = 'repository.admin'
1883 owner_row = []
1883 owner_row = []
1884 if with_owner:
1884 if with_owner:
1885 usr = AttributeDict(self.user.get_dict())
1885 usr = AttributeDict(self.user.get_dict())
1886 usr.owner_row = True
1886 usr.owner_row = True
1887 usr.permission = _admin_perm
1887 usr.permission = _admin_perm
1888 owner_row.append(usr)
1888 owner_row.append(usr)
1889
1889
1890 super_admin_rows = []
1890 super_admin_rows = []
1891 if with_admins:
1891 if with_admins:
1892 for usr in User.get_all_super_admins():
1892 for usr in User.get_all_super_admins():
1893 # if this admin is also owner, don't double the record
1893 # if this admin is also owner, don't double the record
1894 if usr.user_id == owner_row[0].user_id:
1894 if usr.user_id == owner_row[0].user_id:
1895 owner_row[0].admin_row = True
1895 owner_row[0].admin_row = True
1896 else:
1896 else:
1897 usr = AttributeDict(usr.get_dict())
1897 usr = AttributeDict(usr.get_dict())
1898 usr.admin_row = True
1898 usr.admin_row = True
1899 usr.permission = _admin_perm
1899 usr.permission = _admin_perm
1900 super_admin_rows.append(usr)
1900 super_admin_rows.append(usr)
1901
1901
1902 return super_admin_rows + owner_row + perm_rows
1902 return super_admin_rows + owner_row + perm_rows
1903
1903
1904 def permission_user_groups(self):
1904 def permission_user_groups(self):
1905 q = UserGroupRepoToPerm.query().filter(
1905 q = UserGroupRepoToPerm.query().filter(
1906 UserGroupRepoToPerm.repository == self)
1906 UserGroupRepoToPerm.repository == self)
1907 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1907 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1908 joinedload(UserGroupRepoToPerm.users_group),
1908 joinedload(UserGroupRepoToPerm.users_group),
1909 joinedload(UserGroupRepoToPerm.permission),)
1909 joinedload(UserGroupRepoToPerm.permission),)
1910
1910
1911 perm_rows = []
1911 perm_rows = []
1912 for _user_group in q.all():
1912 for _user_group in q.all():
1913 usr = AttributeDict(_user_group.users_group.get_dict())
1913 usr = AttributeDict(_user_group.users_group.get_dict())
1914 usr.permission = _user_group.permission.permission_name
1914 usr.permission = _user_group.permission.permission_name
1915 perm_rows.append(usr)
1915 perm_rows.append(usr)
1916
1916
1917 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1917 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1918 return perm_rows
1918 return perm_rows
1919
1919
1920 def get_api_data(self, include_secrets=False):
1920 def get_api_data(self, include_secrets=False):
1921 """
1921 """
1922 Common function for generating repo api data
1922 Common function for generating repo api data
1923
1923
1924 :param include_secrets: See :meth:`User.get_api_data`.
1924 :param include_secrets: See :meth:`User.get_api_data`.
1925
1925
1926 """
1926 """
1927 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1927 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1928 # move this methods on models level.
1928 # move this methods on models level.
1929 from rhodecode.model.settings import SettingsModel
1929 from rhodecode.model.settings import SettingsModel
1930 from rhodecode.model.repo import RepoModel
1930 from rhodecode.model.repo import RepoModel
1931
1931
1932 repo = self
1932 repo = self
1933 _user_id, _time, _reason = self.locked
1933 _user_id, _time, _reason = self.locked
1934
1934
1935 data = {
1935 data = {
1936 'repo_id': repo.repo_id,
1936 'repo_id': repo.repo_id,
1937 'repo_name': repo.repo_name,
1937 'repo_name': repo.repo_name,
1938 'repo_type': repo.repo_type,
1938 'repo_type': repo.repo_type,
1939 'clone_uri': repo.clone_uri or '',
1939 'clone_uri': repo.clone_uri or '',
1940 'url': RepoModel().get_url(self),
1940 'url': RepoModel().get_url(self),
1941 'private': repo.private,
1941 'private': repo.private,
1942 'created_on': repo.created_on,
1942 'created_on': repo.created_on,
1943 'description': repo.description_safe,
1943 'description': repo.description_safe,
1944 'landing_rev': repo.landing_rev,
1944 'landing_rev': repo.landing_rev,
1945 'owner': repo.user.username,
1945 'owner': repo.user.username,
1946 'fork_of': repo.fork.repo_name if repo.fork else None,
1946 'fork_of': repo.fork.repo_name if repo.fork else None,
1947 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1947 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1948 'enable_statistics': repo.enable_statistics,
1948 'enable_statistics': repo.enable_statistics,
1949 'enable_locking': repo.enable_locking,
1949 'enable_locking': repo.enable_locking,
1950 'enable_downloads': repo.enable_downloads,
1950 'enable_downloads': repo.enable_downloads,
1951 'last_changeset': repo.changeset_cache,
1951 'last_changeset': repo.changeset_cache,
1952 'locked_by': User.get(_user_id).get_api_data(
1952 'locked_by': User.get(_user_id).get_api_data(
1953 include_secrets=include_secrets) if _user_id else None,
1953 include_secrets=include_secrets) if _user_id else None,
1954 'locked_date': time_to_datetime(_time) if _time else None,
1954 'locked_date': time_to_datetime(_time) if _time else None,
1955 'lock_reason': _reason if _reason else None,
1955 'lock_reason': _reason if _reason else None,
1956 }
1956 }
1957
1957
1958 # TODO: mikhail: should be per-repo settings here
1958 # TODO: mikhail: should be per-repo settings here
1959 rc_config = SettingsModel().get_all_settings()
1959 rc_config = SettingsModel().get_all_settings()
1960 repository_fields = str2bool(
1960 repository_fields = str2bool(
1961 rc_config.get('rhodecode_repository_fields'))
1961 rc_config.get('rhodecode_repository_fields'))
1962 if repository_fields:
1962 if repository_fields:
1963 for f in self.extra_fields:
1963 for f in self.extra_fields:
1964 data[f.field_key_prefixed] = f.field_value
1964 data[f.field_key_prefixed] = f.field_value
1965
1965
1966 return data
1966 return data
1967
1967
1968 @classmethod
1968 @classmethod
1969 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1969 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1970 if not lock_time:
1970 if not lock_time:
1971 lock_time = time.time()
1971 lock_time = time.time()
1972 if not lock_reason:
1972 if not lock_reason:
1973 lock_reason = cls.LOCK_AUTOMATIC
1973 lock_reason = cls.LOCK_AUTOMATIC
1974 repo.locked = [user_id, lock_time, lock_reason]
1974 repo.locked = [user_id, lock_time, lock_reason]
1975 Session().add(repo)
1975 Session().add(repo)
1976 Session().commit()
1976 Session().commit()
1977
1977
1978 @classmethod
1978 @classmethod
1979 def unlock(cls, repo):
1979 def unlock(cls, repo):
1980 repo.locked = None
1980 repo.locked = None
1981 Session().add(repo)
1981 Session().add(repo)
1982 Session().commit()
1982 Session().commit()
1983
1983
1984 @classmethod
1984 @classmethod
1985 def getlock(cls, repo):
1985 def getlock(cls, repo):
1986 return repo.locked
1986 return repo.locked
1987
1987
1988 def is_user_lock(self, user_id):
1988 def is_user_lock(self, user_id):
1989 if self.lock[0]:
1989 if self.lock[0]:
1990 lock_user_id = safe_int(self.lock[0])
1990 lock_user_id = safe_int(self.lock[0])
1991 user_id = safe_int(user_id)
1991 user_id = safe_int(user_id)
1992 # both are ints, and they are equal
1992 # both are ints, and they are equal
1993 return all([lock_user_id, user_id]) and lock_user_id == user_id
1993 return all([lock_user_id, user_id]) and lock_user_id == user_id
1994
1994
1995 return False
1995 return False
1996
1996
1997 def get_locking_state(self, action, user_id, only_when_enabled=True):
1997 def get_locking_state(self, action, user_id, only_when_enabled=True):
1998 """
1998 """
1999 Checks locking on this repository, if locking is enabled and lock is
1999 Checks locking on this repository, if locking is enabled and lock is
2000 present returns a tuple of make_lock, locked, locked_by.
2000 present returns a tuple of make_lock, locked, locked_by.
2001 make_lock can have 3 states None (do nothing) True, make lock
2001 make_lock can have 3 states None (do nothing) True, make lock
2002 False release lock, This value is later propagated to hooks, which
2002 False release lock, This value is later propagated to hooks, which
2003 do the locking. Think about this as signals passed to hooks what to do.
2003 do the locking. Think about this as signals passed to hooks what to do.
2004
2004
2005 """
2005 """
2006 # TODO: johbo: This is part of the business logic and should be moved
2006 # TODO: johbo: This is part of the business logic and should be moved
2007 # into the RepositoryModel.
2007 # into the RepositoryModel.
2008
2008
2009 if action not in ('push', 'pull'):
2009 if action not in ('push', 'pull'):
2010 raise ValueError("Invalid action value: %s" % repr(action))
2010 raise ValueError("Invalid action value: %s" % repr(action))
2011
2011
2012 # defines if locked error should be thrown to user
2012 # defines if locked error should be thrown to user
2013 currently_locked = False
2013 currently_locked = False
2014 # defines if new lock should be made, tri-state
2014 # defines if new lock should be made, tri-state
2015 make_lock = None
2015 make_lock = None
2016 repo = self
2016 repo = self
2017 user = User.get(user_id)
2017 user = User.get(user_id)
2018
2018
2019 lock_info = repo.locked
2019 lock_info = repo.locked
2020
2020
2021 if repo and (repo.enable_locking or not only_when_enabled):
2021 if repo and (repo.enable_locking or not only_when_enabled):
2022 if action == 'push':
2022 if action == 'push':
2023 # check if it's already locked !, if it is compare users
2023 # check if it's already locked !, if it is compare users
2024 locked_by_user_id = lock_info[0]
2024 locked_by_user_id = lock_info[0]
2025 if user.user_id == locked_by_user_id:
2025 if user.user_id == locked_by_user_id:
2026 log.debug(
2026 log.debug(
2027 'Got `push` action from user %s, now unlocking', user)
2027 'Got `push` action from user %s, now unlocking', user)
2028 # unlock if we have push from user who locked
2028 # unlock if we have push from user who locked
2029 make_lock = False
2029 make_lock = False
2030 else:
2030 else:
2031 # we're not the same user who locked, ban with
2031 # we're not the same user who locked, ban with
2032 # code defined in settings (default is 423 HTTP Locked) !
2032 # code defined in settings (default is 423 HTTP Locked) !
2033 log.debug('Repo %s is currently locked by %s', repo, user)
2033 log.debug('Repo %s is currently locked by %s', repo, user)
2034 currently_locked = True
2034 currently_locked = True
2035 elif action == 'pull':
2035 elif action == 'pull':
2036 # [0] user [1] date
2036 # [0] user [1] date
2037 if lock_info[0] and lock_info[1]:
2037 if lock_info[0] and lock_info[1]:
2038 log.debug('Repo %s is currently locked by %s', repo, user)
2038 log.debug('Repo %s is currently locked by %s', repo, user)
2039 currently_locked = True
2039 currently_locked = True
2040 else:
2040 else:
2041 log.debug('Setting lock on repo %s by %s', repo, user)
2041 log.debug('Setting lock on repo %s by %s', repo, user)
2042 make_lock = True
2042 make_lock = True
2043
2043
2044 else:
2044 else:
2045 log.debug('Repository %s do not have locking enabled', repo)
2045 log.debug('Repository %s do not have locking enabled', repo)
2046
2046
2047 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2047 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2048 make_lock, currently_locked, lock_info)
2048 make_lock, currently_locked, lock_info)
2049
2049
2050 from rhodecode.lib.auth import HasRepoPermissionAny
2050 from rhodecode.lib.auth import HasRepoPermissionAny
2051 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2051 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2052 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2052 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2053 # if we don't have at least write permission we cannot make a lock
2053 # if we don't have at least write permission we cannot make a lock
2054 log.debug('lock state reset back to FALSE due to lack '
2054 log.debug('lock state reset back to FALSE due to lack '
2055 'of at least read permission')
2055 'of at least read permission')
2056 make_lock = False
2056 make_lock = False
2057
2057
2058 return make_lock, currently_locked, lock_info
2058 return make_lock, currently_locked, lock_info
2059
2059
2060 @property
2060 @property
2061 def last_db_change(self):
2061 def last_db_change(self):
2062 return self.updated_on
2062 return self.updated_on
2063
2063
2064 @property
2064 @property
2065 def clone_uri_hidden(self):
2065 def clone_uri_hidden(self):
2066 clone_uri = self.clone_uri
2066 clone_uri = self.clone_uri
2067 if clone_uri:
2067 if clone_uri:
2068 import urlobject
2068 import urlobject
2069 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2069 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2070 if url_obj.password:
2070 if url_obj.password:
2071 clone_uri = url_obj.with_password('*****')
2071 clone_uri = url_obj.with_password('*****')
2072 return clone_uri
2072 return clone_uri
2073
2073
2074 def clone_url(self, **override):
2074 def clone_url(self, **override):
2075 from rhodecode.model.settings import SettingsModel
2075 from rhodecode.model.settings import SettingsModel
2076
2076
2077 uri_tmpl = None
2077 uri_tmpl = None
2078 if 'with_id' in override:
2078 if 'with_id' in override:
2079 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2079 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2080 del override['with_id']
2080 del override['with_id']
2081
2081
2082 if 'uri_tmpl' in override:
2082 if 'uri_tmpl' in override:
2083 uri_tmpl = override['uri_tmpl']
2083 uri_tmpl = override['uri_tmpl']
2084 del override['uri_tmpl']
2084 del override['uri_tmpl']
2085
2085
2086 # we didn't override our tmpl from **overrides
2086 # we didn't override our tmpl from **overrides
2087 if not uri_tmpl:
2087 if not uri_tmpl:
2088 rc_config = SettingsModel().get_all_settings(cache=True)
2088 rc_config = SettingsModel().get_all_settings(cache=True)
2089 uri_tmpl = rc_config.get(
2089 uri_tmpl = rc_config.get(
2090 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2090 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2091
2091
2092 request = get_current_request()
2092 request = get_current_request()
2093 return get_clone_url(request=request,
2093 return get_clone_url(request=request,
2094 uri_tmpl=uri_tmpl,
2094 uri_tmpl=uri_tmpl,
2095 repo_name=self.repo_name,
2095 repo_name=self.repo_name,
2096 repo_id=self.repo_id, **override)
2096 repo_id=self.repo_id, **override)
2097
2097
2098 def set_state(self, state):
2098 def set_state(self, state):
2099 self.repo_state = state
2099 self.repo_state = state
2100 Session().add(self)
2100 Session().add(self)
2101 #==========================================================================
2101 #==========================================================================
2102 # SCM PROPERTIES
2102 # SCM PROPERTIES
2103 #==========================================================================
2103 #==========================================================================
2104
2104
2105 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2105 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2106 return get_commit_safe(
2106 return get_commit_safe(
2107 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2107 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2108
2108
2109 def get_changeset(self, rev=None, pre_load=None):
2109 def get_changeset(self, rev=None, pre_load=None):
2110 warnings.warn("Use get_commit", DeprecationWarning)
2110 warnings.warn("Use get_commit", DeprecationWarning)
2111 commit_id = None
2111 commit_id = None
2112 commit_idx = None
2112 commit_idx = None
2113 if isinstance(rev, basestring):
2113 if isinstance(rev, basestring):
2114 commit_id = rev
2114 commit_id = rev
2115 else:
2115 else:
2116 commit_idx = rev
2116 commit_idx = rev
2117 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2117 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2118 pre_load=pre_load)
2118 pre_load=pre_load)
2119
2119
2120 def get_landing_commit(self):
2120 def get_landing_commit(self):
2121 """
2121 """
2122 Returns landing commit, or if that doesn't exist returns the tip
2122 Returns landing commit, or if that doesn't exist returns the tip
2123 """
2123 """
2124 _rev_type, _rev = self.landing_rev
2124 _rev_type, _rev = self.landing_rev
2125 commit = self.get_commit(_rev)
2125 commit = self.get_commit(_rev)
2126 if isinstance(commit, EmptyCommit):
2126 if isinstance(commit, EmptyCommit):
2127 return self.get_commit()
2127 return self.get_commit()
2128 return commit
2128 return commit
2129
2129
2130 def update_commit_cache(self, cs_cache=None, config=None):
2130 def update_commit_cache(self, cs_cache=None, config=None):
2131 """
2131 """
2132 Update cache of last changeset for repository, keys should be::
2132 Update cache of last changeset for repository, keys should be::
2133
2133
2134 short_id
2134 short_id
2135 raw_id
2135 raw_id
2136 revision
2136 revision
2137 parents
2137 parents
2138 message
2138 message
2139 date
2139 date
2140 author
2140 author
2141
2141
2142 :param cs_cache:
2142 :param cs_cache:
2143 """
2143 """
2144 from rhodecode.lib.vcs.backends.base import BaseChangeset
2144 from rhodecode.lib.vcs.backends.base import BaseChangeset
2145 if cs_cache is None:
2145 if cs_cache is None:
2146 # use no-cache version here
2146 # use no-cache version here
2147 scm_repo = self.scm_instance(cache=False, config=config)
2147 scm_repo = self.scm_instance(cache=False, config=config)
2148 if scm_repo:
2148 if scm_repo:
2149 cs_cache = scm_repo.get_commit(
2149 cs_cache = scm_repo.get_commit(
2150 pre_load=["author", "date", "message", "parents"])
2150 pre_load=["author", "date", "message", "parents"])
2151 else:
2151 else:
2152 cs_cache = EmptyCommit()
2152 cs_cache = EmptyCommit()
2153
2153
2154 if isinstance(cs_cache, BaseChangeset):
2154 if isinstance(cs_cache, BaseChangeset):
2155 cs_cache = cs_cache.__json__()
2155 cs_cache = cs_cache.__json__()
2156
2156
2157 def is_outdated(new_cs_cache):
2157 def is_outdated(new_cs_cache):
2158 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2158 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2159 new_cs_cache['revision'] != self.changeset_cache['revision']):
2159 new_cs_cache['revision'] != self.changeset_cache['revision']):
2160 return True
2160 return True
2161 return False
2161 return False
2162
2162
2163 # check if we have maybe already latest cached revision
2163 # check if we have maybe already latest cached revision
2164 if is_outdated(cs_cache) or not self.changeset_cache:
2164 if is_outdated(cs_cache) or not self.changeset_cache:
2165 _default = datetime.datetime.fromtimestamp(0)
2165 _default = datetime.datetime.fromtimestamp(0)
2166 last_change = cs_cache.get('date') or _default
2166 last_change = cs_cache.get('date') or _default
2167 log.debug('updated repo %s with new cs cache %s',
2167 log.debug('updated repo %s with new cs cache %s',
2168 self.repo_name, cs_cache)
2168 self.repo_name, cs_cache)
2169 self.updated_on = last_change
2169 self.updated_on = last_change
2170 self.changeset_cache = cs_cache
2170 self.changeset_cache = cs_cache
2171 Session().add(self)
2171 Session().add(self)
2172 Session().commit()
2172 Session().commit()
2173 else:
2173 else:
2174 log.debug('Skipping update_commit_cache for repo:`%s` '
2174 log.debug('Skipping update_commit_cache for repo:`%s` '
2175 'commit already with latest changes', self.repo_name)
2175 'commit already with latest changes', self.repo_name)
2176
2176
2177 @property
2177 @property
2178 def tip(self):
2178 def tip(self):
2179 return self.get_commit('tip')
2179 return self.get_commit('tip')
2180
2180
2181 @property
2181 @property
2182 def author(self):
2182 def author(self):
2183 return self.tip.author
2183 return self.tip.author
2184
2184
2185 @property
2185 @property
2186 def last_change(self):
2186 def last_change(self):
2187 return self.scm_instance().last_change
2187 return self.scm_instance().last_change
2188
2188
2189 def get_comments(self, revisions=None):
2189 def get_comments(self, revisions=None):
2190 """
2190 """
2191 Returns comments for this repository grouped by revisions
2191 Returns comments for this repository grouped by revisions
2192
2192
2193 :param revisions: filter query by revisions only
2193 :param revisions: filter query by revisions only
2194 """
2194 """
2195 cmts = ChangesetComment.query()\
2195 cmts = ChangesetComment.query()\
2196 .filter(ChangesetComment.repo == self)
2196 .filter(ChangesetComment.repo == self)
2197 if revisions:
2197 if revisions:
2198 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2198 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2199 grouped = collections.defaultdict(list)
2199 grouped = collections.defaultdict(list)
2200 for cmt in cmts.all():
2200 for cmt in cmts.all():
2201 grouped[cmt.revision].append(cmt)
2201 grouped[cmt.revision].append(cmt)
2202 return grouped
2202 return grouped
2203
2203
2204 def statuses(self, revisions=None):
2204 def statuses(self, revisions=None):
2205 """
2205 """
2206 Returns statuses for this repository
2206 Returns statuses for this repository
2207
2207
2208 :param revisions: list of revisions to get statuses for
2208 :param revisions: list of revisions to get statuses for
2209 """
2209 """
2210 statuses = ChangesetStatus.query()\
2210 statuses = ChangesetStatus.query()\
2211 .filter(ChangesetStatus.repo == self)\
2211 .filter(ChangesetStatus.repo == self)\
2212 .filter(ChangesetStatus.version == 0)
2212 .filter(ChangesetStatus.version == 0)
2213
2213
2214 if revisions:
2214 if revisions:
2215 # Try doing the filtering in chunks to avoid hitting limits
2215 # Try doing the filtering in chunks to avoid hitting limits
2216 size = 500
2216 size = 500
2217 status_results = []
2217 status_results = []
2218 for chunk in xrange(0, len(revisions), size):
2218 for chunk in xrange(0, len(revisions), size):
2219 status_results += statuses.filter(
2219 status_results += statuses.filter(
2220 ChangesetStatus.revision.in_(
2220 ChangesetStatus.revision.in_(
2221 revisions[chunk: chunk+size])
2221 revisions[chunk: chunk+size])
2222 ).all()
2222 ).all()
2223 else:
2223 else:
2224 status_results = statuses.all()
2224 status_results = statuses.all()
2225
2225
2226 grouped = {}
2226 grouped = {}
2227
2227
2228 # maybe we have open new pullrequest without a status?
2228 # maybe we have open new pullrequest without a status?
2229 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2229 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2230 status_lbl = ChangesetStatus.get_status_lbl(stat)
2230 status_lbl = ChangesetStatus.get_status_lbl(stat)
2231 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2231 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2232 for rev in pr.revisions:
2232 for rev in pr.revisions:
2233 pr_id = pr.pull_request_id
2233 pr_id = pr.pull_request_id
2234 pr_repo = pr.target_repo.repo_name
2234 pr_repo = pr.target_repo.repo_name
2235 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2235 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2236
2236
2237 for stat in status_results:
2237 for stat in status_results:
2238 pr_id = pr_repo = None
2238 pr_id = pr_repo = None
2239 if stat.pull_request:
2239 if stat.pull_request:
2240 pr_id = stat.pull_request.pull_request_id
2240 pr_id = stat.pull_request.pull_request_id
2241 pr_repo = stat.pull_request.target_repo.repo_name
2241 pr_repo = stat.pull_request.target_repo.repo_name
2242 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2242 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2243 pr_id, pr_repo]
2243 pr_id, pr_repo]
2244 return grouped
2244 return grouped
2245
2245
2246 # ==========================================================================
2246 # ==========================================================================
2247 # SCM CACHE INSTANCE
2247 # SCM CACHE INSTANCE
2248 # ==========================================================================
2248 # ==========================================================================
2249
2249
2250 def scm_instance(self, **kwargs):
2250 def scm_instance(self, **kwargs):
2251 import rhodecode
2251 import rhodecode
2252
2252
2253 # Passing a config will not hit the cache currently only used
2253 # Passing a config will not hit the cache currently only used
2254 # for repo2dbmapper
2254 # for repo2dbmapper
2255 config = kwargs.pop('config', None)
2255 config = kwargs.pop('config', None)
2256 cache = kwargs.pop('cache', None)
2256 cache = kwargs.pop('cache', None)
2257 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2257 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2258 # if cache is NOT defined use default global, else we have a full
2258 # if cache is NOT defined use default global, else we have a full
2259 # control over cache behaviour
2259 # control over cache behaviour
2260 if cache is None and full_cache and not config:
2260 if cache is None and full_cache and not config:
2261 return self._get_instance_cached()
2261 return self._get_instance_cached()
2262 return self._get_instance(cache=bool(cache), config=config)
2262 return self._get_instance(cache=bool(cache), config=config)
2263
2263
2264 def _get_instance_cached(self):
2264 def _get_instance_cached(self):
2265 @cache_region('long_term')
2265 self._get_instance()
2266 def _get_repo(cache_key):
2267 return self._get_instance()
2268
2269 invalidator_context = CacheKey.repo_context_cache(
2270 _get_repo, self.repo_name, None, thread_scoped=True)
2271
2272 with invalidator_context as context:
2273 context.invalidate()
2274 repo = context.compute()
2275
2276 return repo
2277
2266
2278 def _get_instance(self, cache=True, config=None):
2267 def _get_instance(self, cache=True, config=None):
2279 config = config or self._config
2268 config = config or self._config
2280 custom_wire = {
2269 custom_wire = {
2281 'cache': cache # controls the vcs.remote cache
2270 'cache': cache # controls the vcs.remote cache
2282 }
2271 }
2283 repo = get_vcs_instance(
2272 repo = get_vcs_instance(
2284 repo_path=safe_str(self.repo_full_path),
2273 repo_path=safe_str(self.repo_full_path),
2285 config=config,
2274 config=config,
2286 with_wire=custom_wire,
2275 with_wire=custom_wire,
2287 create=False,
2276 create=False,
2288 _vcs_alias=self.repo_type)
2277 _vcs_alias=self.repo_type)
2289
2278
2290 return repo
2279 return repo
2291
2280
2292 def __json__(self):
2281 def __json__(self):
2293 return {'landing_rev': self.landing_rev}
2282 return {'landing_rev': self.landing_rev}
2294
2283
2295 def get_dict(self):
2284 def get_dict(self):
2296
2285
2297 # Since we transformed `repo_name` to a hybrid property, we need to
2286 # Since we transformed `repo_name` to a hybrid property, we need to
2298 # keep compatibility with the code which uses `repo_name` field.
2287 # keep compatibility with the code which uses `repo_name` field.
2299
2288
2300 result = super(Repository, self).get_dict()
2289 result = super(Repository, self).get_dict()
2301 result['repo_name'] = result.pop('_repo_name', None)
2290 result['repo_name'] = result.pop('_repo_name', None)
2302 return result
2291 return result
2303
2292
2304
2293
2305 class RepoGroup(Base, BaseModel):
2294 class RepoGroup(Base, BaseModel):
2306 __tablename__ = 'groups'
2295 __tablename__ = 'groups'
2307 __table_args__ = (
2296 __table_args__ = (
2308 UniqueConstraint('group_name', 'group_parent_id'),
2297 UniqueConstraint('group_name', 'group_parent_id'),
2309 CheckConstraint('group_id != group_parent_id'),
2298 CheckConstraint('group_id != group_parent_id'),
2310 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2299 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2311 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2300 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2312 )
2301 )
2313 __mapper_args__ = {'order_by': 'group_name'}
2302 __mapper_args__ = {'order_by': 'group_name'}
2314
2303
2315 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2304 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2316
2305
2317 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2306 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2318 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2307 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2319 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2308 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2320 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2309 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2321 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2310 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2322 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2323 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2312 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2324 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2313 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2325 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2314 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2326
2315
2327 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2316 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2328 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2317 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2329 parent_group = relationship('RepoGroup', remote_side=group_id)
2318 parent_group = relationship('RepoGroup', remote_side=group_id)
2330 user = relationship('User')
2319 user = relationship('User')
2331 integrations = relationship('Integration',
2320 integrations = relationship('Integration',
2332 cascade="all, delete, delete-orphan")
2321 cascade="all, delete, delete-orphan")
2333
2322
2334 def __init__(self, group_name='', parent_group=None):
2323 def __init__(self, group_name='', parent_group=None):
2335 self.group_name = group_name
2324 self.group_name = group_name
2336 self.parent_group = parent_group
2325 self.parent_group = parent_group
2337
2326
2338 def __unicode__(self):
2327 def __unicode__(self):
2339 return u"<%s('id:%s:%s')>" % (
2328 return u"<%s('id:%s:%s')>" % (
2340 self.__class__.__name__, self.group_id, self.group_name)
2329 self.__class__.__name__, self.group_id, self.group_name)
2341
2330
2342 @hybrid_property
2331 @hybrid_property
2343 def description_safe(self):
2332 def description_safe(self):
2344 from rhodecode.lib import helpers as h
2333 from rhodecode.lib import helpers as h
2345 return h.escape(self.group_description)
2334 return h.escape(self.group_description)
2346
2335
2347 @classmethod
2336 @classmethod
2348 def _generate_choice(cls, repo_group):
2337 def _generate_choice(cls, repo_group):
2349 from webhelpers.html import literal as _literal
2338 from webhelpers.html import literal as _literal
2350 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2339 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2351 return repo_group.group_id, _name(repo_group.full_path_splitted)
2340 return repo_group.group_id, _name(repo_group.full_path_splitted)
2352
2341
2353 @classmethod
2342 @classmethod
2354 def groups_choices(cls, groups=None, show_empty_group=True):
2343 def groups_choices(cls, groups=None, show_empty_group=True):
2355 if not groups:
2344 if not groups:
2356 groups = cls.query().all()
2345 groups = cls.query().all()
2357
2346
2358 repo_groups = []
2347 repo_groups = []
2359 if show_empty_group:
2348 if show_empty_group:
2360 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2349 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2361
2350
2362 repo_groups.extend([cls._generate_choice(x) for x in groups])
2351 repo_groups.extend([cls._generate_choice(x) for x in groups])
2363
2352
2364 repo_groups = sorted(
2353 repo_groups = sorted(
2365 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2354 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2366 return repo_groups
2355 return repo_groups
2367
2356
2368 @classmethod
2357 @classmethod
2369 def url_sep(cls):
2358 def url_sep(cls):
2370 return URL_SEP
2359 return URL_SEP
2371
2360
2372 @classmethod
2361 @classmethod
2373 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2362 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2374 if case_insensitive:
2363 if case_insensitive:
2375 gr = cls.query().filter(func.lower(cls.group_name)
2364 gr = cls.query().filter(func.lower(cls.group_name)
2376 == func.lower(group_name))
2365 == func.lower(group_name))
2377 else:
2366 else:
2378 gr = cls.query().filter(cls.group_name == group_name)
2367 gr = cls.query().filter(cls.group_name == group_name)
2379 if cache:
2368 if cache:
2380 name_key = _hash_key(group_name)
2369 name_key = _hash_key(group_name)
2381 gr = gr.options(
2370 gr = gr.options(
2382 FromCache("sql_cache_short", "get_group_%s" % name_key))
2371 FromCache("sql_cache_short", "get_group_%s" % name_key))
2383 return gr.scalar()
2372 return gr.scalar()
2384
2373
2385 @classmethod
2374 @classmethod
2386 def get_user_personal_repo_group(cls, user_id):
2375 def get_user_personal_repo_group(cls, user_id):
2387 user = User.get(user_id)
2376 user = User.get(user_id)
2388 if user.username == User.DEFAULT_USER:
2377 if user.username == User.DEFAULT_USER:
2389 return None
2378 return None
2390
2379
2391 return cls.query()\
2380 return cls.query()\
2392 .filter(cls.personal == true()) \
2381 .filter(cls.personal == true()) \
2393 .filter(cls.user == user).scalar()
2382 .filter(cls.user == user).scalar()
2394
2383
2395 @classmethod
2384 @classmethod
2396 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2385 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2397 case_insensitive=True):
2386 case_insensitive=True):
2398 q = RepoGroup.query()
2387 q = RepoGroup.query()
2399
2388
2400 if not isinstance(user_id, Optional):
2389 if not isinstance(user_id, Optional):
2401 q = q.filter(RepoGroup.user_id == user_id)
2390 q = q.filter(RepoGroup.user_id == user_id)
2402
2391
2403 if not isinstance(group_id, Optional):
2392 if not isinstance(group_id, Optional):
2404 q = q.filter(RepoGroup.group_parent_id == group_id)
2393 q = q.filter(RepoGroup.group_parent_id == group_id)
2405
2394
2406 if case_insensitive:
2395 if case_insensitive:
2407 q = q.order_by(func.lower(RepoGroup.group_name))
2396 q = q.order_by(func.lower(RepoGroup.group_name))
2408 else:
2397 else:
2409 q = q.order_by(RepoGroup.group_name)
2398 q = q.order_by(RepoGroup.group_name)
2410 return q.all()
2399 return q.all()
2411
2400
2412 @property
2401 @property
2413 def parents(self):
2402 def parents(self):
2414 parents_recursion_limit = 10
2403 parents_recursion_limit = 10
2415 groups = []
2404 groups = []
2416 if self.parent_group is None:
2405 if self.parent_group is None:
2417 return groups
2406 return groups
2418 cur_gr = self.parent_group
2407 cur_gr = self.parent_group
2419 groups.insert(0, cur_gr)
2408 groups.insert(0, cur_gr)
2420 cnt = 0
2409 cnt = 0
2421 while 1:
2410 while 1:
2422 cnt += 1
2411 cnt += 1
2423 gr = getattr(cur_gr, 'parent_group', None)
2412 gr = getattr(cur_gr, 'parent_group', None)
2424 cur_gr = cur_gr.parent_group
2413 cur_gr = cur_gr.parent_group
2425 if gr is None:
2414 if gr is None:
2426 break
2415 break
2427 if cnt == parents_recursion_limit:
2416 if cnt == parents_recursion_limit:
2428 # this will prevent accidental infinit loops
2417 # this will prevent accidental infinit loops
2429 log.error(('more than %s parents found for group %s, stopping '
2418 log.error(('more than %s parents found for group %s, stopping '
2430 'recursive parent fetching' % (parents_recursion_limit, self)))
2419 'recursive parent fetching' % (parents_recursion_limit, self)))
2431 break
2420 break
2432
2421
2433 groups.insert(0, gr)
2422 groups.insert(0, gr)
2434 return groups
2423 return groups
2435
2424
2436 @property
2425 @property
2437 def last_db_change(self):
2426 def last_db_change(self):
2438 return self.updated_on
2427 return self.updated_on
2439
2428
2440 @property
2429 @property
2441 def children(self):
2430 def children(self):
2442 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2431 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2443
2432
2444 @property
2433 @property
2445 def name(self):
2434 def name(self):
2446 return self.group_name.split(RepoGroup.url_sep())[-1]
2435 return self.group_name.split(RepoGroup.url_sep())[-1]
2447
2436
2448 @property
2437 @property
2449 def full_path(self):
2438 def full_path(self):
2450 return self.group_name
2439 return self.group_name
2451
2440
2452 @property
2441 @property
2453 def full_path_splitted(self):
2442 def full_path_splitted(self):
2454 return self.group_name.split(RepoGroup.url_sep())
2443 return self.group_name.split(RepoGroup.url_sep())
2455
2444
2456 @property
2445 @property
2457 def repositories(self):
2446 def repositories(self):
2458 return Repository.query()\
2447 return Repository.query()\
2459 .filter(Repository.group == self)\
2448 .filter(Repository.group == self)\
2460 .order_by(Repository.repo_name)
2449 .order_by(Repository.repo_name)
2461
2450
2462 @property
2451 @property
2463 def repositories_recursive_count(self):
2452 def repositories_recursive_count(self):
2464 cnt = self.repositories.count()
2453 cnt = self.repositories.count()
2465
2454
2466 def children_count(group):
2455 def children_count(group):
2467 cnt = 0
2456 cnt = 0
2468 for child in group.children:
2457 for child in group.children:
2469 cnt += child.repositories.count()
2458 cnt += child.repositories.count()
2470 cnt += children_count(child)
2459 cnt += children_count(child)
2471 return cnt
2460 return cnt
2472
2461
2473 return cnt + children_count(self)
2462 return cnt + children_count(self)
2474
2463
2475 def _recursive_objects(self, include_repos=True):
2464 def _recursive_objects(self, include_repos=True):
2476 all_ = []
2465 all_ = []
2477
2466
2478 def _get_members(root_gr):
2467 def _get_members(root_gr):
2479 if include_repos:
2468 if include_repos:
2480 for r in root_gr.repositories:
2469 for r in root_gr.repositories:
2481 all_.append(r)
2470 all_.append(r)
2482 childs = root_gr.children.all()
2471 childs = root_gr.children.all()
2483 if childs:
2472 if childs:
2484 for gr in childs:
2473 for gr in childs:
2485 all_.append(gr)
2474 all_.append(gr)
2486 _get_members(gr)
2475 _get_members(gr)
2487
2476
2488 _get_members(self)
2477 _get_members(self)
2489 return [self] + all_
2478 return [self] + all_
2490
2479
2491 def recursive_groups_and_repos(self):
2480 def recursive_groups_and_repos(self):
2492 """
2481 """
2493 Recursive return all groups, with repositories in those groups
2482 Recursive return all groups, with repositories in those groups
2494 """
2483 """
2495 return self._recursive_objects()
2484 return self._recursive_objects()
2496
2485
2497 def recursive_groups(self):
2486 def recursive_groups(self):
2498 """
2487 """
2499 Returns all children groups for this group including children of children
2488 Returns all children groups for this group including children of children
2500 """
2489 """
2501 return self._recursive_objects(include_repos=False)
2490 return self._recursive_objects(include_repos=False)
2502
2491
2503 def get_new_name(self, group_name):
2492 def get_new_name(self, group_name):
2504 """
2493 """
2505 returns new full group name based on parent and new name
2494 returns new full group name based on parent and new name
2506
2495
2507 :param group_name:
2496 :param group_name:
2508 """
2497 """
2509 path_prefix = (self.parent_group.full_path_splitted if
2498 path_prefix = (self.parent_group.full_path_splitted if
2510 self.parent_group else [])
2499 self.parent_group else [])
2511 return RepoGroup.url_sep().join(path_prefix + [group_name])
2500 return RepoGroup.url_sep().join(path_prefix + [group_name])
2512
2501
2513 def permissions(self, with_admins=True, with_owner=True):
2502 def permissions(self, with_admins=True, with_owner=True):
2514 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2503 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2515 q = q.options(joinedload(UserRepoGroupToPerm.group),
2504 q = q.options(joinedload(UserRepoGroupToPerm.group),
2516 joinedload(UserRepoGroupToPerm.user),
2505 joinedload(UserRepoGroupToPerm.user),
2517 joinedload(UserRepoGroupToPerm.permission),)
2506 joinedload(UserRepoGroupToPerm.permission),)
2518
2507
2519 # get owners and admins and permissions. We do a trick of re-writing
2508 # get owners and admins and permissions. We do a trick of re-writing
2520 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2509 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2521 # has a global reference and changing one object propagates to all
2510 # has a global reference and changing one object propagates to all
2522 # others. This means if admin is also an owner admin_row that change
2511 # others. This means if admin is also an owner admin_row that change
2523 # would propagate to both objects
2512 # would propagate to both objects
2524 perm_rows = []
2513 perm_rows = []
2525 for _usr in q.all():
2514 for _usr in q.all():
2526 usr = AttributeDict(_usr.user.get_dict())
2515 usr = AttributeDict(_usr.user.get_dict())
2527 usr.permission = _usr.permission.permission_name
2516 usr.permission = _usr.permission.permission_name
2528 perm_rows.append(usr)
2517 perm_rows.append(usr)
2529
2518
2530 # filter the perm rows by 'default' first and then sort them by
2519 # filter the perm rows by 'default' first and then sort them by
2531 # admin,write,read,none permissions sorted again alphabetically in
2520 # admin,write,read,none permissions sorted again alphabetically in
2532 # each group
2521 # each group
2533 perm_rows = sorted(perm_rows, key=display_user_sort)
2522 perm_rows = sorted(perm_rows, key=display_user_sort)
2534
2523
2535 _admin_perm = 'group.admin'
2524 _admin_perm = 'group.admin'
2536 owner_row = []
2525 owner_row = []
2537 if with_owner:
2526 if with_owner:
2538 usr = AttributeDict(self.user.get_dict())
2527 usr = AttributeDict(self.user.get_dict())
2539 usr.owner_row = True
2528 usr.owner_row = True
2540 usr.permission = _admin_perm
2529 usr.permission = _admin_perm
2541 owner_row.append(usr)
2530 owner_row.append(usr)
2542
2531
2543 super_admin_rows = []
2532 super_admin_rows = []
2544 if with_admins:
2533 if with_admins:
2545 for usr in User.get_all_super_admins():
2534 for usr in User.get_all_super_admins():
2546 # if this admin is also owner, don't double the record
2535 # if this admin is also owner, don't double the record
2547 if usr.user_id == owner_row[0].user_id:
2536 if usr.user_id == owner_row[0].user_id:
2548 owner_row[0].admin_row = True
2537 owner_row[0].admin_row = True
2549 else:
2538 else:
2550 usr = AttributeDict(usr.get_dict())
2539 usr = AttributeDict(usr.get_dict())
2551 usr.admin_row = True
2540 usr.admin_row = True
2552 usr.permission = _admin_perm
2541 usr.permission = _admin_perm
2553 super_admin_rows.append(usr)
2542 super_admin_rows.append(usr)
2554
2543
2555 return super_admin_rows + owner_row + perm_rows
2544 return super_admin_rows + owner_row + perm_rows
2556
2545
2557 def permission_user_groups(self):
2546 def permission_user_groups(self):
2558 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2547 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2559 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2548 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2560 joinedload(UserGroupRepoGroupToPerm.users_group),
2549 joinedload(UserGroupRepoGroupToPerm.users_group),
2561 joinedload(UserGroupRepoGroupToPerm.permission),)
2550 joinedload(UserGroupRepoGroupToPerm.permission),)
2562
2551
2563 perm_rows = []
2552 perm_rows = []
2564 for _user_group in q.all():
2553 for _user_group in q.all():
2565 usr = AttributeDict(_user_group.users_group.get_dict())
2554 usr = AttributeDict(_user_group.users_group.get_dict())
2566 usr.permission = _user_group.permission.permission_name
2555 usr.permission = _user_group.permission.permission_name
2567 perm_rows.append(usr)
2556 perm_rows.append(usr)
2568
2557
2569 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2558 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2570 return perm_rows
2559 return perm_rows
2571
2560
2572 def get_api_data(self):
2561 def get_api_data(self):
2573 """
2562 """
2574 Common function for generating api data
2563 Common function for generating api data
2575
2564
2576 """
2565 """
2577 group = self
2566 group = self
2578 data = {
2567 data = {
2579 'group_id': group.group_id,
2568 'group_id': group.group_id,
2580 'group_name': group.group_name,
2569 'group_name': group.group_name,
2581 'group_description': group.description_safe,
2570 'group_description': group.description_safe,
2582 'parent_group': group.parent_group.group_name if group.parent_group else None,
2571 'parent_group': group.parent_group.group_name if group.parent_group else None,
2583 'repositories': [x.repo_name for x in group.repositories],
2572 'repositories': [x.repo_name for x in group.repositories],
2584 'owner': group.user.username,
2573 'owner': group.user.username,
2585 }
2574 }
2586 return data
2575 return data
2587
2576
2588
2577
2589 class Permission(Base, BaseModel):
2578 class Permission(Base, BaseModel):
2590 __tablename__ = 'permissions'
2579 __tablename__ = 'permissions'
2591 __table_args__ = (
2580 __table_args__ = (
2592 Index('p_perm_name_idx', 'permission_name'),
2581 Index('p_perm_name_idx', 'permission_name'),
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2582 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2583 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2595 )
2584 )
2596 PERMS = [
2585 PERMS = [
2597 ('hg.admin', _('RhodeCode Super Administrator')),
2586 ('hg.admin', _('RhodeCode Super Administrator')),
2598
2587
2599 ('repository.none', _('Repository no access')),
2588 ('repository.none', _('Repository no access')),
2600 ('repository.read', _('Repository read access')),
2589 ('repository.read', _('Repository read access')),
2601 ('repository.write', _('Repository write access')),
2590 ('repository.write', _('Repository write access')),
2602 ('repository.admin', _('Repository admin access')),
2591 ('repository.admin', _('Repository admin access')),
2603
2592
2604 ('group.none', _('Repository group no access')),
2593 ('group.none', _('Repository group no access')),
2605 ('group.read', _('Repository group read access')),
2594 ('group.read', _('Repository group read access')),
2606 ('group.write', _('Repository group write access')),
2595 ('group.write', _('Repository group write access')),
2607 ('group.admin', _('Repository group admin access')),
2596 ('group.admin', _('Repository group admin access')),
2608
2597
2609 ('usergroup.none', _('User group no access')),
2598 ('usergroup.none', _('User group no access')),
2610 ('usergroup.read', _('User group read access')),
2599 ('usergroup.read', _('User group read access')),
2611 ('usergroup.write', _('User group write access')),
2600 ('usergroup.write', _('User group write access')),
2612 ('usergroup.admin', _('User group admin access')),
2601 ('usergroup.admin', _('User group admin access')),
2613
2602
2614 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2603 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2615 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2604 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2616
2605
2617 ('hg.usergroup.create.false', _('User Group creation disabled')),
2606 ('hg.usergroup.create.false', _('User Group creation disabled')),
2618 ('hg.usergroup.create.true', _('User Group creation enabled')),
2607 ('hg.usergroup.create.true', _('User Group creation enabled')),
2619
2608
2620 ('hg.create.none', _('Repository creation disabled')),
2609 ('hg.create.none', _('Repository creation disabled')),
2621 ('hg.create.repository', _('Repository creation enabled')),
2610 ('hg.create.repository', _('Repository creation enabled')),
2622 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2611 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2623 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2612 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2624
2613
2625 ('hg.fork.none', _('Repository forking disabled')),
2614 ('hg.fork.none', _('Repository forking disabled')),
2626 ('hg.fork.repository', _('Repository forking enabled')),
2615 ('hg.fork.repository', _('Repository forking enabled')),
2627
2616
2628 ('hg.register.none', _('Registration disabled')),
2617 ('hg.register.none', _('Registration disabled')),
2629 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2618 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2630 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2619 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2631
2620
2632 ('hg.password_reset.enabled', _('Password reset enabled')),
2621 ('hg.password_reset.enabled', _('Password reset enabled')),
2633 ('hg.password_reset.hidden', _('Password reset hidden')),
2622 ('hg.password_reset.hidden', _('Password reset hidden')),
2634 ('hg.password_reset.disabled', _('Password reset disabled')),
2623 ('hg.password_reset.disabled', _('Password reset disabled')),
2635
2624
2636 ('hg.extern_activate.manual', _('Manual activation of external account')),
2625 ('hg.extern_activate.manual', _('Manual activation of external account')),
2637 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2626 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2638
2627
2639 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2628 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2640 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2629 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2641 ]
2630 ]
2642
2631
2643 # definition of system default permissions for DEFAULT user
2632 # definition of system default permissions for DEFAULT user
2644 DEFAULT_USER_PERMISSIONS = [
2633 DEFAULT_USER_PERMISSIONS = [
2645 'repository.read',
2634 'repository.read',
2646 'group.read',
2635 'group.read',
2647 'usergroup.read',
2636 'usergroup.read',
2648 'hg.create.repository',
2637 'hg.create.repository',
2649 'hg.repogroup.create.false',
2638 'hg.repogroup.create.false',
2650 'hg.usergroup.create.false',
2639 'hg.usergroup.create.false',
2651 'hg.create.write_on_repogroup.true',
2640 'hg.create.write_on_repogroup.true',
2652 'hg.fork.repository',
2641 'hg.fork.repository',
2653 'hg.register.manual_activate',
2642 'hg.register.manual_activate',
2654 'hg.password_reset.enabled',
2643 'hg.password_reset.enabled',
2655 'hg.extern_activate.auto',
2644 'hg.extern_activate.auto',
2656 'hg.inherit_default_perms.true',
2645 'hg.inherit_default_perms.true',
2657 ]
2646 ]
2658
2647
2659 # defines which permissions are more important higher the more important
2648 # defines which permissions are more important higher the more important
2660 # Weight defines which permissions are more important.
2649 # Weight defines which permissions are more important.
2661 # The higher number the more important.
2650 # The higher number the more important.
2662 PERM_WEIGHTS = {
2651 PERM_WEIGHTS = {
2663 'repository.none': 0,
2652 'repository.none': 0,
2664 'repository.read': 1,
2653 'repository.read': 1,
2665 'repository.write': 3,
2654 'repository.write': 3,
2666 'repository.admin': 4,
2655 'repository.admin': 4,
2667
2656
2668 'group.none': 0,
2657 'group.none': 0,
2669 'group.read': 1,
2658 'group.read': 1,
2670 'group.write': 3,
2659 'group.write': 3,
2671 'group.admin': 4,
2660 'group.admin': 4,
2672
2661
2673 'usergroup.none': 0,
2662 'usergroup.none': 0,
2674 'usergroup.read': 1,
2663 'usergroup.read': 1,
2675 'usergroup.write': 3,
2664 'usergroup.write': 3,
2676 'usergroup.admin': 4,
2665 'usergroup.admin': 4,
2677
2666
2678 'hg.repogroup.create.false': 0,
2667 'hg.repogroup.create.false': 0,
2679 'hg.repogroup.create.true': 1,
2668 'hg.repogroup.create.true': 1,
2680
2669
2681 'hg.usergroup.create.false': 0,
2670 'hg.usergroup.create.false': 0,
2682 'hg.usergroup.create.true': 1,
2671 'hg.usergroup.create.true': 1,
2683
2672
2684 'hg.fork.none': 0,
2673 'hg.fork.none': 0,
2685 'hg.fork.repository': 1,
2674 'hg.fork.repository': 1,
2686 'hg.create.none': 0,
2675 'hg.create.none': 0,
2687 'hg.create.repository': 1
2676 'hg.create.repository': 1
2688 }
2677 }
2689
2678
2690 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2679 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2691 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2680 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2692 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2681 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2693
2682
2694 def __unicode__(self):
2683 def __unicode__(self):
2695 return u"<%s('%s:%s')>" % (
2684 return u"<%s('%s:%s')>" % (
2696 self.__class__.__name__, self.permission_id, self.permission_name
2685 self.__class__.__name__, self.permission_id, self.permission_name
2697 )
2686 )
2698
2687
2699 @classmethod
2688 @classmethod
2700 def get_by_key(cls, key):
2689 def get_by_key(cls, key):
2701 return cls.query().filter(cls.permission_name == key).scalar()
2690 return cls.query().filter(cls.permission_name == key).scalar()
2702
2691
2703 @classmethod
2692 @classmethod
2704 def get_default_repo_perms(cls, user_id, repo_id=None):
2693 def get_default_repo_perms(cls, user_id, repo_id=None):
2705 q = Session().query(UserRepoToPerm, Repository, Permission)\
2694 q = Session().query(UserRepoToPerm, Repository, Permission)\
2706 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2695 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2707 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2696 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2708 .filter(UserRepoToPerm.user_id == user_id)
2697 .filter(UserRepoToPerm.user_id == user_id)
2709 if repo_id:
2698 if repo_id:
2710 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2699 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2711 return q.all()
2700 return q.all()
2712
2701
2713 @classmethod
2702 @classmethod
2714 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2703 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2715 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2704 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2716 .join(
2705 .join(
2717 Permission,
2706 Permission,
2718 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2707 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2719 .join(
2708 .join(
2720 Repository,
2709 Repository,
2721 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2710 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2722 .join(
2711 .join(
2723 UserGroup,
2712 UserGroup,
2724 UserGroupRepoToPerm.users_group_id ==
2713 UserGroupRepoToPerm.users_group_id ==
2725 UserGroup.users_group_id)\
2714 UserGroup.users_group_id)\
2726 .join(
2715 .join(
2727 UserGroupMember,
2716 UserGroupMember,
2728 UserGroupRepoToPerm.users_group_id ==
2717 UserGroupRepoToPerm.users_group_id ==
2729 UserGroupMember.users_group_id)\
2718 UserGroupMember.users_group_id)\
2730 .filter(
2719 .filter(
2731 UserGroupMember.user_id == user_id,
2720 UserGroupMember.user_id == user_id,
2732 UserGroup.users_group_active == true())
2721 UserGroup.users_group_active == true())
2733 if repo_id:
2722 if repo_id:
2734 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2723 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2735 return q.all()
2724 return q.all()
2736
2725
2737 @classmethod
2726 @classmethod
2738 def get_default_group_perms(cls, user_id, repo_group_id=None):
2727 def get_default_group_perms(cls, user_id, repo_group_id=None):
2739 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2728 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2740 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2729 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2741 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2730 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2742 .filter(UserRepoGroupToPerm.user_id == user_id)
2731 .filter(UserRepoGroupToPerm.user_id == user_id)
2743 if repo_group_id:
2732 if repo_group_id:
2744 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2733 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2745 return q.all()
2734 return q.all()
2746
2735
2747 @classmethod
2736 @classmethod
2748 def get_default_group_perms_from_user_group(
2737 def get_default_group_perms_from_user_group(
2749 cls, user_id, repo_group_id=None):
2738 cls, user_id, repo_group_id=None):
2750 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2739 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2751 .join(
2740 .join(
2752 Permission,
2741 Permission,
2753 UserGroupRepoGroupToPerm.permission_id ==
2742 UserGroupRepoGroupToPerm.permission_id ==
2754 Permission.permission_id)\
2743 Permission.permission_id)\
2755 .join(
2744 .join(
2756 RepoGroup,
2745 RepoGroup,
2757 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2746 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2758 .join(
2747 .join(
2759 UserGroup,
2748 UserGroup,
2760 UserGroupRepoGroupToPerm.users_group_id ==
2749 UserGroupRepoGroupToPerm.users_group_id ==
2761 UserGroup.users_group_id)\
2750 UserGroup.users_group_id)\
2762 .join(
2751 .join(
2763 UserGroupMember,
2752 UserGroupMember,
2764 UserGroupRepoGroupToPerm.users_group_id ==
2753 UserGroupRepoGroupToPerm.users_group_id ==
2765 UserGroupMember.users_group_id)\
2754 UserGroupMember.users_group_id)\
2766 .filter(
2755 .filter(
2767 UserGroupMember.user_id == user_id,
2756 UserGroupMember.user_id == user_id,
2768 UserGroup.users_group_active == true())
2757 UserGroup.users_group_active == true())
2769 if repo_group_id:
2758 if repo_group_id:
2770 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2759 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2771 return q.all()
2760 return q.all()
2772
2761
2773 @classmethod
2762 @classmethod
2774 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2763 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2775 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2764 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2776 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2765 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2777 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2766 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2778 .filter(UserUserGroupToPerm.user_id == user_id)
2767 .filter(UserUserGroupToPerm.user_id == user_id)
2779 if user_group_id:
2768 if user_group_id:
2780 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2769 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2781 return q.all()
2770 return q.all()
2782
2771
2783 @classmethod
2772 @classmethod
2784 def get_default_user_group_perms_from_user_group(
2773 def get_default_user_group_perms_from_user_group(
2785 cls, user_id, user_group_id=None):
2774 cls, user_id, user_group_id=None):
2786 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2775 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2787 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2776 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2788 .join(
2777 .join(
2789 Permission,
2778 Permission,
2790 UserGroupUserGroupToPerm.permission_id ==
2779 UserGroupUserGroupToPerm.permission_id ==
2791 Permission.permission_id)\
2780 Permission.permission_id)\
2792 .join(
2781 .join(
2793 TargetUserGroup,
2782 TargetUserGroup,
2794 UserGroupUserGroupToPerm.target_user_group_id ==
2783 UserGroupUserGroupToPerm.target_user_group_id ==
2795 TargetUserGroup.users_group_id)\
2784 TargetUserGroup.users_group_id)\
2796 .join(
2785 .join(
2797 UserGroup,
2786 UserGroup,
2798 UserGroupUserGroupToPerm.user_group_id ==
2787 UserGroupUserGroupToPerm.user_group_id ==
2799 UserGroup.users_group_id)\
2788 UserGroup.users_group_id)\
2800 .join(
2789 .join(
2801 UserGroupMember,
2790 UserGroupMember,
2802 UserGroupUserGroupToPerm.user_group_id ==
2791 UserGroupUserGroupToPerm.user_group_id ==
2803 UserGroupMember.users_group_id)\
2792 UserGroupMember.users_group_id)\
2804 .filter(
2793 .filter(
2805 UserGroupMember.user_id == user_id,
2794 UserGroupMember.user_id == user_id,
2806 UserGroup.users_group_active == true())
2795 UserGroup.users_group_active == true())
2807 if user_group_id:
2796 if user_group_id:
2808 q = q.filter(
2797 q = q.filter(
2809 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2798 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2810
2799
2811 return q.all()
2800 return q.all()
2812
2801
2813
2802
2814 class UserRepoToPerm(Base, BaseModel):
2803 class UserRepoToPerm(Base, BaseModel):
2815 __tablename__ = 'repo_to_perm'
2804 __tablename__ = 'repo_to_perm'
2816 __table_args__ = (
2805 __table_args__ = (
2817 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2806 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2818 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2807 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2819 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2808 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2820 )
2809 )
2821 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2810 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2822 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2811 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2823 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2812 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2824 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2813 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2825
2814
2826 user = relationship('User')
2815 user = relationship('User')
2827 repository = relationship('Repository')
2816 repository = relationship('Repository')
2828 permission = relationship('Permission')
2817 permission = relationship('Permission')
2829
2818
2830 @classmethod
2819 @classmethod
2831 def create(cls, user, repository, permission):
2820 def create(cls, user, repository, permission):
2832 n = cls()
2821 n = cls()
2833 n.user = user
2822 n.user = user
2834 n.repository = repository
2823 n.repository = repository
2835 n.permission = permission
2824 n.permission = permission
2836 Session().add(n)
2825 Session().add(n)
2837 return n
2826 return n
2838
2827
2839 def __unicode__(self):
2828 def __unicode__(self):
2840 return u'<%s => %s >' % (self.user, self.repository)
2829 return u'<%s => %s >' % (self.user, self.repository)
2841
2830
2842
2831
2843 class UserUserGroupToPerm(Base, BaseModel):
2832 class UserUserGroupToPerm(Base, BaseModel):
2844 __tablename__ = 'user_user_group_to_perm'
2833 __tablename__ = 'user_user_group_to_perm'
2845 __table_args__ = (
2834 __table_args__ = (
2846 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2835 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2847 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2836 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2848 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2837 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2849 )
2838 )
2850 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2839 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2851 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2840 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2852 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2841 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2853 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2842 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2854
2843
2855 user = relationship('User')
2844 user = relationship('User')
2856 user_group = relationship('UserGroup')
2845 user_group = relationship('UserGroup')
2857 permission = relationship('Permission')
2846 permission = relationship('Permission')
2858
2847
2859 @classmethod
2848 @classmethod
2860 def create(cls, user, user_group, permission):
2849 def create(cls, user, user_group, permission):
2861 n = cls()
2850 n = cls()
2862 n.user = user
2851 n.user = user
2863 n.user_group = user_group
2852 n.user_group = user_group
2864 n.permission = permission
2853 n.permission = permission
2865 Session().add(n)
2854 Session().add(n)
2866 return n
2855 return n
2867
2856
2868 def __unicode__(self):
2857 def __unicode__(self):
2869 return u'<%s => %s >' % (self.user, self.user_group)
2858 return u'<%s => %s >' % (self.user, self.user_group)
2870
2859
2871
2860
2872 class UserToPerm(Base, BaseModel):
2861 class UserToPerm(Base, BaseModel):
2873 __tablename__ = 'user_to_perm'
2862 __tablename__ = 'user_to_perm'
2874 __table_args__ = (
2863 __table_args__ = (
2875 UniqueConstraint('user_id', 'permission_id'),
2864 UniqueConstraint('user_id', 'permission_id'),
2876 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2865 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2877 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2866 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2878 )
2867 )
2879 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2868 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2880 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2881 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2870 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2882
2871
2883 user = relationship('User')
2872 user = relationship('User')
2884 permission = relationship('Permission', lazy='joined')
2873 permission = relationship('Permission', lazy='joined')
2885
2874
2886 def __unicode__(self):
2875 def __unicode__(self):
2887 return u'<%s => %s >' % (self.user, self.permission)
2876 return u'<%s => %s >' % (self.user, self.permission)
2888
2877
2889
2878
2890 class UserGroupRepoToPerm(Base, BaseModel):
2879 class UserGroupRepoToPerm(Base, BaseModel):
2891 __tablename__ = 'users_group_repo_to_perm'
2880 __tablename__ = 'users_group_repo_to_perm'
2892 __table_args__ = (
2881 __table_args__ = (
2893 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2882 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2895 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2896 )
2885 )
2897 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2898 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2899 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2900 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2889 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2901
2890
2902 users_group = relationship('UserGroup')
2891 users_group = relationship('UserGroup')
2903 permission = relationship('Permission')
2892 permission = relationship('Permission')
2904 repository = relationship('Repository')
2893 repository = relationship('Repository')
2905
2894
2906 @classmethod
2895 @classmethod
2907 def create(cls, users_group, repository, permission):
2896 def create(cls, users_group, repository, permission):
2908 n = cls()
2897 n = cls()
2909 n.users_group = users_group
2898 n.users_group = users_group
2910 n.repository = repository
2899 n.repository = repository
2911 n.permission = permission
2900 n.permission = permission
2912 Session().add(n)
2901 Session().add(n)
2913 return n
2902 return n
2914
2903
2915 def __unicode__(self):
2904 def __unicode__(self):
2916 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2905 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2917
2906
2918
2907
2919 class UserGroupUserGroupToPerm(Base, BaseModel):
2908 class UserGroupUserGroupToPerm(Base, BaseModel):
2920 __tablename__ = 'user_group_user_group_to_perm'
2909 __tablename__ = 'user_group_user_group_to_perm'
2921 __table_args__ = (
2910 __table_args__ = (
2922 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2911 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2923 CheckConstraint('target_user_group_id != user_group_id'),
2912 CheckConstraint('target_user_group_id != user_group_id'),
2924 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2913 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2925 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2914 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2926 )
2915 )
2927 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)
2916 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)
2928 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2917 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2929 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2930 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2919 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2931
2920
2932 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2921 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2933 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2922 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2934 permission = relationship('Permission')
2923 permission = relationship('Permission')
2935
2924
2936 @classmethod
2925 @classmethod
2937 def create(cls, target_user_group, user_group, permission):
2926 def create(cls, target_user_group, user_group, permission):
2938 n = cls()
2927 n = cls()
2939 n.target_user_group = target_user_group
2928 n.target_user_group = target_user_group
2940 n.user_group = user_group
2929 n.user_group = user_group
2941 n.permission = permission
2930 n.permission = permission
2942 Session().add(n)
2931 Session().add(n)
2943 return n
2932 return n
2944
2933
2945 def __unicode__(self):
2934 def __unicode__(self):
2946 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2935 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2947
2936
2948
2937
2949 class UserGroupToPerm(Base, BaseModel):
2938 class UserGroupToPerm(Base, BaseModel):
2950 __tablename__ = 'users_group_to_perm'
2939 __tablename__ = 'users_group_to_perm'
2951 __table_args__ = (
2940 __table_args__ = (
2952 UniqueConstraint('users_group_id', 'permission_id',),
2941 UniqueConstraint('users_group_id', 'permission_id',),
2953 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2954 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2955 )
2944 )
2956 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2945 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2957 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2946 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2958 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2947 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2959
2948
2960 users_group = relationship('UserGroup')
2949 users_group = relationship('UserGroup')
2961 permission = relationship('Permission')
2950 permission = relationship('Permission')
2962
2951
2963
2952
2964 class UserRepoGroupToPerm(Base, BaseModel):
2953 class UserRepoGroupToPerm(Base, BaseModel):
2965 __tablename__ = 'user_repo_group_to_perm'
2954 __tablename__ = 'user_repo_group_to_perm'
2966 __table_args__ = (
2955 __table_args__ = (
2967 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2956 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2969 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2958 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2970 )
2959 )
2971
2960
2972 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2961 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2973 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2974 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2963 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2975 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2964 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2976
2965
2977 user = relationship('User')
2966 user = relationship('User')
2978 group = relationship('RepoGroup')
2967 group = relationship('RepoGroup')
2979 permission = relationship('Permission')
2968 permission = relationship('Permission')
2980
2969
2981 @classmethod
2970 @classmethod
2982 def create(cls, user, repository_group, permission):
2971 def create(cls, user, repository_group, permission):
2983 n = cls()
2972 n = cls()
2984 n.user = user
2973 n.user = user
2985 n.group = repository_group
2974 n.group = repository_group
2986 n.permission = permission
2975 n.permission = permission
2987 Session().add(n)
2976 Session().add(n)
2988 return n
2977 return n
2989
2978
2990
2979
2991 class UserGroupRepoGroupToPerm(Base, BaseModel):
2980 class UserGroupRepoGroupToPerm(Base, BaseModel):
2992 __tablename__ = 'users_group_repo_group_to_perm'
2981 __tablename__ = 'users_group_repo_group_to_perm'
2993 __table_args__ = (
2982 __table_args__ = (
2994 UniqueConstraint('users_group_id', 'group_id'),
2983 UniqueConstraint('users_group_id', 'group_id'),
2995 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2996 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2985 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2997 )
2986 )
2998
2987
2999 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)
2988 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)
3000 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2989 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3001 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2990 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3002 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2991 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3003
2992
3004 users_group = relationship('UserGroup')
2993 users_group = relationship('UserGroup')
3005 permission = relationship('Permission')
2994 permission = relationship('Permission')
3006 group = relationship('RepoGroup')
2995 group = relationship('RepoGroup')
3007
2996
3008 @classmethod
2997 @classmethod
3009 def create(cls, user_group, repository_group, permission):
2998 def create(cls, user_group, repository_group, permission):
3010 n = cls()
2999 n = cls()
3011 n.users_group = user_group
3000 n.users_group = user_group
3012 n.group = repository_group
3001 n.group = repository_group
3013 n.permission = permission
3002 n.permission = permission
3014 Session().add(n)
3003 Session().add(n)
3015 return n
3004 return n
3016
3005
3017 def __unicode__(self):
3006 def __unicode__(self):
3018 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3007 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3019
3008
3020
3009
3021 class Statistics(Base, BaseModel):
3010 class Statistics(Base, BaseModel):
3022 __tablename__ = 'statistics'
3011 __tablename__ = 'statistics'
3023 __table_args__ = (
3012 __table_args__ = (
3024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3013 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3014 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3026 )
3015 )
3027 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3016 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3028 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3017 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3029 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3018 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3030 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3019 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3031 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3020 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3032 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3021 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3033
3022
3034 repository = relationship('Repository', single_parent=True)
3023 repository = relationship('Repository', single_parent=True)
3035
3024
3036
3025
3037 class UserFollowing(Base, BaseModel):
3026 class UserFollowing(Base, BaseModel):
3038 __tablename__ = 'user_followings'
3027 __tablename__ = 'user_followings'
3039 __table_args__ = (
3028 __table_args__ = (
3040 UniqueConstraint('user_id', 'follows_repository_id'),
3029 UniqueConstraint('user_id', 'follows_repository_id'),
3041 UniqueConstraint('user_id', 'follows_user_id'),
3030 UniqueConstraint('user_id', 'follows_user_id'),
3042 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3031 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3043 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3032 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3044 )
3033 )
3045
3034
3046 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3035 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3047 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3036 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3048 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3037 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3049 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3038 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3050 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3039 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3051
3040
3052 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3041 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3053
3042
3054 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3043 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3055 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3044 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3056
3045
3057 @classmethod
3046 @classmethod
3058 def get_repo_followers(cls, repo_id):
3047 def get_repo_followers(cls, repo_id):
3059 return cls.query().filter(cls.follows_repo_id == repo_id)
3048 return cls.query().filter(cls.follows_repo_id == repo_id)
3060
3049
3061
3050
3062 class CacheKey(Base, BaseModel):
3051 class CacheKey(Base, BaseModel):
3063 __tablename__ = 'cache_invalidation'
3052 __tablename__ = 'cache_invalidation'
3064 __table_args__ = (
3053 __table_args__ = (
3065 UniqueConstraint('cache_key'),
3054 UniqueConstraint('cache_key'),
3066 Index('key_idx', 'cache_key'),
3055 Index('key_idx', 'cache_key'),
3067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3057 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3069 )
3058 )
3070 CACHE_TYPE_ATOM = 'ATOM'
3059 CACHE_TYPE_ATOM = 'ATOM'
3071 CACHE_TYPE_RSS = 'RSS'
3060 CACHE_TYPE_RSS = 'RSS'
3072 CACHE_TYPE_README = 'README'
3061 CACHE_TYPE_README = 'README'
3073
3062
3074 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3063 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3075 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3064 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3076 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3065 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3077 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3066 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3078
3067
3079 def __init__(self, cache_key, cache_args=''):
3068 def __init__(self, cache_key, cache_args=''):
3080 self.cache_key = cache_key
3069 self.cache_key = cache_key
3081 self.cache_args = cache_args
3070 self.cache_args = cache_args
3082 self.cache_active = False
3071 self.cache_active = False
3083
3072
3084 def __unicode__(self):
3073 def __unicode__(self):
3085 return u"<%s('%s:%s[%s]')>" % (
3074 return u"<%s('%s:%s[%s]')>" % (
3086 self.__class__.__name__,
3075 self.__class__.__name__,
3087 self.cache_id, self.cache_key, self.cache_active)
3076 self.cache_id, self.cache_key, self.cache_active)
3088
3077
3089 def _cache_key_partition(self):
3078 def _cache_key_partition(self):
3090 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3079 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3091 return prefix, repo_name, suffix
3080 return prefix, repo_name, suffix
3092
3081
3093 def get_prefix(self):
3082 def get_prefix(self):
3094 """
3083 """
3095 Try to extract prefix from existing cache key. The key could consist
3084 Try to extract prefix from existing cache key. The key could consist
3096 of prefix, repo_name, suffix
3085 of prefix, repo_name, suffix
3097 """
3086 """
3098 # this returns prefix, repo_name, suffix
3087 # this returns prefix, repo_name, suffix
3099 return self._cache_key_partition()[0]
3088 return self._cache_key_partition()[0]
3100
3089
3101 def get_suffix(self):
3090 def get_suffix(self):
3102 """
3091 """
3103 get suffix that might have been used in _get_cache_key to
3092 get suffix that might have been used in _get_cache_key to
3104 generate self.cache_key. Only used for informational purposes
3093 generate self.cache_key. Only used for informational purposes
3105 in repo_edit.mako.
3094 in repo_edit.mako.
3106 """
3095 """
3107 # prefix, repo_name, suffix
3096 # prefix, repo_name, suffix
3108 return self._cache_key_partition()[2]
3097 return self._cache_key_partition()[2]
3109
3098
3110 @classmethod
3099 @classmethod
3111 def delete_all_cache(cls):
3100 def delete_all_cache(cls):
3112 """
3101 """
3113 Delete all cache keys from database.
3102 Delete all cache keys from database.
3114 Should only be run when all instances are down and all entries
3103 Should only be run when all instances are down and all entries
3115 thus stale.
3104 thus stale.
3116 """
3105 """
3117 cls.query().delete()
3106 cls.query().delete()
3118 Session().commit()
3107 Session().commit()
3119
3108
3120 @classmethod
3109 @classmethod
3121 def get_cache_key(cls, repo_name, cache_type):
3110 def get_cache_key(cls, repo_name, cache_type):
3122 """
3111 """
3123
3112
3124 Generate a cache key for this process of RhodeCode instance.
3113 Generate a cache key for this process of RhodeCode instance.
3125 Prefix most likely will be process id or maybe explicitly set
3114 Prefix most likely will be process id or maybe explicitly set
3126 instance_id from .ini file.
3115 instance_id from .ini file.
3127 """
3116 """
3128 import rhodecode
3117 import rhodecode
3129 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3118 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3130
3119
3131 repo_as_unicode = safe_unicode(repo_name)
3120 repo_as_unicode = safe_unicode(repo_name)
3132 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3121 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3133 if cache_type else repo_as_unicode
3122 if cache_type else repo_as_unicode
3134
3123
3135 return u'{}{}'.format(prefix, key)
3124 return u'{}{}'.format(prefix, key)
3136
3125
3137 @classmethod
3126 @classmethod
3138 def set_invalidate(cls, repo_name, delete=False):
3127 def set_invalidate(cls, repo_name, delete=False):
3139 """
3128 """
3140 Mark all caches of a repo as invalid in the database.
3129 Mark all caches of a repo as invalid in the database.
3141 """
3130 """
3142
3131
3143 try:
3132 try:
3144 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3133 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3145 if delete:
3134 if delete:
3146 log.debug('cache objects deleted for repo %s',
3135 log.debug('cache objects deleted for repo %s',
3147 safe_str(repo_name))
3136 safe_str(repo_name))
3148 qry.delete()
3137 qry.delete()
3149 else:
3138 else:
3150 log.debug('cache objects marked as invalid for repo %s',
3139 log.debug('cache objects marked as invalid for repo %s',
3151 safe_str(repo_name))
3140 safe_str(repo_name))
3152 qry.update({"cache_active": False})
3141 qry.update({"cache_active": False})
3153
3142
3154 Session().commit()
3143 Session().commit()
3155 except Exception:
3144 except Exception:
3156 log.exception(
3145 log.exception(
3157 'Cache key invalidation failed for repository %s',
3146 'Cache key invalidation failed for repository %s',
3158 safe_str(repo_name))
3147 safe_str(repo_name))
3159 Session().rollback()
3148 Session().rollback()
3160
3149
3161 @classmethod
3150 @classmethod
3162 def get_active_cache(cls, cache_key):
3151 def get_active_cache(cls, cache_key):
3163 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3152 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3164 if inv_obj:
3153 if inv_obj:
3165 return inv_obj
3154 return inv_obj
3166 return None
3155 return None
3167
3156
3168 @classmethod
3169 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3170 thread_scoped=False):
3171 """
3172 @cache_region('long_term')
3173 def _heavy_calculation(cache_key):
3174 return 'result'
3175
3176 cache_context = CacheKey.repo_context_cache(
3177 _heavy_calculation, repo_name, cache_type)
3178
3179 with cache_context as context:
3180 context.invalidate()
3181 computed = context.compute()
3182
3183 assert computed == 'result'
3184 """
3185 from rhodecode.lib import caches
3186 return caches.InvalidationContext(
3187 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3188
3189
3157
3190 class ChangesetComment(Base, BaseModel):
3158 class ChangesetComment(Base, BaseModel):
3191 __tablename__ = 'changeset_comments'
3159 __tablename__ = 'changeset_comments'
3192 __table_args__ = (
3160 __table_args__ = (
3193 Index('cc_revision_idx', 'revision'),
3161 Index('cc_revision_idx', 'revision'),
3194 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3195 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3196 )
3164 )
3197
3165
3198 COMMENT_OUTDATED = u'comment_outdated'
3166 COMMENT_OUTDATED = u'comment_outdated'
3199 COMMENT_TYPE_NOTE = u'note'
3167 COMMENT_TYPE_NOTE = u'note'
3200 COMMENT_TYPE_TODO = u'todo'
3168 COMMENT_TYPE_TODO = u'todo'
3201 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3169 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3202
3170
3203 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3171 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3204 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3172 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3205 revision = Column('revision', String(40), nullable=True)
3173 revision = Column('revision', String(40), nullable=True)
3206 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3174 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3207 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3175 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3208 line_no = Column('line_no', Unicode(10), nullable=True)
3176 line_no = Column('line_no', Unicode(10), nullable=True)
3209 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3177 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3210 f_path = Column('f_path', Unicode(1000), nullable=True)
3178 f_path = Column('f_path', Unicode(1000), nullable=True)
3211 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3179 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3212 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3180 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3181 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3214 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3182 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3215 renderer = Column('renderer', Unicode(64), nullable=True)
3183 renderer = Column('renderer', Unicode(64), nullable=True)
3216 display_state = Column('display_state', Unicode(128), nullable=True)
3184 display_state = Column('display_state', Unicode(128), nullable=True)
3217
3185
3218 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3186 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3219 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3187 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3220 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3188 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3221 author = relationship('User', lazy='joined')
3189 author = relationship('User', lazy='joined')
3222 repo = relationship('Repository')
3190 repo = relationship('Repository')
3223 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3191 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3224 pull_request = relationship('PullRequest', lazy='joined')
3192 pull_request = relationship('PullRequest', lazy='joined')
3225 pull_request_version = relationship('PullRequestVersion')
3193 pull_request_version = relationship('PullRequestVersion')
3226
3194
3227 @classmethod
3195 @classmethod
3228 def get_users(cls, revision=None, pull_request_id=None):
3196 def get_users(cls, revision=None, pull_request_id=None):
3229 """
3197 """
3230 Returns user associated with this ChangesetComment. ie those
3198 Returns user associated with this ChangesetComment. ie those
3231 who actually commented
3199 who actually commented
3232
3200
3233 :param cls:
3201 :param cls:
3234 :param revision:
3202 :param revision:
3235 """
3203 """
3236 q = Session().query(User)\
3204 q = Session().query(User)\
3237 .join(ChangesetComment.author)
3205 .join(ChangesetComment.author)
3238 if revision:
3206 if revision:
3239 q = q.filter(cls.revision == revision)
3207 q = q.filter(cls.revision == revision)
3240 elif pull_request_id:
3208 elif pull_request_id:
3241 q = q.filter(cls.pull_request_id == pull_request_id)
3209 q = q.filter(cls.pull_request_id == pull_request_id)
3242 return q.all()
3210 return q.all()
3243
3211
3244 @classmethod
3212 @classmethod
3245 def get_index_from_version(cls, pr_version, versions):
3213 def get_index_from_version(cls, pr_version, versions):
3246 num_versions = [x.pull_request_version_id for x in versions]
3214 num_versions = [x.pull_request_version_id for x in versions]
3247 try:
3215 try:
3248 return num_versions.index(pr_version) +1
3216 return num_versions.index(pr_version) +1
3249 except (IndexError, ValueError):
3217 except (IndexError, ValueError):
3250 return
3218 return
3251
3219
3252 @property
3220 @property
3253 def outdated(self):
3221 def outdated(self):
3254 return self.display_state == self.COMMENT_OUTDATED
3222 return self.display_state == self.COMMENT_OUTDATED
3255
3223
3256 def outdated_at_version(self, version):
3224 def outdated_at_version(self, version):
3257 """
3225 """
3258 Checks if comment is outdated for given pull request version
3226 Checks if comment is outdated for given pull request version
3259 """
3227 """
3260 return self.outdated and self.pull_request_version_id != version
3228 return self.outdated and self.pull_request_version_id != version
3261
3229
3262 def older_than_version(self, version):
3230 def older_than_version(self, version):
3263 """
3231 """
3264 Checks if comment is made from previous version than given
3232 Checks if comment is made from previous version than given
3265 """
3233 """
3266 if version is None:
3234 if version is None:
3267 return self.pull_request_version_id is not None
3235 return self.pull_request_version_id is not None
3268
3236
3269 return self.pull_request_version_id < version
3237 return self.pull_request_version_id < version
3270
3238
3271 @property
3239 @property
3272 def resolved(self):
3240 def resolved(self):
3273 return self.resolved_by[0] if self.resolved_by else None
3241 return self.resolved_by[0] if self.resolved_by else None
3274
3242
3275 @property
3243 @property
3276 def is_todo(self):
3244 def is_todo(self):
3277 return self.comment_type == self.COMMENT_TYPE_TODO
3245 return self.comment_type == self.COMMENT_TYPE_TODO
3278
3246
3279 @property
3247 @property
3280 def is_inline(self):
3248 def is_inline(self):
3281 return self.line_no and self.f_path
3249 return self.line_no and self.f_path
3282
3250
3283 def get_index_version(self, versions):
3251 def get_index_version(self, versions):
3284 return self.get_index_from_version(
3252 return self.get_index_from_version(
3285 self.pull_request_version_id, versions)
3253 self.pull_request_version_id, versions)
3286
3254
3287 def __repr__(self):
3255 def __repr__(self):
3288 if self.comment_id:
3256 if self.comment_id:
3289 return '<DB:Comment #%s>' % self.comment_id
3257 return '<DB:Comment #%s>' % self.comment_id
3290 else:
3258 else:
3291 return '<DB:Comment at %#x>' % id(self)
3259 return '<DB:Comment at %#x>' % id(self)
3292
3260
3293 def get_api_data(self):
3261 def get_api_data(self):
3294 comment = self
3262 comment = self
3295 data = {
3263 data = {
3296 'comment_id': comment.comment_id,
3264 'comment_id': comment.comment_id,
3297 'comment_type': comment.comment_type,
3265 'comment_type': comment.comment_type,
3298 'comment_text': comment.text,
3266 'comment_text': comment.text,
3299 'comment_status': comment.status_change,
3267 'comment_status': comment.status_change,
3300 'comment_f_path': comment.f_path,
3268 'comment_f_path': comment.f_path,
3301 'comment_lineno': comment.line_no,
3269 'comment_lineno': comment.line_no,
3302 'comment_author': comment.author,
3270 'comment_author': comment.author,
3303 'comment_created_on': comment.created_on
3271 'comment_created_on': comment.created_on
3304 }
3272 }
3305 return data
3273 return data
3306
3274
3307 def __json__(self):
3275 def __json__(self):
3308 data = dict()
3276 data = dict()
3309 data.update(self.get_api_data())
3277 data.update(self.get_api_data())
3310 return data
3278 return data
3311
3279
3312
3280
3313 class ChangesetStatus(Base, BaseModel):
3281 class ChangesetStatus(Base, BaseModel):
3314 __tablename__ = 'changeset_statuses'
3282 __tablename__ = 'changeset_statuses'
3315 __table_args__ = (
3283 __table_args__ = (
3316 Index('cs_revision_idx', 'revision'),
3284 Index('cs_revision_idx', 'revision'),
3317 Index('cs_version_idx', 'version'),
3285 Index('cs_version_idx', 'version'),
3318 UniqueConstraint('repo_id', 'revision', 'version'),
3286 UniqueConstraint('repo_id', 'revision', 'version'),
3319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3320 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3288 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3321 )
3289 )
3322 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3290 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3323 STATUS_APPROVED = 'approved'
3291 STATUS_APPROVED = 'approved'
3324 STATUS_REJECTED = 'rejected'
3292 STATUS_REJECTED = 'rejected'
3325 STATUS_UNDER_REVIEW = 'under_review'
3293 STATUS_UNDER_REVIEW = 'under_review'
3326
3294
3327 STATUSES = [
3295 STATUSES = [
3328 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3296 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3329 (STATUS_APPROVED, _("Approved")),
3297 (STATUS_APPROVED, _("Approved")),
3330 (STATUS_REJECTED, _("Rejected")),
3298 (STATUS_REJECTED, _("Rejected")),
3331 (STATUS_UNDER_REVIEW, _("Under Review")),
3299 (STATUS_UNDER_REVIEW, _("Under Review")),
3332 ]
3300 ]
3333
3301
3334 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3302 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3335 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3303 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3336 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3337 revision = Column('revision', String(40), nullable=False)
3305 revision = Column('revision', String(40), nullable=False)
3338 status = Column('status', String(128), nullable=False, default=DEFAULT)
3306 status = Column('status', String(128), nullable=False, default=DEFAULT)
3339 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3307 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3340 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3308 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3341 version = Column('version', Integer(), nullable=False, default=0)
3309 version = Column('version', Integer(), nullable=False, default=0)
3342 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3310 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3343
3311
3344 author = relationship('User', lazy='joined')
3312 author = relationship('User', lazy='joined')
3345 repo = relationship('Repository')
3313 repo = relationship('Repository')
3346 comment = relationship('ChangesetComment', lazy='joined')
3314 comment = relationship('ChangesetComment', lazy='joined')
3347 pull_request = relationship('PullRequest', lazy='joined')
3315 pull_request = relationship('PullRequest', lazy='joined')
3348
3316
3349 def __unicode__(self):
3317 def __unicode__(self):
3350 return u"<%s('%s[v%s]:%s')>" % (
3318 return u"<%s('%s[v%s]:%s')>" % (
3351 self.__class__.__name__,
3319 self.__class__.__name__,
3352 self.status, self.version, self.author
3320 self.status, self.version, self.author
3353 )
3321 )
3354
3322
3355 @classmethod
3323 @classmethod
3356 def get_status_lbl(cls, value):
3324 def get_status_lbl(cls, value):
3357 return dict(cls.STATUSES).get(value)
3325 return dict(cls.STATUSES).get(value)
3358
3326
3359 @property
3327 @property
3360 def status_lbl(self):
3328 def status_lbl(self):
3361 return ChangesetStatus.get_status_lbl(self.status)
3329 return ChangesetStatus.get_status_lbl(self.status)
3362
3330
3363 def get_api_data(self):
3331 def get_api_data(self):
3364 status = self
3332 status = self
3365 data = {
3333 data = {
3366 'status_id': status.changeset_status_id,
3334 'status_id': status.changeset_status_id,
3367 'status': status.status,
3335 'status': status.status,
3368 }
3336 }
3369 return data
3337 return data
3370
3338
3371 def __json__(self):
3339 def __json__(self):
3372 data = dict()
3340 data = dict()
3373 data.update(self.get_api_data())
3341 data.update(self.get_api_data())
3374 return data
3342 return data
3375
3343
3376
3344
3377 class _PullRequestBase(BaseModel):
3345 class _PullRequestBase(BaseModel):
3378 """
3346 """
3379 Common attributes of pull request and version entries.
3347 Common attributes of pull request and version entries.
3380 """
3348 """
3381
3349
3382 # .status values
3350 # .status values
3383 STATUS_NEW = u'new'
3351 STATUS_NEW = u'new'
3384 STATUS_OPEN = u'open'
3352 STATUS_OPEN = u'open'
3385 STATUS_CLOSED = u'closed'
3353 STATUS_CLOSED = u'closed'
3386
3354
3387 title = Column('title', Unicode(255), nullable=True)
3355 title = Column('title', Unicode(255), nullable=True)
3388 description = Column(
3356 description = Column(
3389 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3357 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3390 nullable=True)
3358 nullable=True)
3391 # new/open/closed status of pull request (not approve/reject/etc)
3359 # new/open/closed status of pull request (not approve/reject/etc)
3392 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3360 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3393 created_on = Column(
3361 created_on = Column(
3394 'created_on', DateTime(timezone=False), nullable=False,
3362 'created_on', DateTime(timezone=False), nullable=False,
3395 default=datetime.datetime.now)
3363 default=datetime.datetime.now)
3396 updated_on = Column(
3364 updated_on = Column(
3397 'updated_on', DateTime(timezone=False), nullable=False,
3365 'updated_on', DateTime(timezone=False), nullable=False,
3398 default=datetime.datetime.now)
3366 default=datetime.datetime.now)
3399
3367
3400 @declared_attr
3368 @declared_attr
3401 def user_id(cls):
3369 def user_id(cls):
3402 return Column(
3370 return Column(
3403 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3371 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3404 unique=None)
3372 unique=None)
3405
3373
3406 # 500 revisions max
3374 # 500 revisions max
3407 _revisions = Column(
3375 _revisions = Column(
3408 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3376 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3409
3377
3410 @declared_attr
3378 @declared_attr
3411 def source_repo_id(cls):
3379 def source_repo_id(cls):
3412 # TODO: dan: rename column to source_repo_id
3380 # TODO: dan: rename column to source_repo_id
3413 return Column(
3381 return Column(
3414 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3382 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3415 nullable=False)
3383 nullable=False)
3416
3384
3417 source_ref = Column('org_ref', Unicode(255), nullable=False)
3385 source_ref = Column('org_ref', Unicode(255), nullable=False)
3418
3386
3419 @declared_attr
3387 @declared_attr
3420 def target_repo_id(cls):
3388 def target_repo_id(cls):
3421 # TODO: dan: rename column to target_repo_id
3389 # TODO: dan: rename column to target_repo_id
3422 return Column(
3390 return Column(
3423 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3391 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3424 nullable=False)
3392 nullable=False)
3425
3393
3426 target_ref = Column('other_ref', Unicode(255), nullable=False)
3394 target_ref = Column('other_ref', Unicode(255), nullable=False)
3427 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3395 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3428
3396
3429 # TODO: dan: rename column to last_merge_source_rev
3397 # TODO: dan: rename column to last_merge_source_rev
3430 _last_merge_source_rev = Column(
3398 _last_merge_source_rev = Column(
3431 'last_merge_org_rev', String(40), nullable=True)
3399 'last_merge_org_rev', String(40), nullable=True)
3432 # TODO: dan: rename column to last_merge_target_rev
3400 # TODO: dan: rename column to last_merge_target_rev
3433 _last_merge_target_rev = Column(
3401 _last_merge_target_rev = Column(
3434 'last_merge_other_rev', String(40), nullable=True)
3402 'last_merge_other_rev', String(40), nullable=True)
3435 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3403 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3436 merge_rev = Column('merge_rev', String(40), nullable=True)
3404 merge_rev = Column('merge_rev', String(40), nullable=True)
3437
3405
3438 reviewer_data = Column(
3406 reviewer_data = Column(
3439 'reviewer_data_json', MutationObj.as_mutable(
3407 'reviewer_data_json', MutationObj.as_mutable(
3440 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3408 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3441
3409
3442 @property
3410 @property
3443 def reviewer_data_json(self):
3411 def reviewer_data_json(self):
3444 return json.dumps(self.reviewer_data)
3412 return json.dumps(self.reviewer_data)
3445
3413
3446 @hybrid_property
3414 @hybrid_property
3447 def description_safe(self):
3415 def description_safe(self):
3448 from rhodecode.lib import helpers as h
3416 from rhodecode.lib import helpers as h
3449 return h.escape(self.description)
3417 return h.escape(self.description)
3450
3418
3451 @hybrid_property
3419 @hybrid_property
3452 def revisions(self):
3420 def revisions(self):
3453 return self._revisions.split(':') if self._revisions else []
3421 return self._revisions.split(':') if self._revisions else []
3454
3422
3455 @revisions.setter
3423 @revisions.setter
3456 def revisions(self, val):
3424 def revisions(self, val):
3457 self._revisions = ':'.join(val)
3425 self._revisions = ':'.join(val)
3458
3426
3459 @hybrid_property
3427 @hybrid_property
3460 def last_merge_status(self):
3428 def last_merge_status(self):
3461 return safe_int(self._last_merge_status)
3429 return safe_int(self._last_merge_status)
3462
3430
3463 @last_merge_status.setter
3431 @last_merge_status.setter
3464 def last_merge_status(self, val):
3432 def last_merge_status(self, val):
3465 self._last_merge_status = val
3433 self._last_merge_status = val
3466
3434
3467 @declared_attr
3435 @declared_attr
3468 def author(cls):
3436 def author(cls):
3469 return relationship('User', lazy='joined')
3437 return relationship('User', lazy='joined')
3470
3438
3471 @declared_attr
3439 @declared_attr
3472 def source_repo(cls):
3440 def source_repo(cls):
3473 return relationship(
3441 return relationship(
3474 'Repository',
3442 'Repository',
3475 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3443 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3476
3444
3477 @property
3445 @property
3478 def source_ref_parts(self):
3446 def source_ref_parts(self):
3479 return self.unicode_to_reference(self.source_ref)
3447 return self.unicode_to_reference(self.source_ref)
3480
3448
3481 @declared_attr
3449 @declared_attr
3482 def target_repo(cls):
3450 def target_repo(cls):
3483 return relationship(
3451 return relationship(
3484 'Repository',
3452 'Repository',
3485 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3453 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3486
3454
3487 @property
3455 @property
3488 def target_ref_parts(self):
3456 def target_ref_parts(self):
3489 return self.unicode_to_reference(self.target_ref)
3457 return self.unicode_to_reference(self.target_ref)
3490
3458
3491 @property
3459 @property
3492 def shadow_merge_ref(self):
3460 def shadow_merge_ref(self):
3493 return self.unicode_to_reference(self._shadow_merge_ref)
3461 return self.unicode_to_reference(self._shadow_merge_ref)
3494
3462
3495 @shadow_merge_ref.setter
3463 @shadow_merge_ref.setter
3496 def shadow_merge_ref(self, ref):
3464 def shadow_merge_ref(self, ref):
3497 self._shadow_merge_ref = self.reference_to_unicode(ref)
3465 self._shadow_merge_ref = self.reference_to_unicode(ref)
3498
3466
3499 def unicode_to_reference(self, raw):
3467 def unicode_to_reference(self, raw):
3500 """
3468 """
3501 Convert a unicode (or string) to a reference object.
3469 Convert a unicode (or string) to a reference object.
3502 If unicode evaluates to False it returns None.
3470 If unicode evaluates to False it returns None.
3503 """
3471 """
3504 if raw:
3472 if raw:
3505 refs = raw.split(':')
3473 refs = raw.split(':')
3506 return Reference(*refs)
3474 return Reference(*refs)
3507 else:
3475 else:
3508 return None
3476 return None
3509
3477
3510 def reference_to_unicode(self, ref):
3478 def reference_to_unicode(self, ref):
3511 """
3479 """
3512 Convert a reference object to unicode.
3480 Convert a reference object to unicode.
3513 If reference is None it returns None.
3481 If reference is None it returns None.
3514 """
3482 """
3515 if ref:
3483 if ref:
3516 return u':'.join(ref)
3484 return u':'.join(ref)
3517 else:
3485 else:
3518 return None
3486 return None
3519
3487
3520 def get_api_data(self, with_merge_state=True):
3488 def get_api_data(self, with_merge_state=True):
3521 from rhodecode.model.pull_request import PullRequestModel
3489 from rhodecode.model.pull_request import PullRequestModel
3522
3490
3523 pull_request = self
3491 pull_request = self
3524 if with_merge_state:
3492 if with_merge_state:
3525 merge_status = PullRequestModel().merge_status(pull_request)
3493 merge_status = PullRequestModel().merge_status(pull_request)
3526 merge_state = {
3494 merge_state = {
3527 'status': merge_status[0],
3495 'status': merge_status[0],
3528 'message': safe_unicode(merge_status[1]),
3496 'message': safe_unicode(merge_status[1]),
3529 }
3497 }
3530 else:
3498 else:
3531 merge_state = {'status': 'not_available',
3499 merge_state = {'status': 'not_available',
3532 'message': 'not_available'}
3500 'message': 'not_available'}
3533
3501
3534 merge_data = {
3502 merge_data = {
3535 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3503 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3536 'reference': (
3504 'reference': (
3537 pull_request.shadow_merge_ref._asdict()
3505 pull_request.shadow_merge_ref._asdict()
3538 if pull_request.shadow_merge_ref else None),
3506 if pull_request.shadow_merge_ref else None),
3539 }
3507 }
3540
3508
3541 data = {
3509 data = {
3542 'pull_request_id': pull_request.pull_request_id,
3510 'pull_request_id': pull_request.pull_request_id,
3543 'url': PullRequestModel().get_url(pull_request),
3511 'url': PullRequestModel().get_url(pull_request),
3544 'title': pull_request.title,
3512 'title': pull_request.title,
3545 'description': pull_request.description,
3513 'description': pull_request.description,
3546 'status': pull_request.status,
3514 'status': pull_request.status,
3547 'created_on': pull_request.created_on,
3515 'created_on': pull_request.created_on,
3548 'updated_on': pull_request.updated_on,
3516 'updated_on': pull_request.updated_on,
3549 'commit_ids': pull_request.revisions,
3517 'commit_ids': pull_request.revisions,
3550 'review_status': pull_request.calculated_review_status(),
3518 'review_status': pull_request.calculated_review_status(),
3551 'mergeable': merge_state,
3519 'mergeable': merge_state,
3552 'source': {
3520 'source': {
3553 'clone_url': pull_request.source_repo.clone_url(),
3521 'clone_url': pull_request.source_repo.clone_url(),
3554 'repository': pull_request.source_repo.repo_name,
3522 'repository': pull_request.source_repo.repo_name,
3555 'reference': {
3523 'reference': {
3556 'name': pull_request.source_ref_parts.name,
3524 'name': pull_request.source_ref_parts.name,
3557 'type': pull_request.source_ref_parts.type,
3525 'type': pull_request.source_ref_parts.type,
3558 'commit_id': pull_request.source_ref_parts.commit_id,
3526 'commit_id': pull_request.source_ref_parts.commit_id,
3559 },
3527 },
3560 },
3528 },
3561 'target': {
3529 'target': {
3562 'clone_url': pull_request.target_repo.clone_url(),
3530 'clone_url': pull_request.target_repo.clone_url(),
3563 'repository': pull_request.target_repo.repo_name,
3531 'repository': pull_request.target_repo.repo_name,
3564 'reference': {
3532 'reference': {
3565 'name': pull_request.target_ref_parts.name,
3533 'name': pull_request.target_ref_parts.name,
3566 'type': pull_request.target_ref_parts.type,
3534 'type': pull_request.target_ref_parts.type,
3567 'commit_id': pull_request.target_ref_parts.commit_id,
3535 'commit_id': pull_request.target_ref_parts.commit_id,
3568 },
3536 },
3569 },
3537 },
3570 'merge': merge_data,
3538 'merge': merge_data,
3571 'author': pull_request.author.get_api_data(include_secrets=False,
3539 'author': pull_request.author.get_api_data(include_secrets=False,
3572 details='basic'),
3540 details='basic'),
3573 'reviewers': [
3541 'reviewers': [
3574 {
3542 {
3575 'user': reviewer.get_api_data(include_secrets=False,
3543 'user': reviewer.get_api_data(include_secrets=False,
3576 details='basic'),
3544 details='basic'),
3577 'reasons': reasons,
3545 'reasons': reasons,
3578 'review_status': st[0][1].status if st else 'not_reviewed',
3546 'review_status': st[0][1].status if st else 'not_reviewed',
3579 }
3547 }
3580 for reviewer, reasons, mandatory, st in
3548 for reviewer, reasons, mandatory, st in
3581 pull_request.reviewers_statuses()
3549 pull_request.reviewers_statuses()
3582 ]
3550 ]
3583 }
3551 }
3584
3552
3585 return data
3553 return data
3586
3554
3587
3555
3588 class PullRequest(Base, _PullRequestBase):
3556 class PullRequest(Base, _PullRequestBase):
3589 __tablename__ = 'pull_requests'
3557 __tablename__ = 'pull_requests'
3590 __table_args__ = (
3558 __table_args__ = (
3591 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3592 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3593 )
3561 )
3594
3562
3595 pull_request_id = Column(
3563 pull_request_id = Column(
3596 'pull_request_id', Integer(), nullable=False, primary_key=True)
3564 'pull_request_id', Integer(), nullable=False, primary_key=True)
3597
3565
3598 def __repr__(self):
3566 def __repr__(self):
3599 if self.pull_request_id:
3567 if self.pull_request_id:
3600 return '<DB:PullRequest #%s>' % self.pull_request_id
3568 return '<DB:PullRequest #%s>' % self.pull_request_id
3601 else:
3569 else:
3602 return '<DB:PullRequest at %#x>' % id(self)
3570 return '<DB:PullRequest at %#x>' % id(self)
3603
3571
3604 reviewers = relationship('PullRequestReviewers',
3572 reviewers = relationship('PullRequestReviewers',
3605 cascade="all, delete, delete-orphan")
3573 cascade="all, delete, delete-orphan")
3606 statuses = relationship('ChangesetStatus',
3574 statuses = relationship('ChangesetStatus',
3607 cascade="all, delete, delete-orphan")
3575 cascade="all, delete, delete-orphan")
3608 comments = relationship('ChangesetComment',
3576 comments = relationship('ChangesetComment',
3609 cascade="all, delete, delete-orphan")
3577 cascade="all, delete, delete-orphan")
3610 versions = relationship('PullRequestVersion',
3578 versions = relationship('PullRequestVersion',
3611 cascade="all, delete, delete-orphan",
3579 cascade="all, delete, delete-orphan",
3612 lazy='dynamic')
3580 lazy='dynamic')
3613
3581
3614 @classmethod
3582 @classmethod
3615 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3583 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3616 internal_methods=None):
3584 internal_methods=None):
3617
3585
3618 class PullRequestDisplay(object):
3586 class PullRequestDisplay(object):
3619 """
3587 """
3620 Special object wrapper for showing PullRequest data via Versions
3588 Special object wrapper for showing PullRequest data via Versions
3621 It mimics PR object as close as possible. This is read only object
3589 It mimics PR object as close as possible. This is read only object
3622 just for display
3590 just for display
3623 """
3591 """
3624
3592
3625 def __init__(self, attrs, internal=None):
3593 def __init__(self, attrs, internal=None):
3626 self.attrs = attrs
3594 self.attrs = attrs
3627 # internal have priority over the given ones via attrs
3595 # internal have priority over the given ones via attrs
3628 self.internal = internal or ['versions']
3596 self.internal = internal or ['versions']
3629
3597
3630 def __getattr__(self, item):
3598 def __getattr__(self, item):
3631 if item in self.internal:
3599 if item in self.internal:
3632 return getattr(self, item)
3600 return getattr(self, item)
3633 try:
3601 try:
3634 return self.attrs[item]
3602 return self.attrs[item]
3635 except KeyError:
3603 except KeyError:
3636 raise AttributeError(
3604 raise AttributeError(
3637 '%s object has no attribute %s' % (self, item))
3605 '%s object has no attribute %s' % (self, item))
3638
3606
3639 def __repr__(self):
3607 def __repr__(self):
3640 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3608 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3641
3609
3642 def versions(self):
3610 def versions(self):
3643 return pull_request_obj.versions.order_by(
3611 return pull_request_obj.versions.order_by(
3644 PullRequestVersion.pull_request_version_id).all()
3612 PullRequestVersion.pull_request_version_id).all()
3645
3613
3646 def is_closed(self):
3614 def is_closed(self):
3647 return pull_request_obj.is_closed()
3615 return pull_request_obj.is_closed()
3648
3616
3649 @property
3617 @property
3650 def pull_request_version_id(self):
3618 def pull_request_version_id(self):
3651 return getattr(pull_request_obj, 'pull_request_version_id', None)
3619 return getattr(pull_request_obj, 'pull_request_version_id', None)
3652
3620
3653 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3621 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3654
3622
3655 attrs.author = StrictAttributeDict(
3623 attrs.author = StrictAttributeDict(
3656 pull_request_obj.author.get_api_data())
3624 pull_request_obj.author.get_api_data())
3657 if pull_request_obj.target_repo:
3625 if pull_request_obj.target_repo:
3658 attrs.target_repo = StrictAttributeDict(
3626 attrs.target_repo = StrictAttributeDict(
3659 pull_request_obj.target_repo.get_api_data())
3627 pull_request_obj.target_repo.get_api_data())
3660 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3628 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3661
3629
3662 if pull_request_obj.source_repo:
3630 if pull_request_obj.source_repo:
3663 attrs.source_repo = StrictAttributeDict(
3631 attrs.source_repo = StrictAttributeDict(
3664 pull_request_obj.source_repo.get_api_data())
3632 pull_request_obj.source_repo.get_api_data())
3665 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3633 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3666
3634
3667 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3635 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3668 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3636 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3669 attrs.revisions = pull_request_obj.revisions
3637 attrs.revisions = pull_request_obj.revisions
3670
3638
3671 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3639 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3672 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3640 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3673 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3641 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3674
3642
3675 return PullRequestDisplay(attrs, internal=internal_methods)
3643 return PullRequestDisplay(attrs, internal=internal_methods)
3676
3644
3677 def is_closed(self):
3645 def is_closed(self):
3678 return self.status == self.STATUS_CLOSED
3646 return self.status == self.STATUS_CLOSED
3679
3647
3680 def __json__(self):
3648 def __json__(self):
3681 return {
3649 return {
3682 'revisions': self.revisions,
3650 'revisions': self.revisions,
3683 }
3651 }
3684
3652
3685 def calculated_review_status(self):
3653 def calculated_review_status(self):
3686 from rhodecode.model.changeset_status import ChangesetStatusModel
3654 from rhodecode.model.changeset_status import ChangesetStatusModel
3687 return ChangesetStatusModel().calculated_review_status(self)
3655 return ChangesetStatusModel().calculated_review_status(self)
3688
3656
3689 def reviewers_statuses(self):
3657 def reviewers_statuses(self):
3690 from rhodecode.model.changeset_status import ChangesetStatusModel
3658 from rhodecode.model.changeset_status import ChangesetStatusModel
3691 return ChangesetStatusModel().reviewers_statuses(self)
3659 return ChangesetStatusModel().reviewers_statuses(self)
3692
3660
3693 @property
3661 @property
3694 def workspace_id(self):
3662 def workspace_id(self):
3695 from rhodecode.model.pull_request import PullRequestModel
3663 from rhodecode.model.pull_request import PullRequestModel
3696 return PullRequestModel()._workspace_id(self)
3664 return PullRequestModel()._workspace_id(self)
3697
3665
3698 def get_shadow_repo(self):
3666 def get_shadow_repo(self):
3699 workspace_id = self.workspace_id
3667 workspace_id = self.workspace_id
3700 vcs_obj = self.target_repo.scm_instance()
3668 vcs_obj = self.target_repo.scm_instance()
3701 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3669 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3702 workspace_id)
3670 workspace_id)
3703 return vcs_obj._get_shadow_instance(shadow_repository_path)
3671 return vcs_obj._get_shadow_instance(shadow_repository_path)
3704
3672
3705
3673
3706 class PullRequestVersion(Base, _PullRequestBase):
3674 class PullRequestVersion(Base, _PullRequestBase):
3707 __tablename__ = 'pull_request_versions'
3675 __tablename__ = 'pull_request_versions'
3708 __table_args__ = (
3676 __table_args__ = (
3709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3710 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3711 )
3679 )
3712
3680
3713 pull_request_version_id = Column(
3681 pull_request_version_id = Column(
3714 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3682 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3715 pull_request_id = Column(
3683 pull_request_id = Column(
3716 'pull_request_id', Integer(),
3684 'pull_request_id', Integer(),
3717 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3685 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3718 pull_request = relationship('PullRequest')
3686 pull_request = relationship('PullRequest')
3719
3687
3720 def __repr__(self):
3688 def __repr__(self):
3721 if self.pull_request_version_id:
3689 if self.pull_request_version_id:
3722 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3690 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3723 else:
3691 else:
3724 return '<DB:PullRequestVersion at %#x>' % id(self)
3692 return '<DB:PullRequestVersion at %#x>' % id(self)
3725
3693
3726 @property
3694 @property
3727 def reviewers(self):
3695 def reviewers(self):
3728 return self.pull_request.reviewers
3696 return self.pull_request.reviewers
3729
3697
3730 @property
3698 @property
3731 def versions(self):
3699 def versions(self):
3732 return self.pull_request.versions
3700 return self.pull_request.versions
3733
3701
3734 def is_closed(self):
3702 def is_closed(self):
3735 # calculate from original
3703 # calculate from original
3736 return self.pull_request.status == self.STATUS_CLOSED
3704 return self.pull_request.status == self.STATUS_CLOSED
3737
3705
3738 def calculated_review_status(self):
3706 def calculated_review_status(self):
3739 return self.pull_request.calculated_review_status()
3707 return self.pull_request.calculated_review_status()
3740
3708
3741 def reviewers_statuses(self):
3709 def reviewers_statuses(self):
3742 return self.pull_request.reviewers_statuses()
3710 return self.pull_request.reviewers_statuses()
3743
3711
3744
3712
3745 class PullRequestReviewers(Base, BaseModel):
3713 class PullRequestReviewers(Base, BaseModel):
3746 __tablename__ = 'pull_request_reviewers'
3714 __tablename__ = 'pull_request_reviewers'
3747 __table_args__ = (
3715 __table_args__ = (
3748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3749 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3750 )
3718 )
3751
3719
3752 @hybrid_property
3720 @hybrid_property
3753 def reasons(self):
3721 def reasons(self):
3754 if not self._reasons:
3722 if not self._reasons:
3755 return []
3723 return []
3756 return self._reasons
3724 return self._reasons
3757
3725
3758 @reasons.setter
3726 @reasons.setter
3759 def reasons(self, val):
3727 def reasons(self, val):
3760 val = val or []
3728 val = val or []
3761 if any(not isinstance(x, basestring) for x in val):
3729 if any(not isinstance(x, basestring) for x in val):
3762 raise Exception('invalid reasons type, must be list of strings')
3730 raise Exception('invalid reasons type, must be list of strings')
3763 self._reasons = val
3731 self._reasons = val
3764
3732
3765 pull_requests_reviewers_id = Column(
3733 pull_requests_reviewers_id = Column(
3766 'pull_requests_reviewers_id', Integer(), nullable=False,
3734 'pull_requests_reviewers_id', Integer(), nullable=False,
3767 primary_key=True)
3735 primary_key=True)
3768 pull_request_id = Column(
3736 pull_request_id = Column(
3769 "pull_request_id", Integer(),
3737 "pull_request_id", Integer(),
3770 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3738 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3771 user_id = Column(
3739 user_id = Column(
3772 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3740 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3773 _reasons = Column(
3741 _reasons = Column(
3774 'reason', MutationList.as_mutable(
3742 'reason', MutationList.as_mutable(
3775 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3743 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3776 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3744 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3777 user = relationship('User')
3745 user = relationship('User')
3778 pull_request = relationship('PullRequest')
3746 pull_request = relationship('PullRequest')
3779
3747
3780
3748
3781 class Notification(Base, BaseModel):
3749 class Notification(Base, BaseModel):
3782 __tablename__ = 'notifications'
3750 __tablename__ = 'notifications'
3783 __table_args__ = (
3751 __table_args__ = (
3784 Index('notification_type_idx', 'type'),
3752 Index('notification_type_idx', 'type'),
3785 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3786 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3787 )
3755 )
3788
3756
3789 TYPE_CHANGESET_COMMENT = u'cs_comment'
3757 TYPE_CHANGESET_COMMENT = u'cs_comment'
3790 TYPE_MESSAGE = u'message'
3758 TYPE_MESSAGE = u'message'
3791 TYPE_MENTION = u'mention'
3759 TYPE_MENTION = u'mention'
3792 TYPE_REGISTRATION = u'registration'
3760 TYPE_REGISTRATION = u'registration'
3793 TYPE_PULL_REQUEST = u'pull_request'
3761 TYPE_PULL_REQUEST = u'pull_request'
3794 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3762 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3795
3763
3796 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3764 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3797 subject = Column('subject', Unicode(512), nullable=True)
3765 subject = Column('subject', Unicode(512), nullable=True)
3798 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3766 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3799 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3767 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3800 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3768 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3801 type_ = Column('type', Unicode(255))
3769 type_ = Column('type', Unicode(255))
3802
3770
3803 created_by_user = relationship('User')
3771 created_by_user = relationship('User')
3804 notifications_to_users = relationship('UserNotification', lazy='joined',
3772 notifications_to_users = relationship('UserNotification', lazy='joined',
3805 cascade="all, delete, delete-orphan")
3773 cascade="all, delete, delete-orphan")
3806
3774
3807 @property
3775 @property
3808 def recipients(self):
3776 def recipients(self):
3809 return [x.user for x in UserNotification.query()\
3777 return [x.user for x in UserNotification.query()\
3810 .filter(UserNotification.notification == self)\
3778 .filter(UserNotification.notification == self)\
3811 .order_by(UserNotification.user_id.asc()).all()]
3779 .order_by(UserNotification.user_id.asc()).all()]
3812
3780
3813 @classmethod
3781 @classmethod
3814 def create(cls, created_by, subject, body, recipients, type_=None):
3782 def create(cls, created_by, subject, body, recipients, type_=None):
3815 if type_ is None:
3783 if type_ is None:
3816 type_ = Notification.TYPE_MESSAGE
3784 type_ = Notification.TYPE_MESSAGE
3817
3785
3818 notification = cls()
3786 notification = cls()
3819 notification.created_by_user = created_by
3787 notification.created_by_user = created_by
3820 notification.subject = subject
3788 notification.subject = subject
3821 notification.body = body
3789 notification.body = body
3822 notification.type_ = type_
3790 notification.type_ = type_
3823 notification.created_on = datetime.datetime.now()
3791 notification.created_on = datetime.datetime.now()
3824
3792
3825 for u in recipients:
3793 for u in recipients:
3826 assoc = UserNotification()
3794 assoc = UserNotification()
3827 assoc.notification = notification
3795 assoc.notification = notification
3828
3796
3829 # if created_by is inside recipients mark his notification
3797 # if created_by is inside recipients mark his notification
3830 # as read
3798 # as read
3831 if u.user_id == created_by.user_id:
3799 if u.user_id == created_by.user_id:
3832 assoc.read = True
3800 assoc.read = True
3833
3801
3834 u.notifications.append(assoc)
3802 u.notifications.append(assoc)
3835 Session().add(notification)
3803 Session().add(notification)
3836
3804
3837 return notification
3805 return notification
3838
3806
3839
3807
3840 class UserNotification(Base, BaseModel):
3808 class UserNotification(Base, BaseModel):
3841 __tablename__ = 'user_to_notification'
3809 __tablename__ = 'user_to_notification'
3842 __table_args__ = (
3810 __table_args__ = (
3843 UniqueConstraint('user_id', 'notification_id'),
3811 UniqueConstraint('user_id', 'notification_id'),
3844 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3845 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3846 )
3814 )
3847 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3815 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3848 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3816 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3849 read = Column('read', Boolean, default=False)
3817 read = Column('read', Boolean, default=False)
3850 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3818 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3851
3819
3852 user = relationship('User', lazy="joined")
3820 user = relationship('User', lazy="joined")
3853 notification = relationship('Notification', lazy="joined",
3821 notification = relationship('Notification', lazy="joined",
3854 order_by=lambda: Notification.created_on.desc(),)
3822 order_by=lambda: Notification.created_on.desc(),)
3855
3823
3856 def mark_as_read(self):
3824 def mark_as_read(self):
3857 self.read = True
3825 self.read = True
3858 Session().add(self)
3826 Session().add(self)
3859
3827
3860
3828
3861 class Gist(Base, BaseModel):
3829 class Gist(Base, BaseModel):
3862 __tablename__ = 'gists'
3830 __tablename__ = 'gists'
3863 __table_args__ = (
3831 __table_args__ = (
3864 Index('g_gist_access_id_idx', 'gist_access_id'),
3832 Index('g_gist_access_id_idx', 'gist_access_id'),
3865 Index('g_created_on_idx', 'created_on'),
3833 Index('g_created_on_idx', 'created_on'),
3866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3867 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3868 )
3836 )
3869 GIST_PUBLIC = u'public'
3837 GIST_PUBLIC = u'public'
3870 GIST_PRIVATE = u'private'
3838 GIST_PRIVATE = u'private'
3871 DEFAULT_FILENAME = u'gistfile1.txt'
3839 DEFAULT_FILENAME = u'gistfile1.txt'
3872
3840
3873 ACL_LEVEL_PUBLIC = u'acl_public'
3841 ACL_LEVEL_PUBLIC = u'acl_public'
3874 ACL_LEVEL_PRIVATE = u'acl_private'
3842 ACL_LEVEL_PRIVATE = u'acl_private'
3875
3843
3876 gist_id = Column('gist_id', Integer(), primary_key=True)
3844 gist_id = Column('gist_id', Integer(), primary_key=True)
3877 gist_access_id = Column('gist_access_id', Unicode(250))
3845 gist_access_id = Column('gist_access_id', Unicode(250))
3878 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3846 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3879 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3847 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3880 gist_expires = Column('gist_expires', Float(53), nullable=False)
3848 gist_expires = Column('gist_expires', Float(53), nullable=False)
3881 gist_type = Column('gist_type', Unicode(128), nullable=False)
3849 gist_type = Column('gist_type', Unicode(128), nullable=False)
3882 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3850 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3883 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3851 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3884 acl_level = Column('acl_level', Unicode(128), nullable=True)
3852 acl_level = Column('acl_level', Unicode(128), nullable=True)
3885
3853
3886 owner = relationship('User')
3854 owner = relationship('User')
3887
3855
3888 def __repr__(self):
3856 def __repr__(self):
3889 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3857 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3890
3858
3891 @hybrid_property
3859 @hybrid_property
3892 def description_safe(self):
3860 def description_safe(self):
3893 from rhodecode.lib import helpers as h
3861 from rhodecode.lib import helpers as h
3894 return h.escape(self.gist_description)
3862 return h.escape(self.gist_description)
3895
3863
3896 @classmethod
3864 @classmethod
3897 def get_or_404(cls, id_):
3865 def get_or_404(cls, id_):
3898 from pyramid.httpexceptions import HTTPNotFound
3866 from pyramid.httpexceptions import HTTPNotFound
3899
3867
3900 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3868 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3901 if not res:
3869 if not res:
3902 raise HTTPNotFound()
3870 raise HTTPNotFound()
3903 return res
3871 return res
3904
3872
3905 @classmethod
3873 @classmethod
3906 def get_by_access_id(cls, gist_access_id):
3874 def get_by_access_id(cls, gist_access_id):
3907 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3875 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3908
3876
3909 def gist_url(self):
3877 def gist_url(self):
3910 from rhodecode.model.gist import GistModel
3878 from rhodecode.model.gist import GistModel
3911 return GistModel().get_url(self)
3879 return GistModel().get_url(self)
3912
3880
3913 @classmethod
3881 @classmethod
3914 def base_path(cls):
3882 def base_path(cls):
3915 """
3883 """
3916 Returns base path when all gists are stored
3884 Returns base path when all gists are stored
3917
3885
3918 :param cls:
3886 :param cls:
3919 """
3887 """
3920 from rhodecode.model.gist import GIST_STORE_LOC
3888 from rhodecode.model.gist import GIST_STORE_LOC
3921 q = Session().query(RhodeCodeUi)\
3889 q = Session().query(RhodeCodeUi)\
3922 .filter(RhodeCodeUi.ui_key == URL_SEP)
3890 .filter(RhodeCodeUi.ui_key == URL_SEP)
3923 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3891 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3924 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3892 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3925
3893
3926 def get_api_data(self):
3894 def get_api_data(self):
3927 """
3895 """
3928 Common function for generating gist related data for API
3896 Common function for generating gist related data for API
3929 """
3897 """
3930 gist = self
3898 gist = self
3931 data = {
3899 data = {
3932 'gist_id': gist.gist_id,
3900 'gist_id': gist.gist_id,
3933 'type': gist.gist_type,
3901 'type': gist.gist_type,
3934 'access_id': gist.gist_access_id,
3902 'access_id': gist.gist_access_id,
3935 'description': gist.gist_description,
3903 'description': gist.gist_description,
3936 'url': gist.gist_url(),
3904 'url': gist.gist_url(),
3937 'expires': gist.gist_expires,
3905 'expires': gist.gist_expires,
3938 'created_on': gist.created_on,
3906 'created_on': gist.created_on,
3939 'modified_at': gist.modified_at,
3907 'modified_at': gist.modified_at,
3940 'content': None,
3908 'content': None,
3941 'acl_level': gist.acl_level,
3909 'acl_level': gist.acl_level,
3942 }
3910 }
3943 return data
3911 return data
3944
3912
3945 def __json__(self):
3913 def __json__(self):
3946 data = dict(
3914 data = dict(
3947 )
3915 )
3948 data.update(self.get_api_data())
3916 data.update(self.get_api_data())
3949 return data
3917 return data
3950 # SCM functions
3918 # SCM functions
3951
3919
3952 def scm_instance(self, **kwargs):
3920 def scm_instance(self, **kwargs):
3953 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3921 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3954 return get_vcs_instance(
3922 return get_vcs_instance(
3955 repo_path=safe_str(full_repo_path), create=False)
3923 repo_path=safe_str(full_repo_path), create=False)
3956
3924
3957
3925
3958 class ExternalIdentity(Base, BaseModel):
3926 class ExternalIdentity(Base, BaseModel):
3959 __tablename__ = 'external_identities'
3927 __tablename__ = 'external_identities'
3960 __table_args__ = (
3928 __table_args__ = (
3961 Index('local_user_id_idx', 'local_user_id'),
3929 Index('local_user_id_idx', 'local_user_id'),
3962 Index('external_id_idx', 'external_id'),
3930 Index('external_id_idx', 'external_id'),
3963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3964 'mysql_charset': 'utf8'})
3932 'mysql_charset': 'utf8'})
3965
3933
3966 external_id = Column('external_id', Unicode(255), default=u'',
3934 external_id = Column('external_id', Unicode(255), default=u'',
3967 primary_key=True)
3935 primary_key=True)
3968 external_username = Column('external_username', Unicode(1024), default=u'')
3936 external_username = Column('external_username', Unicode(1024), default=u'')
3969 local_user_id = Column('local_user_id', Integer(),
3937 local_user_id = Column('local_user_id', Integer(),
3970 ForeignKey('users.user_id'), primary_key=True)
3938 ForeignKey('users.user_id'), primary_key=True)
3971 provider_name = Column('provider_name', Unicode(255), default=u'',
3939 provider_name = Column('provider_name', Unicode(255), default=u'',
3972 primary_key=True)
3940 primary_key=True)
3973 access_token = Column('access_token', String(1024), default=u'')
3941 access_token = Column('access_token', String(1024), default=u'')
3974 alt_token = Column('alt_token', String(1024), default=u'')
3942 alt_token = Column('alt_token', String(1024), default=u'')
3975 token_secret = Column('token_secret', String(1024), default=u'')
3943 token_secret = Column('token_secret', String(1024), default=u'')
3976
3944
3977 @classmethod
3945 @classmethod
3978 def by_external_id_and_provider(cls, external_id, provider_name,
3946 def by_external_id_and_provider(cls, external_id, provider_name,
3979 local_user_id=None):
3947 local_user_id=None):
3980 """
3948 """
3981 Returns ExternalIdentity instance based on search params
3949 Returns ExternalIdentity instance based on search params
3982
3950
3983 :param external_id:
3951 :param external_id:
3984 :param provider_name:
3952 :param provider_name:
3985 :return: ExternalIdentity
3953 :return: ExternalIdentity
3986 """
3954 """
3987 query = cls.query()
3955 query = cls.query()
3988 query = query.filter(cls.external_id == external_id)
3956 query = query.filter(cls.external_id == external_id)
3989 query = query.filter(cls.provider_name == provider_name)
3957 query = query.filter(cls.provider_name == provider_name)
3990 if local_user_id:
3958 if local_user_id:
3991 query = query.filter(cls.local_user_id == local_user_id)
3959 query = query.filter(cls.local_user_id == local_user_id)
3992 return query.first()
3960 return query.first()
3993
3961
3994 @classmethod
3962 @classmethod
3995 def user_by_external_id_and_provider(cls, external_id, provider_name):
3963 def user_by_external_id_and_provider(cls, external_id, provider_name):
3996 """
3964 """
3997 Returns User instance based on search params
3965 Returns User instance based on search params
3998
3966
3999 :param external_id:
3967 :param external_id:
4000 :param provider_name:
3968 :param provider_name:
4001 :return: User
3969 :return: User
4002 """
3970 """
4003 query = User.query()
3971 query = User.query()
4004 query = query.filter(cls.external_id == external_id)
3972 query = query.filter(cls.external_id == external_id)
4005 query = query.filter(cls.provider_name == provider_name)
3973 query = query.filter(cls.provider_name == provider_name)
4006 query = query.filter(User.user_id == cls.local_user_id)
3974 query = query.filter(User.user_id == cls.local_user_id)
4007 return query.first()
3975 return query.first()
4008
3976
4009 @classmethod
3977 @classmethod
4010 def by_local_user_id(cls, local_user_id):
3978 def by_local_user_id(cls, local_user_id):
4011 """
3979 """
4012 Returns all tokens for user
3980 Returns all tokens for user
4013
3981
4014 :param local_user_id:
3982 :param local_user_id:
4015 :return: ExternalIdentity
3983 :return: ExternalIdentity
4016 """
3984 """
4017 query = cls.query()
3985 query = cls.query()
4018 query = query.filter(cls.local_user_id == local_user_id)
3986 query = query.filter(cls.local_user_id == local_user_id)
4019 return query
3987 return query
4020
3988
4021
3989
4022 class Integration(Base, BaseModel):
3990 class Integration(Base, BaseModel):
4023 __tablename__ = 'integrations'
3991 __tablename__ = 'integrations'
4024 __table_args__ = (
3992 __table_args__ = (
4025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4027 )
3995 )
4028
3996
4029 integration_id = Column('integration_id', Integer(), primary_key=True)
3997 integration_id = Column('integration_id', Integer(), primary_key=True)
4030 integration_type = Column('integration_type', String(255))
3998 integration_type = Column('integration_type', String(255))
4031 enabled = Column('enabled', Boolean(), nullable=False)
3999 enabled = Column('enabled', Boolean(), nullable=False)
4032 name = Column('name', String(255), nullable=False)
4000 name = Column('name', String(255), nullable=False)
4033 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4001 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4034 default=False)
4002 default=False)
4035
4003
4036 settings = Column(
4004 settings = Column(
4037 'settings_json', MutationObj.as_mutable(
4005 'settings_json', MutationObj.as_mutable(
4038 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4006 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4039 repo_id = Column(
4007 repo_id = Column(
4040 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4008 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4041 nullable=True, unique=None, default=None)
4009 nullable=True, unique=None, default=None)
4042 repo = relationship('Repository', lazy='joined')
4010 repo = relationship('Repository', lazy='joined')
4043
4011
4044 repo_group_id = Column(
4012 repo_group_id = Column(
4045 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4013 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4046 nullable=True, unique=None, default=None)
4014 nullable=True, unique=None, default=None)
4047 repo_group = relationship('RepoGroup', lazy='joined')
4015 repo_group = relationship('RepoGroup', lazy='joined')
4048
4016
4049 @property
4017 @property
4050 def scope(self):
4018 def scope(self):
4051 if self.repo:
4019 if self.repo:
4052 return repr(self.repo)
4020 return repr(self.repo)
4053 if self.repo_group:
4021 if self.repo_group:
4054 if self.child_repos_only:
4022 if self.child_repos_only:
4055 return repr(self.repo_group) + ' (child repos only)'
4023 return repr(self.repo_group) + ' (child repos only)'
4056 else:
4024 else:
4057 return repr(self.repo_group) + ' (recursive)'
4025 return repr(self.repo_group) + ' (recursive)'
4058 if self.child_repos_only:
4026 if self.child_repos_only:
4059 return 'root_repos'
4027 return 'root_repos'
4060 return 'global'
4028 return 'global'
4061
4029
4062 def __repr__(self):
4030 def __repr__(self):
4063 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4031 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4064
4032
4065
4033
4066 class RepoReviewRuleUser(Base, BaseModel):
4034 class RepoReviewRuleUser(Base, BaseModel):
4067 __tablename__ = 'repo_review_rules_users'
4035 __tablename__ = 'repo_review_rules_users'
4068 __table_args__ = (
4036 __table_args__ = (
4069 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4070 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4071 )
4039 )
4072 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4040 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4073 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4041 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4074 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4075 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4043 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4076 user = relationship('User')
4044 user = relationship('User')
4077
4045
4078 def rule_data(self):
4046 def rule_data(self):
4079 return {
4047 return {
4080 'mandatory': self.mandatory
4048 'mandatory': self.mandatory
4081 }
4049 }
4082
4050
4083
4051
4084 class RepoReviewRuleUserGroup(Base, BaseModel):
4052 class RepoReviewRuleUserGroup(Base, BaseModel):
4085 __tablename__ = 'repo_review_rules_users_groups'
4053 __tablename__ = 'repo_review_rules_users_groups'
4086 __table_args__ = (
4054 __table_args__ = (
4087 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4088 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4089 )
4057 )
4090 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4058 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4091 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4059 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4092 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4060 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4093 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4061 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4094 users_group = relationship('UserGroup')
4062 users_group = relationship('UserGroup')
4095
4063
4096 def rule_data(self):
4064 def rule_data(self):
4097 return {
4065 return {
4098 'mandatory': self.mandatory
4066 'mandatory': self.mandatory
4099 }
4067 }
4100
4068
4101
4069
4102 class RepoReviewRule(Base, BaseModel):
4070 class RepoReviewRule(Base, BaseModel):
4103 __tablename__ = 'repo_review_rules'
4071 __tablename__ = 'repo_review_rules'
4104 __table_args__ = (
4072 __table_args__ = (
4105 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4106 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4107 )
4075 )
4108
4076
4109 repo_review_rule_id = Column(
4077 repo_review_rule_id = Column(
4110 'repo_review_rule_id', Integer(), primary_key=True)
4078 'repo_review_rule_id', Integer(), primary_key=True)
4111 repo_id = Column(
4079 repo_id = Column(
4112 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4080 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4113 repo = relationship('Repository', backref='review_rules')
4081 repo = relationship('Repository', backref='review_rules')
4114
4082
4115 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4083 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4116 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4084 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4117
4085
4118 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4086 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4119 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4087 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4120 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4088 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4121 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4089 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4122
4090
4123 rule_users = relationship('RepoReviewRuleUser')
4091 rule_users = relationship('RepoReviewRuleUser')
4124 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4092 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4125
4093
4126 @hybrid_property
4094 @hybrid_property
4127 def branch_pattern(self):
4095 def branch_pattern(self):
4128 return self._branch_pattern or '*'
4096 return self._branch_pattern or '*'
4129
4097
4130 def _validate_glob(self, value):
4098 def _validate_glob(self, value):
4131 re.compile('^' + glob2re(value) + '$')
4099 re.compile('^' + glob2re(value) + '$')
4132
4100
4133 @branch_pattern.setter
4101 @branch_pattern.setter
4134 def branch_pattern(self, value):
4102 def branch_pattern(self, value):
4135 self._validate_glob(value)
4103 self._validate_glob(value)
4136 self._branch_pattern = value or '*'
4104 self._branch_pattern = value or '*'
4137
4105
4138 @hybrid_property
4106 @hybrid_property
4139 def file_pattern(self):
4107 def file_pattern(self):
4140 return self._file_pattern or '*'
4108 return self._file_pattern or '*'
4141
4109
4142 @file_pattern.setter
4110 @file_pattern.setter
4143 def file_pattern(self, value):
4111 def file_pattern(self, value):
4144 self._validate_glob(value)
4112 self._validate_glob(value)
4145 self._file_pattern = value or '*'
4113 self._file_pattern = value or '*'
4146
4114
4147 def matches(self, branch, files_changed):
4115 def matches(self, branch, files_changed):
4148 """
4116 """
4149 Check if this review rule matches a branch/files in a pull request
4117 Check if this review rule matches a branch/files in a pull request
4150
4118
4151 :param branch: branch name for the commit
4119 :param branch: branch name for the commit
4152 :param files_changed: list of file paths changed in the pull request
4120 :param files_changed: list of file paths changed in the pull request
4153 """
4121 """
4154
4122
4155 branch = branch or ''
4123 branch = branch or ''
4156 files_changed = files_changed or []
4124 files_changed = files_changed or []
4157
4125
4158 branch_matches = True
4126 branch_matches = True
4159 if branch:
4127 if branch:
4160 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4128 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4161 branch_matches = bool(branch_regex.search(branch))
4129 branch_matches = bool(branch_regex.search(branch))
4162
4130
4163 files_matches = True
4131 files_matches = True
4164 if self.file_pattern != '*':
4132 if self.file_pattern != '*':
4165 files_matches = False
4133 files_matches = False
4166 file_regex = re.compile(glob2re(self.file_pattern))
4134 file_regex = re.compile(glob2re(self.file_pattern))
4167 for filename in files_changed:
4135 for filename in files_changed:
4168 if file_regex.search(filename):
4136 if file_regex.search(filename):
4169 files_matches = True
4137 files_matches = True
4170 break
4138 break
4171
4139
4172 return branch_matches and files_matches
4140 return branch_matches and files_matches
4173
4141
4174 @property
4142 @property
4175 def review_users(self):
4143 def review_users(self):
4176 """ Returns the users which this rule applies to """
4144 """ Returns the users which this rule applies to """
4177
4145
4178 users = collections.OrderedDict()
4146 users = collections.OrderedDict()
4179
4147
4180 for rule_user in self.rule_users:
4148 for rule_user in self.rule_users:
4181 if rule_user.user.active:
4149 if rule_user.user.active:
4182 if rule_user.user not in users:
4150 if rule_user.user not in users:
4183 users[rule_user.user.username] = {
4151 users[rule_user.user.username] = {
4184 'user': rule_user.user,
4152 'user': rule_user.user,
4185 'source': 'user',
4153 'source': 'user',
4186 'source_data': {},
4154 'source_data': {},
4187 'data': rule_user.rule_data()
4155 'data': rule_user.rule_data()
4188 }
4156 }
4189
4157
4190 for rule_user_group in self.rule_user_groups:
4158 for rule_user_group in self.rule_user_groups:
4191 source_data = {
4159 source_data = {
4192 'name': rule_user_group.users_group.users_group_name,
4160 'name': rule_user_group.users_group.users_group_name,
4193 'members': len(rule_user_group.users_group.members)
4161 'members': len(rule_user_group.users_group.members)
4194 }
4162 }
4195 for member in rule_user_group.users_group.members:
4163 for member in rule_user_group.users_group.members:
4196 if member.user.active:
4164 if member.user.active:
4197 users[member.user.username] = {
4165 users[member.user.username] = {
4198 'user': member.user,
4166 'user': member.user,
4199 'source': 'user_group',
4167 'source': 'user_group',
4200 'source_data': source_data,
4168 'source_data': source_data,
4201 'data': rule_user_group.rule_data()
4169 'data': rule_user_group.rule_data()
4202 }
4170 }
4203
4171
4204 return users
4172 return users
4205
4173
4206 def __repr__(self):
4174 def __repr__(self):
4207 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4175 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4208 self.repo_review_rule_id, self.repo)
4176 self.repo_review_rule_id, self.repo)
4209
4177
4210
4178
4211 class ScheduleEntry(Base, BaseModel):
4179 class ScheduleEntry(Base, BaseModel):
4212 __tablename__ = 'schedule_entries'
4180 __tablename__ = 'schedule_entries'
4213 __table_args__ = (
4181 __table_args__ = (
4214 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4182 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4215 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4183 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4216 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4217 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4218 )
4186 )
4219 schedule_types = ['crontab', 'timedelta', 'integer']
4187 schedule_types = ['crontab', 'timedelta', 'integer']
4220 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4188 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4221
4189
4222 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4190 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4223 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4191 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4224 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4192 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4225
4193
4226 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4194 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4227 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4195 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4228
4196
4229 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4197 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4230 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4198 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4231
4199
4232 # task
4200 # task
4233 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4201 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4234 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4202 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4235 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4203 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4236 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4204 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4237
4205
4238 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4206 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4239 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4207 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4240
4208
4241 @hybrid_property
4209 @hybrid_property
4242 def schedule_type(self):
4210 def schedule_type(self):
4243 return self._schedule_type
4211 return self._schedule_type
4244
4212
4245 @schedule_type.setter
4213 @schedule_type.setter
4246 def schedule_type(self, val):
4214 def schedule_type(self, val):
4247 if val not in self.schedule_types:
4215 if val not in self.schedule_types:
4248 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4216 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4249 val, self.schedule_type))
4217 val, self.schedule_type))
4250
4218
4251 self._schedule_type = val
4219 self._schedule_type = val
4252
4220
4253 @classmethod
4221 @classmethod
4254 def get_uid(cls, obj):
4222 def get_uid(cls, obj):
4255 args = obj.task_args
4223 args = obj.task_args
4256 kwargs = obj.task_kwargs
4224 kwargs = obj.task_kwargs
4257 if isinstance(args, JsonRaw):
4225 if isinstance(args, JsonRaw):
4258 try:
4226 try:
4259 args = json.loads(args)
4227 args = json.loads(args)
4260 except ValueError:
4228 except ValueError:
4261 args = tuple()
4229 args = tuple()
4262
4230
4263 if isinstance(kwargs, JsonRaw):
4231 if isinstance(kwargs, JsonRaw):
4264 try:
4232 try:
4265 kwargs = json.loads(kwargs)
4233 kwargs = json.loads(kwargs)
4266 except ValueError:
4234 except ValueError:
4267 kwargs = dict()
4235 kwargs = dict()
4268
4236
4269 dot_notation = obj.task_dot_notation
4237 dot_notation = obj.task_dot_notation
4270 val = '.'.join(map(safe_str, [
4238 val = '.'.join(map(safe_str, [
4271 sorted(dot_notation), args, sorted(kwargs.items())]))
4239 sorted(dot_notation), args, sorted(kwargs.items())]))
4272 return hashlib.sha1(val).hexdigest()
4240 return hashlib.sha1(val).hexdigest()
4273
4241
4274 @classmethod
4242 @classmethod
4275 def get_by_schedule_name(cls, schedule_name):
4243 def get_by_schedule_name(cls, schedule_name):
4276 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4244 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4277
4245
4278 @classmethod
4246 @classmethod
4279 def get_by_schedule_id(cls, schedule_id):
4247 def get_by_schedule_id(cls, schedule_id):
4280 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4248 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4281
4249
4282 @property
4250 @property
4283 def task(self):
4251 def task(self):
4284 return self.task_dot_notation
4252 return self.task_dot_notation
4285
4253
4286 @property
4254 @property
4287 def schedule(self):
4255 def schedule(self):
4288 from rhodecode.lib.celerylib.utils import raw_2_schedule
4256 from rhodecode.lib.celerylib.utils import raw_2_schedule
4289 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4257 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4290 return schedule
4258 return schedule
4291
4259
4292 @property
4260 @property
4293 def args(self):
4261 def args(self):
4294 try:
4262 try:
4295 return list(self.task_args or [])
4263 return list(self.task_args or [])
4296 except ValueError:
4264 except ValueError:
4297 return list()
4265 return list()
4298
4266
4299 @property
4267 @property
4300 def kwargs(self):
4268 def kwargs(self):
4301 try:
4269 try:
4302 return dict(self.task_kwargs or {})
4270 return dict(self.task_kwargs or {})
4303 except ValueError:
4271 except ValueError:
4304 return dict()
4272 return dict()
4305
4273
4306 def _as_raw(self, val):
4274 def _as_raw(self, val):
4307 if hasattr(val, 'de_coerce'):
4275 if hasattr(val, 'de_coerce'):
4308 val = val.de_coerce()
4276 val = val.de_coerce()
4309 if val:
4277 if val:
4310 val = json.dumps(val)
4278 val = json.dumps(val)
4311
4279
4312 return val
4280 return val
4313
4281
4314 @property
4282 @property
4315 def schedule_definition_raw(self):
4283 def schedule_definition_raw(self):
4316 return self._as_raw(self.schedule_definition)
4284 return self._as_raw(self.schedule_definition)
4317
4285
4318 @property
4286 @property
4319 def args_raw(self):
4287 def args_raw(self):
4320 return self._as_raw(self.task_args)
4288 return self._as_raw(self.task_args)
4321
4289
4322 @property
4290 @property
4323 def kwargs_raw(self):
4291 def kwargs_raw(self):
4324 return self._as_raw(self.task_kwargs)
4292 return self._as_raw(self.task_kwargs)
4325
4293
4326 def __repr__(self):
4294 def __repr__(self):
4327 return '<DB:ScheduleEntry({}:{})>'.format(
4295 return '<DB:ScheduleEntry({}:{})>'.format(
4328 self.schedule_entry_id, self.schedule_name)
4296 self.schedule_entry_id, self.schedule_name)
4329
4297
4330
4298
4331 @event.listens_for(ScheduleEntry, 'before_update')
4299 @event.listens_for(ScheduleEntry, 'before_update')
4332 def update_task_uid(mapper, connection, target):
4300 def update_task_uid(mapper, connection, target):
4333 target.task_uid = ScheduleEntry.get_uid(target)
4301 target.task_uid = ScheduleEntry.get_uid(target)
4334
4302
4335
4303
4336 @event.listens_for(ScheduleEntry, 'before_insert')
4304 @event.listens_for(ScheduleEntry, 'before_insert')
4337 def set_task_uid(mapper, connection, target):
4305 def set_task_uid(mapper, connection, target):
4338 target.task_uid = ScheduleEntry.get_uid(target)
4306 target.task_uid = ScheduleEntry.get_uid(target)
4339
4307
4340
4308
4341 class DbMigrateVersion(Base, BaseModel):
4309 class DbMigrateVersion(Base, BaseModel):
4342 __tablename__ = 'db_migrate_version'
4310 __tablename__ = 'db_migrate_version'
4343 __table_args__ = (
4311 __table_args__ = (
4344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4346 )
4314 )
4347 repository_id = Column('repository_id', String(250), primary_key=True)
4315 repository_id = Column('repository_id', String(250), primary_key=True)
4348 repository_path = Column('repository_path', Text)
4316 repository_path = Column('repository_path', Text)
4349 version = Column('version', Integer)
4317 version = Column('version', Integer)
4350
4318
4351
4319
4352 class DbSession(Base, BaseModel):
4320 class DbSession(Base, BaseModel):
4353 __tablename__ = 'db_session'
4321 __tablename__ = 'db_session'
4354 __table_args__ = (
4322 __table_args__ = (
4355 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4356 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4324 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4357 )
4325 )
4358
4326
4359 def __repr__(self):
4327 def __repr__(self):
4360 return '<DB:DbSession({})>'.format(self.id)
4328 return '<DB:DbSession({})>'.format(self.id)
4361
4329
4362 id = Column('id', Integer())
4330 id = Column('id', Integer())
4363 namespace = Column('namespace', String(255), primary_key=True)
4331 namespace = Column('namespace', String(255), primary_key=True)
4364 accessed = Column('accessed', DateTime, nullable=False)
4332 accessed = Column('accessed', DateTime, nullable=False)
4365 created = Column('created', DateTime, nullable=False)
4333 created = Column('created', DateTime, nullable=False)
4366 data = Column('data', PickleType, nullable=False)
4334 data = Column('data', PickleType, nullable=False)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now