##// 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 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: 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