##// END OF EJS Templates
encryption: Implement a slightly improved AesCipher encryption....
marcink -
r281:f41dae1c default
parent child Browse files
Show More
@@ -1,614 +1,618 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode Enterprise - configuration file #
4 4 # Built-in functions and variables #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 # #
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10 debug = true
11 11 ################################################################################
12 12 ## Uncomment and replace with the email address which should receive ##
13 13 ## any error reports after an application crash ##
14 14 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20 #email_prefix = [RhodeCode]
21 21
22 22 #smtp_server = mail.server.com
23 23 #smtp_username =
24 24 #smtp_password =
25 25 #smtp_port =
26 26 #smtp_use_tls = false
27 27 #smtp_use_ssl = true
28 28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 29 #smtp_auth =
30 30
31 31 [server:main]
32 32 ## COMMON ##
33 33 host = 127.0.0.1
34 34 port = 5000
35 35
36 36 ##################################
37 37 ## WAITRESS WSGI SERVER ##
38 38 ## Recommended for Development ##
39 39 ##################################
40 40 use = egg:waitress#main
41 41 ## number of worker threads
42 42 threads = 5
43 43 ## MAX BODY SIZE 100GB
44 44 max_request_body_size = 107374182400
45 45 ## Use poll instead of select, fixes file descriptors limits problems.
46 46 ## May not work on old windows systems.
47 47 asyncore_use_poll = true
48 48
49 49
50 50 ##########################
51 51 ## GUNICORN WSGI SERVER ##
52 52 ##########################
53 53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 54 #use = egg:gunicorn#main
55 55 ## Sets the number of process workers. You must set `instance_id = *`
56 56 ## when this option is set to more than one worker, recommended
57 57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 58 ## The `instance_id = *` must be set in the [app:main] section below
59 59 #workers = 2
60 60 ## number of threads for each of the worker, must be set to 1 for gevent
61 61 ## generally recommened to be at 1
62 62 #threads = 1
63 63 ## process name
64 64 #proc_name = rhodecode
65 65 ## type of worker class, one of sync, gevent
66 66 ## recommended for bigger setup is using of of other than sync one
67 67 #worker_class = sync
68 68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 69 #worker_connections = 10
70 70 ## max number of requests that worker will handle before being gracefully
71 71 ## restarted, could prevent memory leaks
72 72 #max_requests = 1000
73 73 #max_requests_jitter = 30
74 74 ## amount of time a worker can spend with handling a request before it
75 75 ## gets killed and restarted. Set to 6hrs
76 76 #timeout = 21600
77 77
78 78
79 79 ## prefix middleware for RhodeCode, disables force_https flag.
80 80 ## allows to set RhodeCode under a prefix in server.
81 81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 82 #[filter:proxy-prefix]
83 83 #use = egg:PasteDeploy#prefix
84 84 #prefix = /<your-prefix>
85 85
86 86 [app:main]
87 87 use = egg:rhodecode-enterprise-ce
88 88 ## enable proxy prefix middleware, defined below
89 89 #filter-with = proxy-prefix
90 90
91 91 # During development the we want to have the debug toolbar enabled
92 92 pyramid.includes =
93 93 pyramid_debugtoolbar
94 94 rhodecode.utils.debugtoolbar
95 95 rhodecode.lib.middleware.request_wrapper
96 96
97 97 pyramid.reload_templates = true
98 98
99 99 debugtoolbar.hosts = 0.0.0.0/0
100 100 debugtoolbar.exclude_prefixes =
101 101 /css
102 102 /fonts
103 103 /images
104 104 /js
105 105
106 106 ## RHODECODE PLUGINS ##
107 107 rhodecode.includes =
108 108 rhodecode.api
109 109
110 110
111 111 # api prefix url
112 112 rhodecode.api.url = /_admin/api
113 113
114 114
115 115 ## END RHODECODE PLUGINS ##
116 116
117 117 ## encryption key used to encrypt social plugin tokens,
118 118 ## remote_urls with credentials etc, if not set it defaults to
119 119 ## `beaker.session.secret`
120 120 #rhodecode.encrypted_values.secret =
121 121
122 ## decryption strict mode (enabled by default). It controls if decryption raises
123 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
124 #rhodecode.encrypted_values.strict = false
125
122 126 full_stack = true
123 127
124 128 ## Serve static files via RhodeCode, disable to serve them via HTTP server
125 129 static_files = true
126 130
127 131 # autogenerate javascript routes file on startup
128 132 generate_js_files = false
129 133
130 134 ## Optional Languages
131 135 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
132 136 lang = en
133 137
134 138 ## perform a full repository scan on each server start, this should be
135 139 ## set to false after first startup, to allow faster server restarts.
136 140 startup.import_repos = false
137 141
138 142 ## Uncomment and set this path to use archive download cache.
139 143 ## Once enabled, generated archives will be cached at this location
140 144 ## and served from the cache during subsequent requests for the same archive of
141 145 ## the repository.
142 146 #archive_cache_dir = /tmp/tarballcache
143 147
144 148 ## change this to unique ID for security
145 149 app_instance_uuid = rc-production
146 150
147 151 ## cut off limit for large diffs (size in bytes)
148 152 cut_off_limit_diff = 1024000
149 153 cut_off_limit_file = 256000
150 154
151 155 ## use cache version of scm repo everywhere
152 156 vcs_full_cache = true
153 157
154 158 ## force https in RhodeCode, fixes https redirects, assumes it's always https
155 159 ## Normally this is controlled by proper http flags sent from http server
156 160 force_https = false
157 161
158 162 ## use Strict-Transport-Security headers
159 163 use_htsts = false
160 164
161 165 ## number of commits stats will parse on each iteration
162 166 commit_parse_limit = 25
163 167
164 168 ## git rev filter option, --all is the default filter, if you need to
165 169 ## hide all refs in changelog switch this to --branches --tags
166 170 git_rev_filter = --branches --tags
167 171
168 172 # Set to true if your repos are exposed using the dumb protocol
169 173 git_update_server_info = false
170 174
171 175 ## RSS/ATOM feed options
172 176 rss_cut_off_limit = 256000
173 177 rss_items_per_page = 10
174 178 rss_include_diff = false
175 179
176 180 ## gist URL alias, used to create nicer urls for gist. This should be an
177 181 ## url that does rewrites to _admin/gists/<gistid>.
178 182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
179 183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
180 184 gist_alias_url =
181 185
182 186 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
183 187 ## used for access.
184 188 ## Adding ?auth_token = <token> to the url authenticates this request as if it
185 189 ## came from the the logged in user who own this authentication token.
186 190 ##
187 191 ## Syntax is <ControllerClass>:<function_pattern>.
188 192 ## To enable access to raw_files put `FilesController:raw`.
189 193 ## To enable access to patches add `ChangesetController:changeset_patch`.
190 194 ## The list should be "," separated and on a single line.
191 195 ##
192 196 ## Recommended controllers to enable:
193 197 # ChangesetController:changeset_patch,
194 198 # ChangesetController:changeset_raw,
195 199 # FilesController:raw,
196 200 # FilesController:archivefile,
197 201 # GistsController:*,
198 202 api_access_controllers_whitelist =
199 203
200 204 ## default encoding used to convert from and to unicode
201 205 ## can be also a comma separated list of encoding in case of mixed encodings
202 206 default_encoding = UTF-8
203 207
204 208 ## instance-id prefix
205 209 ## a prefix key for this instance used for cache invalidation when running
206 210 ## multiple instances of rhodecode, make sure it's globally unique for
207 211 ## all running rhodecode instances. Leave empty if you don't use it
208 212 instance_id =
209 213
210 214 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
211 215 ## of an authentication plugin also if it is disabled by it's settings.
212 216 ## This could be useful if you are unable to log in to the system due to broken
213 217 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
214 218 ## module to log in again and fix the settings.
215 219 ##
216 220 ## Available builtin plugin IDs (hash is part of the ID):
217 221 ## egg:rhodecode-enterprise-ce#rhodecode
218 222 ## egg:rhodecode-enterprise-ce#pam
219 223 ## egg:rhodecode-enterprise-ce#ldap
220 224 ## egg:rhodecode-enterprise-ce#jasig_cas
221 225 ## egg:rhodecode-enterprise-ce#headers
222 226 ## egg:rhodecode-enterprise-ce#crowd
223 227 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
224 228
225 229 ## alternative return HTTP header for failed authentication. Default HTTP
226 230 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
227 231 ## handling that causing a series of failed authentication calls.
228 232 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
229 233 ## This will be served instead of default 401 on bad authnetication
230 234 auth_ret_code =
231 235
232 236 ## use special detection method when serving auth_ret_code, instead of serving
233 237 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
234 238 ## and then serve auth_ret_code to clients
235 239 auth_ret_code_detection = false
236 240
237 241 ## locking return code. When repository is locked return this HTTP code. 2XX
238 242 ## codes don't break the transactions while 4XX codes do
239 243 lock_ret_code = 423
240 244
241 245 ## allows to change the repository location in settings page
242 246 allow_repo_location_change = true
243 247
244 248 ## allows to setup custom hooks in settings page
245 249 allow_custom_hooks_settings = true
246 250
247 251 ## generated license token, goto license page in RhodeCode settings to obtain
248 252 ## new token
249 253 license_token =
250 254
251 255 ## supervisor connection uri, for managing supervisor and logs.
252 256 supervisor.uri =
253 257 ## supervisord group name/id we only want this RC instance to handle
254 258 supervisor.group_id = dev
255 259
256 260 ## Display extended labs settings
257 261 labs_settings_active = true
258 262
259 263 ####################################
260 264 ### CELERY CONFIG ####
261 265 ####################################
262 266 use_celery = false
263 267 broker.host = localhost
264 268 broker.vhost = rabbitmqhost
265 269 broker.port = 5672
266 270 broker.user = rabbitmq
267 271 broker.password = qweqwe
268 272
269 273 celery.imports = rhodecode.lib.celerylib.tasks
270 274
271 275 celery.result.backend = amqp
272 276 celery.result.dburi = amqp://
273 277 celery.result.serialier = json
274 278
275 279 #celery.send.task.error.emails = true
276 280 #celery.amqp.task.result.expires = 18000
277 281
278 282 celeryd.concurrency = 2
279 283 #celeryd.log.file = celeryd.log
280 284 celeryd.log.level = debug
281 285 celeryd.max.tasks.per.child = 1
282 286
283 287 ## tasks will never be sent to the queue, but executed locally instead.
284 288 celery.always.eager = false
285 289
286 290 ####################################
287 291 ### BEAKER CACHE ####
288 292 ####################################
289 293 # default cache dir for templates. Putting this into a ramdisk
290 294 ## can boost performance, eg. %(here)s/data_ramdisk
291 295 cache_dir = %(here)s/data
292 296
293 297 ## locking and default file storage for Beaker. Putting this into a ramdisk
294 298 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
295 299 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
296 300 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
297 301
298 302 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
299 303
300 304 beaker.cache.super_short_term.type = memory
301 305 beaker.cache.super_short_term.expire = 10
302 306 beaker.cache.super_short_term.key_length = 256
303 307
304 308 beaker.cache.short_term.type = memory
305 309 beaker.cache.short_term.expire = 60
306 310 beaker.cache.short_term.key_length = 256
307 311
308 312 beaker.cache.long_term.type = memory
309 313 beaker.cache.long_term.expire = 36000
310 314 beaker.cache.long_term.key_length = 256
311 315
312 316 beaker.cache.sql_cache_short.type = memory
313 317 beaker.cache.sql_cache_short.expire = 10
314 318 beaker.cache.sql_cache_short.key_length = 256
315 319
316 320 # default is memory cache, configure only if required
317 321 # using multi-node or multi-worker setup
318 322 #beaker.cache.auth_plugins.type = ext:database
319 323 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
320 324 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
321 325 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
322 326 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
323 327 #beaker.cache.auth_plugins.sa.pool_size = 10
324 328 #beaker.cache.auth_plugins.sa.max_overflow = 0
325 329
326 330 beaker.cache.repo_cache_long.type = memorylru_base
327 331 beaker.cache.repo_cache_long.max_items = 4096
328 332 beaker.cache.repo_cache_long.expire = 2592000
329 333
330 334 # default is memorylru_base cache, configure only if required
331 335 # using multi-node or multi-worker setup
332 336 #beaker.cache.repo_cache_long.type = ext:memcached
333 337 #beaker.cache.repo_cache_long.url = localhost:11211
334 338 #beaker.cache.repo_cache_long.expire = 1209600
335 339 #beaker.cache.repo_cache_long.key_length = 256
336 340
337 341 ####################################
338 342 ### BEAKER SESSION ####
339 343 ####################################
340 344
341 345 ## .session.type is type of storage options for the session, current allowed
342 346 ## types are file, ext:memcached, ext:database, and memory (default).
343 347 beaker.session.type = file
344 348 beaker.session.data_dir = %(here)s/data/sessions/data
345 349
346 350 ## db based session, fast, and allows easy management over logged in users ##
347 351 #beaker.session.type = ext:database
348 352 #beaker.session.table_name = db_session
349 353 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
350 354 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
351 355 #beaker.session.sa.pool_recycle = 3600
352 356 #beaker.session.sa.echo = false
353 357
354 358 beaker.session.key = rhodecode
355 359 beaker.session.secret = develop-rc-uytcxaz
356 360 beaker.session.lock_dir = %(here)s/data/sessions/lock
357 361
358 362 ## Secure encrypted cookie. Requires AES and AES python libraries
359 363 ## you must disable beaker.session.secret to use this
360 364 #beaker.session.encrypt_key = <key_for_encryption>
361 365 #beaker.session.validate_key = <validation_key>
362 366
363 367 ## sets session as invalid(also logging out user) if it haven not been
364 368 ## accessed for given amount of time in seconds
365 369 beaker.session.timeout = 2592000
366 370 beaker.session.httponly = true
367 371 #beaker.session.cookie_path = /<your-prefix>
368 372
369 373 ## uncomment for https secure cookie
370 374 beaker.session.secure = false
371 375
372 376 ## auto save the session to not to use .save()
373 377 beaker.session.auto = false
374 378
375 379 ## default cookie expiration time in seconds, set to `true` to set expire
376 380 ## at browser close
377 381 #beaker.session.cookie_expires = 3600
378 382
379 383 ###################################
380 384 ## SEARCH INDEXING CONFIGURATION ##
381 385 ###################################
382 386 ## Full text search indexer is available in rhodecode-tools under
383 387 ## `rhodecode-tools index` command
384 388
385 389 # WHOOSH Backend, doesn't require additional services to run
386 390 # it works good with few dozen repos
387 391 search.module = rhodecode.lib.index.whoosh
388 392 search.location = %(here)s/data/index
389 393
390 394 ###################################
391 395 ## APPENLIGHT CONFIG ##
392 396 ###################################
393 397
394 398 ## Appenlight is tailored to work with RhodeCode, see
395 399 ## http://appenlight.com for details how to obtain an account
396 400
397 401 ## appenlight integration enabled
398 402 appenlight = false
399 403
400 404 appenlight.server_url = https://api.appenlight.com
401 405 appenlight.api_key = YOUR_API_KEY
402 406 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
403 407
404 408 # used for JS client
405 409 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
406 410
407 411 ## TWEAK AMOUNT OF INFO SENT HERE
408 412
409 413 ## enables 404 error logging (default False)
410 414 appenlight.report_404 = false
411 415
412 416 ## time in seconds after request is considered being slow (default 1)
413 417 appenlight.slow_request_time = 1
414 418
415 419 ## record slow requests in application
416 420 ## (needs to be enabled for slow datastore recording and time tracking)
417 421 appenlight.slow_requests = true
418 422
419 423 ## enable hooking to application loggers
420 424 appenlight.logging = true
421 425
422 426 ## minimum log level for log capture
423 427 appenlight.logging.level = WARNING
424 428
425 429 ## send logs only from erroneous/slow requests
426 430 ## (saves API quota for intensive logging)
427 431 appenlight.logging_on_error = false
428 432
429 433 ## list of additonal keywords that should be grabbed from environ object
430 434 ## can be string with comma separated list of words in lowercase
431 435 ## (by default client will always send following info:
432 436 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
433 437 ## start with HTTP* this list be extended with additional keywords here
434 438 appenlight.environ_keys_whitelist =
435 439
436 440 ## list of keywords that should be blanked from request object
437 441 ## can be string with comma separated list of words in lowercase
438 442 ## (by default client will always blank keys that contain following words
439 443 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
440 444 ## this list be extended with additional keywords set here
441 445 appenlight.request_keys_blacklist =
442 446
443 447 ## list of namespaces that should be ignores when gathering log entries
444 448 ## can be string with comma separated list of namespaces
445 449 ## (by default the client ignores own entries: appenlight_client.client)
446 450 appenlight.log_namespace_blacklist =
447 451
448 452
449 453 ################################################################################
450 454 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
451 455 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
452 456 ## execute malicious code after an exception is raised. ##
453 457 ################################################################################
454 458 #set debug = false
455 459
456 460
457 461 ##############
458 462 ## STYLING ##
459 463 ##############
460 464 debug_style = true
461 465
462 466 #########################################################
463 467 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
464 468 #########################################################
465 469 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
466 470 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
467 471 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
468 472
469 473 # see sqlalchemy docs for other advanced settings
470 474
471 475 ## print the sql statements to output
472 476 sqlalchemy.db1.echo = false
473 477 ## recycle the connections after this ammount of seconds
474 478 sqlalchemy.db1.pool_recycle = 3600
475 479 sqlalchemy.db1.convert_unicode = true
476 480
477 481 ## the number of connections to keep open inside the connection pool.
478 482 ## 0 indicates no limit
479 483 #sqlalchemy.db1.pool_size = 5
480 484
481 485 ## the number of connections to allow in connection pool "overflow", that is
482 486 ## connections that can be opened above and beyond the pool_size setting,
483 487 ## which defaults to five.
484 488 #sqlalchemy.db1.max_overflow = 10
485 489
486 490
487 491 ##################
488 492 ### VCS CONFIG ###
489 493 ##################
490 494 vcs.server.enable = true
491 495 vcs.server = localhost:9900
492 496
493 497 ## Web server connectivity protocol, responsible for web based VCS operatations
494 498 ## Available protocols are:
495 499 ## `pyro4` - using pyro4 server
496 500 ## `http` - using http-rpc backend
497 501 #vcs.server.protocol = http
498 502
499 503 ## Push/Pull operations protocol, available options are:
500 504 ## `pyro4` - using pyro4 server
501 505 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
502 506 ## `vcsserver.scm_app` - internal app (EE only)
503 507 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
504 508
505 509 ## Push/Pull operations hooks protocol, available options are:
506 510 ## `pyro4` - using pyro4 server
507 511 ## `http` - using http-rpc backend
508 512 #vcs.hooks.protocol = http
509 513
510 514 vcs.server.log_level = debug
511 515 ## Start VCSServer with this instance as a subprocess, usefull for development
512 516 vcs.start_server = true
513 517 vcs.backends = hg, git, svn
514 518 vcs.connection_timeout = 3600
515 519 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
516 520 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
517 521 #vcs.svn.compatible_version = pre-1.8-compatible
518 522
519 523 ################################
520 524 ### LOGGING CONFIGURATION ####
521 525 ################################
522 526 [loggers]
523 527 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
524 528
525 529 [handlers]
526 530 keys = console, console_sql
527 531
528 532 [formatters]
529 533 keys = generic, color_formatter, color_formatter_sql
530 534
531 535 #############
532 536 ## LOGGERS ##
533 537 #############
534 538 [logger_root]
535 539 level = NOTSET
536 540 handlers = console
537 541
538 542 [logger_routes]
539 543 level = DEBUG
540 544 handlers =
541 545 qualname = routes.middleware
542 546 ## "level = DEBUG" logs the route matched and routing variables.
543 547 propagate = 1
544 548
545 549 [logger_beaker]
546 550 level = DEBUG
547 551 handlers =
548 552 qualname = beaker.container
549 553 propagate = 1
550 554
551 555 [logger_pyro4]
552 556 level = DEBUG
553 557 handlers =
554 558 qualname = Pyro4
555 559 propagate = 1
556 560
557 561 [logger_templates]
558 562 level = INFO
559 563 handlers =
560 564 qualname = pylons.templating
561 565 propagate = 1
562 566
563 567 [logger_rhodecode]
564 568 level = DEBUG
565 569 handlers =
566 570 qualname = rhodecode
567 571 propagate = 1
568 572
569 573 [logger_sqlalchemy]
570 574 level = INFO
571 575 handlers = console_sql
572 576 qualname = sqlalchemy.engine
573 577 propagate = 0
574 578
575 579 [logger_whoosh_indexer]
576 580 level = DEBUG
577 581 handlers =
578 582 qualname = whoosh_indexer
579 583 propagate = 1
580 584
581 585 ##############
582 586 ## HANDLERS ##
583 587 ##############
584 588
585 589 [handler_console]
586 590 class = StreamHandler
587 591 args = (sys.stderr,)
588 592 level = DEBUG
589 593 formatter = color_formatter
590 594
591 595 [handler_console_sql]
592 596 class = StreamHandler
593 597 args = (sys.stderr,)
594 598 level = DEBUG
595 599 formatter = color_formatter_sql
596 600
597 601 ################
598 602 ## FORMATTERS ##
599 603 ################
600 604
601 605 [formatter_generic]
602 606 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
603 607 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
604 608 datefmt = %Y-%m-%d %H:%M:%S
605 609
606 610 [formatter_color_formatter]
607 611 class = rhodecode.lib.logging_formatter.ColorFormatter
608 612 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
609 613 datefmt = %Y-%m-%d %H:%M:%S
610 614
611 615 [formatter_color_formatter_sql]
612 616 class = rhodecode.lib.logging_formatter.ColorFormatterSql
613 617 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
614 618 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,583 +1,587 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode Enterprise - configuration file #
4 4 # Built-in functions and variables #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 # #
7 7 ################################################################################
8 8
9 9 [DEFAULT]
10 10 debug = true
11 11 ################################################################################
12 12 ## Uncomment and replace with the email address which should receive ##
13 13 ## any error reports after an application crash ##
14 14 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20 #email_prefix = [RhodeCode]
21 21
22 22 #smtp_server = mail.server.com
23 23 #smtp_username =
24 24 #smtp_password =
25 25 #smtp_port =
26 26 #smtp_use_tls = false
27 27 #smtp_use_ssl = true
28 28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 29 #smtp_auth =
30 30
31 31 [server:main]
32 32 ## COMMON ##
33 33 host = 127.0.0.1
34 34 port = 5000
35 35
36 36 ##################################
37 37 ## WAITRESS WSGI SERVER ##
38 38 ## Recommended for Development ##
39 39 ##################################
40 40 #use = egg:waitress#main
41 41 ## number of worker threads
42 42 #threads = 5
43 43 ## MAX BODY SIZE 100GB
44 44 #max_request_body_size = 107374182400
45 45 ## Use poll instead of select, fixes file descriptors limits problems.
46 46 ## May not work on old windows systems.
47 47 #asyncore_use_poll = true
48 48
49 49
50 50 ##########################
51 51 ## GUNICORN WSGI SERVER ##
52 52 ##########################
53 53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 54 use = egg:gunicorn#main
55 55 ## Sets the number of process workers. You must set `instance_id = *`
56 56 ## when this option is set to more than one worker, recommended
57 57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 58 ## The `instance_id = *` must be set in the [app:main] section below
59 59 workers = 2
60 60 ## number of threads for each of the worker, must be set to 1 for gevent
61 61 ## generally recommened to be at 1
62 62 #threads = 1
63 63 ## process name
64 64 proc_name = rhodecode
65 65 ## type of worker class, one of sync, gevent
66 66 ## recommended for bigger setup is using of of other than sync one
67 67 worker_class = sync
68 68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 69 #worker_connections = 10
70 70 ## max number of requests that worker will handle before being gracefully
71 71 ## restarted, could prevent memory leaks
72 72 max_requests = 1000
73 73 max_requests_jitter = 30
74 74 ## amount of time a worker can spend with handling a request before it
75 75 ## gets killed and restarted. Set to 6hrs
76 76 timeout = 21600
77 77
78 78
79 79 ## prefix middleware for RhodeCode, disables force_https flag.
80 80 ## allows to set RhodeCode under a prefix in server.
81 81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 82 #[filter:proxy-prefix]
83 83 #use = egg:PasteDeploy#prefix
84 84 #prefix = /<your-prefix>
85 85
86 86 [app:main]
87 87 use = egg:rhodecode-enterprise-ce
88 88 ## enable proxy prefix middleware, defined below
89 89 #filter-with = proxy-prefix
90 90
91 91 ## encryption key used to encrypt social plugin tokens,
92 92 ## remote_urls with credentials etc, if not set it defaults to
93 93 ## `beaker.session.secret`
94 94 #rhodecode.encrypted_values.secret =
95 95
96 ## decryption strict mode (enabled by default). It controls if decryption raises
97 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
98 #rhodecode.encrypted_values.strict = false
99
96 100 full_stack = true
97 101
98 102 ## Serve static files via RhodeCode, disable to serve them via HTTP server
99 103 static_files = true
100 104
101 105 # autogenerate javascript routes file on startup
102 106 generate_js_files = false
103 107
104 108 ## Optional Languages
105 109 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
106 110 lang = en
107 111
108 112 ## perform a full repository scan on each server start, this should be
109 113 ## set to false after first startup, to allow faster server restarts.
110 114 startup.import_repos = false
111 115
112 116 ## Uncomment and set this path to use archive download cache.
113 117 ## Once enabled, generated archives will be cached at this location
114 118 ## and served from the cache during subsequent requests for the same archive of
115 119 ## the repository.
116 120 #archive_cache_dir = /tmp/tarballcache
117 121
118 122 ## change this to unique ID for security
119 123 app_instance_uuid = rc-production
120 124
121 125 ## cut off limit for large diffs (size in bytes)
122 126 cut_off_limit_diff = 1024000
123 127 cut_off_limit_file = 256000
124 128
125 129 ## use cache version of scm repo everywhere
126 130 vcs_full_cache = true
127 131
128 132 ## force https in RhodeCode, fixes https redirects, assumes it's always https
129 133 ## Normally this is controlled by proper http flags sent from http server
130 134 force_https = false
131 135
132 136 ## use Strict-Transport-Security headers
133 137 use_htsts = false
134 138
135 139 ## number of commits stats will parse on each iteration
136 140 commit_parse_limit = 25
137 141
138 142 ## git rev filter option, --all is the default filter, if you need to
139 143 ## hide all refs in changelog switch this to --branches --tags
140 144 git_rev_filter = --branches --tags
141 145
142 146 # Set to true if your repos are exposed using the dumb protocol
143 147 git_update_server_info = false
144 148
145 149 ## RSS/ATOM feed options
146 150 rss_cut_off_limit = 256000
147 151 rss_items_per_page = 10
148 152 rss_include_diff = false
149 153
150 154 ## gist URL alias, used to create nicer urls for gist. This should be an
151 155 ## url that does rewrites to _admin/gists/<gistid>.
152 156 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
153 157 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
154 158 gist_alias_url =
155 159
156 160 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
157 161 ## used for access.
158 162 ## Adding ?auth_token = <token> to the url authenticates this request as if it
159 163 ## came from the the logged in user who own this authentication token.
160 164 ##
161 165 ## Syntax is <ControllerClass>:<function_pattern>.
162 166 ## To enable access to raw_files put `FilesController:raw`.
163 167 ## To enable access to patches add `ChangesetController:changeset_patch`.
164 168 ## The list should be "," separated and on a single line.
165 169 ##
166 170 ## Recommended controllers to enable:
167 171 # ChangesetController:changeset_patch,
168 172 # ChangesetController:changeset_raw,
169 173 # FilesController:raw,
170 174 # FilesController:archivefile,
171 175 # GistsController:*,
172 176 api_access_controllers_whitelist =
173 177
174 178 ## default encoding used to convert from and to unicode
175 179 ## can be also a comma separated list of encoding in case of mixed encodings
176 180 default_encoding = UTF-8
177 181
178 182 ## instance-id prefix
179 183 ## a prefix key for this instance used for cache invalidation when running
180 184 ## multiple instances of rhodecode, make sure it's globally unique for
181 185 ## all running rhodecode instances. Leave empty if you don't use it
182 186 instance_id =
183 187
184 188 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
185 189 ## of an authentication plugin also if it is disabled by it's settings.
186 190 ## This could be useful if you are unable to log in to the system due to broken
187 191 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
188 192 ## module to log in again and fix the settings.
189 193 ##
190 194 ## Available builtin plugin IDs (hash is part of the ID):
191 195 ## egg:rhodecode-enterprise-ce#rhodecode
192 196 ## egg:rhodecode-enterprise-ce#pam
193 197 ## egg:rhodecode-enterprise-ce#ldap
194 198 ## egg:rhodecode-enterprise-ce#jasig_cas
195 199 ## egg:rhodecode-enterprise-ce#headers
196 200 ## egg:rhodecode-enterprise-ce#crowd
197 201 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
198 202
199 203 ## alternative return HTTP header for failed authentication. Default HTTP
200 204 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
201 205 ## handling that causing a series of failed authentication calls.
202 206 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
203 207 ## This will be served instead of default 401 on bad authnetication
204 208 auth_ret_code =
205 209
206 210 ## use special detection method when serving auth_ret_code, instead of serving
207 211 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
208 212 ## and then serve auth_ret_code to clients
209 213 auth_ret_code_detection = false
210 214
211 215 ## locking return code. When repository is locked return this HTTP code. 2XX
212 216 ## codes don't break the transactions while 4XX codes do
213 217 lock_ret_code = 423
214 218
215 219 ## allows to change the repository location in settings page
216 220 allow_repo_location_change = true
217 221
218 222 ## allows to setup custom hooks in settings page
219 223 allow_custom_hooks_settings = true
220 224
221 225 ## generated license token, goto license page in RhodeCode settings to obtain
222 226 ## new token
223 227 license_token =
224 228
225 229 ## supervisor connection uri, for managing supervisor and logs.
226 230 supervisor.uri =
227 231 ## supervisord group name/id we only want this RC instance to handle
228 232 supervisor.group_id = prod
229 233
230 234 ## Display extended labs settings
231 235 labs_settings_active = true
232 236
233 237 ####################################
234 238 ### CELERY CONFIG ####
235 239 ####################################
236 240 use_celery = false
237 241 broker.host = localhost
238 242 broker.vhost = rabbitmqhost
239 243 broker.port = 5672
240 244 broker.user = rabbitmq
241 245 broker.password = qweqwe
242 246
243 247 celery.imports = rhodecode.lib.celerylib.tasks
244 248
245 249 celery.result.backend = amqp
246 250 celery.result.dburi = amqp://
247 251 celery.result.serialier = json
248 252
249 253 #celery.send.task.error.emails = true
250 254 #celery.amqp.task.result.expires = 18000
251 255
252 256 celeryd.concurrency = 2
253 257 #celeryd.log.file = celeryd.log
254 258 celeryd.log.level = debug
255 259 celeryd.max.tasks.per.child = 1
256 260
257 261 ## tasks will never be sent to the queue, but executed locally instead.
258 262 celery.always.eager = false
259 263
260 264 ####################################
261 265 ### BEAKER CACHE ####
262 266 ####################################
263 267 # default cache dir for templates. Putting this into a ramdisk
264 268 ## can boost performance, eg. %(here)s/data_ramdisk
265 269 cache_dir = %(here)s/data
266 270
267 271 ## locking and default file storage for Beaker. Putting this into a ramdisk
268 272 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
269 273 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
270 274 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
271 275
272 276 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
273 277
274 278 beaker.cache.super_short_term.type = memory
275 279 beaker.cache.super_short_term.expire = 10
276 280 beaker.cache.super_short_term.key_length = 256
277 281
278 282 beaker.cache.short_term.type = memory
279 283 beaker.cache.short_term.expire = 60
280 284 beaker.cache.short_term.key_length = 256
281 285
282 286 beaker.cache.long_term.type = memory
283 287 beaker.cache.long_term.expire = 36000
284 288 beaker.cache.long_term.key_length = 256
285 289
286 290 beaker.cache.sql_cache_short.type = memory
287 291 beaker.cache.sql_cache_short.expire = 10
288 292 beaker.cache.sql_cache_short.key_length = 256
289 293
290 294 # default is memory cache, configure only if required
291 295 # using multi-node or multi-worker setup
292 296 #beaker.cache.auth_plugins.type = ext:database
293 297 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
294 298 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
295 299 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
296 300 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
297 301 #beaker.cache.auth_plugins.sa.pool_size = 10
298 302 #beaker.cache.auth_plugins.sa.max_overflow = 0
299 303
300 304 beaker.cache.repo_cache_long.type = memorylru_base
301 305 beaker.cache.repo_cache_long.max_items = 4096
302 306 beaker.cache.repo_cache_long.expire = 2592000
303 307
304 308 # default is memorylru_base cache, configure only if required
305 309 # using multi-node or multi-worker setup
306 310 #beaker.cache.repo_cache_long.type = ext:memcached
307 311 #beaker.cache.repo_cache_long.url = localhost:11211
308 312 #beaker.cache.repo_cache_long.expire = 1209600
309 313 #beaker.cache.repo_cache_long.key_length = 256
310 314
311 315 ####################################
312 316 ### BEAKER SESSION ####
313 317 ####################################
314 318
315 319 ## .session.type is type of storage options for the session, current allowed
316 320 ## types are file, ext:memcached, ext:database, and memory (default).
317 321 beaker.session.type = file
318 322 beaker.session.data_dir = %(here)s/data/sessions/data
319 323
320 324 ## db based session, fast, and allows easy management over logged in users ##
321 325 #beaker.session.type = ext:database
322 326 #beaker.session.table_name = db_session
323 327 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
324 328 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
325 329 #beaker.session.sa.pool_recycle = 3600
326 330 #beaker.session.sa.echo = false
327 331
328 332 beaker.session.key = rhodecode
329 333 beaker.session.secret = production-rc-uytcxaz
330 334 beaker.session.lock_dir = %(here)s/data/sessions/lock
331 335
332 336 ## Secure encrypted cookie. Requires AES and AES python libraries
333 337 ## you must disable beaker.session.secret to use this
334 338 #beaker.session.encrypt_key = <key_for_encryption>
335 339 #beaker.session.validate_key = <validation_key>
336 340
337 341 ## sets session as invalid(also logging out user) if it haven not been
338 342 ## accessed for given amount of time in seconds
339 343 beaker.session.timeout = 2592000
340 344 beaker.session.httponly = true
341 345 #beaker.session.cookie_path = /<your-prefix>
342 346
343 347 ## uncomment for https secure cookie
344 348 beaker.session.secure = false
345 349
346 350 ## auto save the session to not to use .save()
347 351 beaker.session.auto = false
348 352
349 353 ## default cookie expiration time in seconds, set to `true` to set expire
350 354 ## at browser close
351 355 #beaker.session.cookie_expires = 3600
352 356
353 357 ###################################
354 358 ## SEARCH INDEXING CONFIGURATION ##
355 359 ###################################
356 360 ## Full text search indexer is available in rhodecode-tools under
357 361 ## `rhodecode-tools index` command
358 362
359 363 # WHOOSH Backend, doesn't require additional services to run
360 364 # it works good with few dozen repos
361 365 search.module = rhodecode.lib.index.whoosh
362 366 search.location = %(here)s/data/index
363 367
364 368 ###################################
365 369 ## APPENLIGHT CONFIG ##
366 370 ###################################
367 371
368 372 ## Appenlight is tailored to work with RhodeCode, see
369 373 ## http://appenlight.com for details how to obtain an account
370 374
371 375 ## appenlight integration enabled
372 376 appenlight = false
373 377
374 378 appenlight.server_url = https://api.appenlight.com
375 379 appenlight.api_key = YOUR_API_KEY
376 380 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
377 381
378 382 # used for JS client
379 383 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
380 384
381 385 ## TWEAK AMOUNT OF INFO SENT HERE
382 386
383 387 ## enables 404 error logging (default False)
384 388 appenlight.report_404 = false
385 389
386 390 ## time in seconds after request is considered being slow (default 1)
387 391 appenlight.slow_request_time = 1
388 392
389 393 ## record slow requests in application
390 394 ## (needs to be enabled for slow datastore recording and time tracking)
391 395 appenlight.slow_requests = true
392 396
393 397 ## enable hooking to application loggers
394 398 appenlight.logging = true
395 399
396 400 ## minimum log level for log capture
397 401 appenlight.logging.level = WARNING
398 402
399 403 ## send logs only from erroneous/slow requests
400 404 ## (saves API quota for intensive logging)
401 405 appenlight.logging_on_error = false
402 406
403 407 ## list of additonal keywords that should be grabbed from environ object
404 408 ## can be string with comma separated list of words in lowercase
405 409 ## (by default client will always send following info:
406 410 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
407 411 ## start with HTTP* this list be extended with additional keywords here
408 412 appenlight.environ_keys_whitelist =
409 413
410 414 ## list of keywords that should be blanked from request object
411 415 ## can be string with comma separated list of words in lowercase
412 416 ## (by default client will always blank keys that contain following words
413 417 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
414 418 ## this list be extended with additional keywords set here
415 419 appenlight.request_keys_blacklist =
416 420
417 421 ## list of namespaces that should be ignores when gathering log entries
418 422 ## can be string with comma separated list of namespaces
419 423 ## (by default the client ignores own entries: appenlight_client.client)
420 424 appenlight.log_namespace_blacklist =
421 425
422 426
423 427 ################################################################################
424 428 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
425 429 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
426 430 ## execute malicious code after an exception is raised. ##
427 431 ################################################################################
428 432 set debug = false
429 433
430 434
431 435 #########################################################
432 436 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
433 437 #########################################################
434 438 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
435 439 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
436 440 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
437 441
438 442 # see sqlalchemy docs for other advanced settings
439 443
440 444 ## print the sql statements to output
441 445 sqlalchemy.db1.echo = false
442 446 ## recycle the connections after this ammount of seconds
443 447 sqlalchemy.db1.pool_recycle = 3600
444 448 sqlalchemy.db1.convert_unicode = true
445 449
446 450 ## the number of connections to keep open inside the connection pool.
447 451 ## 0 indicates no limit
448 452 #sqlalchemy.db1.pool_size = 5
449 453
450 454 ## the number of connections to allow in connection pool "overflow", that is
451 455 ## connections that can be opened above and beyond the pool_size setting,
452 456 ## which defaults to five.
453 457 #sqlalchemy.db1.max_overflow = 10
454 458
455 459
456 460 ##################
457 461 ### VCS CONFIG ###
458 462 ##################
459 463 vcs.server.enable = true
460 464 vcs.server = localhost:9900
461 465
462 466 ## Web server connectivity protocol, responsible for web based VCS operatations
463 467 ## Available protocols are:
464 468 ## `pyro4` - using pyro4 server
465 469 ## `http` - using http-rpc backend
466 470 #vcs.server.protocol = http
467 471
468 472 ## Push/Pull operations protocol, available options are:
469 473 ## `pyro4` - using pyro4 server
470 474 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
471 475 ## `vcsserver.scm_app` - internal app (EE only)
472 476 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
473 477
474 478 ## Push/Pull operations hooks protocol, available options are:
475 479 ## `pyro4` - using pyro4 server
476 480 ## `http` - using http-rpc backend
477 481 #vcs.hooks.protocol = http
478 482
479 483 vcs.server.log_level = info
480 484 ## Start VCSServer with this instance as a subprocess, usefull for development
481 485 vcs.start_server = false
482 486 vcs.backends = hg, git, svn
483 487 vcs.connection_timeout = 3600
484 488 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
485 489 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
486 490 #vcs.svn.compatible_version = pre-1.8-compatible
487 491
488 492 ################################
489 493 ### LOGGING CONFIGURATION ####
490 494 ################################
491 495 [loggers]
492 496 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates, whoosh_indexer
493 497
494 498 [handlers]
495 499 keys = console, console_sql
496 500
497 501 [formatters]
498 502 keys = generic, color_formatter, color_formatter_sql
499 503
500 504 #############
501 505 ## LOGGERS ##
502 506 #############
503 507 [logger_root]
504 508 level = NOTSET
505 509 handlers = console
506 510
507 511 [logger_routes]
508 512 level = DEBUG
509 513 handlers =
510 514 qualname = routes.middleware
511 515 ## "level = DEBUG" logs the route matched and routing variables.
512 516 propagate = 1
513 517
514 518 [logger_beaker]
515 519 level = DEBUG
516 520 handlers =
517 521 qualname = beaker.container
518 522 propagate = 1
519 523
520 524 [logger_pyro4]
521 525 level = DEBUG
522 526 handlers =
523 527 qualname = Pyro4
524 528 propagate = 1
525 529
526 530 [logger_templates]
527 531 level = INFO
528 532 handlers =
529 533 qualname = pylons.templating
530 534 propagate = 1
531 535
532 536 [logger_rhodecode]
533 537 level = DEBUG
534 538 handlers =
535 539 qualname = rhodecode
536 540 propagate = 1
537 541
538 542 [logger_sqlalchemy]
539 543 level = INFO
540 544 handlers = console_sql
541 545 qualname = sqlalchemy.engine
542 546 propagate = 0
543 547
544 548 [logger_whoosh_indexer]
545 549 level = DEBUG
546 550 handlers =
547 551 qualname = whoosh_indexer
548 552 propagate = 1
549 553
550 554 ##############
551 555 ## HANDLERS ##
552 556 ##############
553 557
554 558 [handler_console]
555 559 class = StreamHandler
556 560 args = (sys.stderr,)
557 561 level = INFO
558 562 formatter = generic
559 563
560 564 [handler_console_sql]
561 565 class = StreamHandler
562 566 args = (sys.stderr,)
563 567 level = WARN
564 568 formatter = generic
565 569
566 570 ################
567 571 ## FORMATTERS ##
568 572 ################
569 573
570 574 [formatter_generic]
571 575 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
572 576 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
573 577 datefmt = %Y-%m-%d %H:%M:%S
574 578
575 579 [formatter_color_formatter]
576 580 class = rhodecode.lib.logging_formatter.ColorFormatter
577 581 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
578 582 datefmt = %Y-%m-%d %H:%M:%S
579 583
580 584 [formatter_color_formatter_sql]
581 585 class = rhodecode.lib.logging_formatter.ColorFormatterSql
582 586 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
583 587 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,61 +1,114 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Generic encryption library for RhodeCode
24 24 """
25 25
26 import hashlib
27 26 import base64
28 27
29 28 from Crypto.Cipher import AES
30 29 from Crypto import Random
30 from Crypto.Hash import HMAC, SHA256
31 31
32 32 from rhodecode.lib.utils2 import safe_str
33 33
34 34
35 class SignatureVerificationError(Exception):
36 pass
37
38
39 class InvalidDecryptedValue(str):
40
41 def __new__(cls, content):
42 """
43 This will generate something like this::
44 <InvalidDecryptedValue(QkWusFgLJXR6m42v...)>
45 And represent a safe indicator that encryption key is broken
46 """
47 content = '<{}({}...)>'.format(cls.__name__, content[:16])
48 return str.__new__(cls, content)
49
50
35 51 class AESCipher(object):
36 def __init__(self, key):
37 # create padding, trim to long enc key
52 def __init__(self, key, hmac=False, strict_verification=True):
38 53 if not key:
39 54 raise ValueError('passed key variable is empty')
55 self.strict_verification = strict_verification
40 56 self.block_size = 32
41 self.key = hashlib.sha256(safe_str(key)).digest()
57 self.hmac_size = 32
58 self.hmac = hmac
59
60 self.key = SHA256.new(safe_str(key)).digest()
61 self.hmac_key = SHA256.new(self.key).digest()
62
63 def verify_hmac_signature(self, raw_data):
64 org_hmac_signature = raw_data[-self.hmac_size:]
65 data_without_sig = raw_data[:-self.hmac_size]
66 recomputed_hmac = HMAC.new(
67 self.hmac_key, data_without_sig, digestmod=SHA256).digest()
68 return org_hmac_signature == recomputed_hmac
42 69
43 70 def encrypt(self, raw):
44 71 raw = self._pad(raw)
45 72 iv = Random.new().read(AES.block_size)
46 73 cipher = AES.new(self.key, AES.MODE_CBC, iv)
47 return base64.b64encode(iv + cipher.encrypt(raw))
74 enc_value = cipher.encrypt(raw)
75
76 hmac_signature = ''
77 if self.hmac:
78 # compute hmac+sha256 on iv + enc text, we use
79 # encrypt then mac method to create the signature
80 hmac_signature = HMAC.new(
81 self.hmac_key, iv + enc_value, digestmod=SHA256).digest()
82
83 return base64.b64encode(iv + enc_value + hmac_signature)
48 84
49 85 def decrypt(self, enc):
86 enc_org = enc
50 87 enc = base64.b64decode(enc)
88
89 if self.hmac and len(enc) > self.hmac_size:
90 if self.verify_hmac_signature(enc):
91 # cut off the HMAC verification digest
92 enc = enc[:-self.hmac_size]
93 else:
94 if self.strict_verification:
95 raise SignatureVerificationError(
96 "Encryption signature verification failed. "
97 "Please check your secret key, and/or encrypted value. "
98 "Secret key is stored as "
99 "`rhodecode.encrypted_values.secret` or "
100 "`beaker.session.secret` inside .ini file")
101
102 return InvalidDecryptedValue(enc_org)
103
51 104 iv = enc[:AES.block_size]
52 105 cipher = AES.new(self.key, AES.MODE_CBC, iv)
53 106 return self._unpad(cipher.decrypt(enc[AES.block_size:]))
54 107
55 108 def _pad(self, s):
56 109 return (s + (self.block_size - len(s) % self.block_size)
57 110 * chr(self.block_size - len(s) % self.block_size))
58 111
59 112 @staticmethod
60 113 def _unpad(s):
61 114 return s[:-ord(s[len(s)-1:])] No newline at end of file
@@ -1,3462 +1,3476 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database Models for RhodeCode Enterprise
23 23 """
24 24
25 25 import os
26 26 import sys
27 27 import time
28 28 import hashlib
29 29 import logging
30 30 import datetime
31 31 import warnings
32 32 import ipaddress
33 33 import functools
34 34 import traceback
35 35 import collections
36 36
37 37
38 38 from sqlalchemy import *
39 39 from sqlalchemy.exc import IntegrityError
40 40 from sqlalchemy.ext.declarative import declared_attr
41 41 from sqlalchemy.ext.hybrid import hybrid_property
42 42 from sqlalchemy.orm import (
43 43 relationship, joinedload, class_mapper, validates, aliased)
44 44 from sqlalchemy.sql.expression import true
45 45 from beaker.cache import cache_region, region_invalidate
46 46 from webob.exc import HTTPNotFound
47 47 from zope.cachedescriptors.property import Lazy as LazyProperty
48 48
49 49 from pylons import url
50 50 from pylons.i18n.translation import lazy_ugettext as _
51 51
52 52 from rhodecode.lib.vcs import get_backend
53 53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 54 from rhodecode.lib.vcs.exceptions import VCSError
55 55 from rhodecode.lib.vcs.backends.base import (
56 56 EmptyCommit, Reference, MergeFailureReason)
57 57 from rhodecode.lib.utils2 import (
58 58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 60 from rhodecode.lib.ext_json import json
61 61 from rhodecode.lib.caching_query import FromCache
62 62 from rhodecode.lib.encrypt import AESCipher
63 63
64 64 from rhodecode.model.meta import Base, Session
65 65
66 66 URL_SEP = '/'
67 67 log = logging.getLogger(__name__)
68 68
69 69 # =============================================================================
70 70 # BASE CLASSES
71 71 # =============================================================================
72 72
73 # this is propagated from .ini file beaker.session.secret
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
74 # beaker.session.secret if first is not set.
74 75 # and initialized at environment.py
75 76 ENCRYPTION_KEY = None
76 77
77 78 # used to sort permissions by types, '#' used here is not allowed to be in
78 79 # usernames, and it's very early in sorted string.printable table.
79 80 PERMISSION_TYPE_SORT = {
80 81 'admin': '####',
81 82 'write': '###',
82 83 'read': '##',
83 84 'none': '#',
84 85 }
85 86
86 87
87 88 def display_sort(obj):
88 89 """
89 90 Sort function used to sort permissions in .permissions() function of
90 91 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 92 of all other resources
92 93 """
93 94
94 95 if obj.username == User.DEFAULT_USER:
95 96 return '#####'
96 97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 98 return prefix + obj.username
98 99
99 100
100 101 def _hash_key(k):
101 102 return md5_safe(k)
102 103
103 104
104 105 class EncryptedTextValue(TypeDecorator):
105 106 """
106 107 Special column for encrypted long text data, use like::
107 108
108 109 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109 110
110 111 This column is intelligent so if value is in unencrypted form it return
111 112 unencrypted form, but on save it always encrypts
112 113 """
113 114 impl = Text
114 115
115 116 def process_bind_param(self, value, dialect):
116 117 if not value:
117 118 return value
118 if value.startswith('enc$aes$'):
119 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
119 120 # protect against double encrypting if someone manually starts
120 121 # doing
121 122 raise ValueError('value needs to be in unencrypted format, ie. '
122 'not starting with enc$aes$')
123 return 'enc$aes$%s' % AESCipher(ENCRYPTION_KEY).encrypt(value)
123 'not starting with enc$aes')
124 return 'enc$aes_hmac$%s' % AESCipher(
125 ENCRYPTION_KEY, hmac=True).encrypt(value)
124 126
125 127 def process_result_value(self, value, dialect):
128 import rhodecode
129
126 130 if not value:
127 131 return value
128 132
129 133 parts = value.split('$', 3)
130 134 if not len(parts) == 3:
131 135 # probably not encrypted values
132 136 return value
133 137 else:
134 138 if parts[0] != 'enc':
135 139 # parts ok but without our header ?
136 140 return value
137
141 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
142 'rhodecode.encrypted_values.strict') or True)
138 143 # at that stage we know it's our encryption
144 if parts[1] == 'aes':
139 145 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
146 elif parts[1] == 'aes_hmac':
147 decrypted_data = AESCipher(
148 ENCRYPTION_KEY, hmac=True,
149 strict_verification=enc_strict_mode).decrypt(parts[2])
150 else:
151 raise ValueError(
152 'Encryption type part is wrong, must be `aes` '
153 'or `aes_hmac`, got `%s` instead' % (parts[1]))
140 154 return decrypted_data
141 155
142 156
143 157 class BaseModel(object):
144 158 """
145 159 Base Model for all classes
146 160 """
147 161
148 162 @classmethod
149 163 def _get_keys(cls):
150 164 """return column names for this model """
151 165 return class_mapper(cls).c.keys()
152 166
153 167 def get_dict(self):
154 168 """
155 169 return dict with keys and values corresponding
156 170 to this model data """
157 171
158 172 d = {}
159 173 for k in self._get_keys():
160 174 d[k] = getattr(self, k)
161 175
162 176 # also use __json__() if present to get additional fields
163 177 _json_attr = getattr(self, '__json__', None)
164 178 if _json_attr:
165 179 # update with attributes from __json__
166 180 if callable(_json_attr):
167 181 _json_attr = _json_attr()
168 182 for k, val in _json_attr.iteritems():
169 183 d[k] = val
170 184 return d
171 185
172 186 def get_appstruct(self):
173 187 """return list with keys and values tuples corresponding
174 188 to this model data """
175 189
176 190 l = []
177 191 for k in self._get_keys():
178 192 l.append((k, getattr(self, k),))
179 193 return l
180 194
181 195 def populate_obj(self, populate_dict):
182 196 """populate model with data from given populate_dict"""
183 197
184 198 for k in self._get_keys():
185 199 if k in populate_dict:
186 200 setattr(self, k, populate_dict[k])
187 201
188 202 @classmethod
189 203 def query(cls):
190 204 return Session().query(cls)
191 205
192 206 @classmethod
193 207 def get(cls, id_):
194 208 if id_:
195 209 return cls.query().get(id_)
196 210
197 211 @classmethod
198 212 def get_or_404(cls, id_):
199 213 try:
200 214 id_ = int(id_)
201 215 except (TypeError, ValueError):
202 216 raise HTTPNotFound
203 217
204 218 res = cls.query().get(id_)
205 219 if not res:
206 220 raise HTTPNotFound
207 221 return res
208 222
209 223 @classmethod
210 224 def getAll(cls):
211 225 # deprecated and left for backward compatibility
212 226 return cls.get_all()
213 227
214 228 @classmethod
215 229 def get_all(cls):
216 230 return cls.query().all()
217 231
218 232 @classmethod
219 233 def delete(cls, id_):
220 234 obj = cls.query().get(id_)
221 235 Session().delete(obj)
222 236
223 237 @classmethod
224 238 def identity_cache(cls, session, attr_name, value):
225 239 exist_in_session = []
226 240 for (item_cls, pkey), instance in session.identity_map.items():
227 241 if cls == item_cls and getattr(instance, attr_name) == value:
228 242 exist_in_session.append(instance)
229 243 if exist_in_session:
230 244 if len(exist_in_session) == 1:
231 245 return exist_in_session[0]
232 246 log.exception(
233 247 'multiple objects with attr %s and '
234 248 'value %s found with same name: %r',
235 249 attr_name, value, exist_in_session)
236 250
237 251 def __repr__(self):
238 252 if hasattr(self, '__unicode__'):
239 253 # python repr needs to return str
240 254 try:
241 255 return safe_str(self.__unicode__())
242 256 except UnicodeDecodeError:
243 257 pass
244 258 return '<DB:%s>' % (self.__class__.__name__)
245 259
246 260
247 261 class RhodeCodeSetting(Base, BaseModel):
248 262 __tablename__ = 'rhodecode_settings'
249 263 __table_args__ = (
250 264 UniqueConstraint('app_settings_name'),
251 265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
253 267 )
254 268
255 269 SETTINGS_TYPES = {
256 270 'str': safe_str,
257 271 'int': safe_int,
258 272 'unicode': safe_unicode,
259 273 'bool': str2bool,
260 274 'list': functools.partial(aslist, sep=',')
261 275 }
262 276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
263 277 GLOBAL_CONF_KEY = 'app_settings'
264 278
265 279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
267 281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
268 282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
269 283
270 284 def __init__(self, key='', val='', type='unicode'):
271 285 self.app_settings_name = key
272 286 self.app_settings_type = type
273 287 self.app_settings_value = val
274 288
275 289 @validates('_app_settings_value')
276 290 def validate_settings_value(self, key, val):
277 291 assert type(val) == unicode
278 292 return val
279 293
280 294 @hybrid_property
281 295 def app_settings_value(self):
282 296 v = self._app_settings_value
283 297 _type = self.app_settings_type
284 298 if _type:
285 299 _type = self.app_settings_type.split('.')[0]
286 300 # decode the encrypted value
287 301 if 'encrypted' in self.app_settings_type:
288 302 cipher = EncryptedTextValue()
289 303 v = safe_unicode(cipher.process_result_value(v, None))
290 304
291 305 converter = self.SETTINGS_TYPES.get(_type) or \
292 306 self.SETTINGS_TYPES['unicode']
293 307 return converter(v)
294 308
295 309 @app_settings_value.setter
296 310 def app_settings_value(self, val):
297 311 """
298 312 Setter that will always make sure we use unicode in app_settings_value
299 313
300 314 :param val:
301 315 """
302 316 val = safe_unicode(val)
303 317 # encode the encrypted value
304 318 if 'encrypted' in self.app_settings_type:
305 319 cipher = EncryptedTextValue()
306 320 val = safe_unicode(cipher.process_bind_param(val, None))
307 321 self._app_settings_value = val
308 322
309 323 @hybrid_property
310 324 def app_settings_type(self):
311 325 return self._app_settings_type
312 326
313 327 @app_settings_type.setter
314 328 def app_settings_type(self, val):
315 329 if val.split('.')[0] not in self.SETTINGS_TYPES:
316 330 raise Exception('type must be one of %s got %s'
317 331 % (self.SETTINGS_TYPES.keys(), val))
318 332 self._app_settings_type = val
319 333
320 334 def __unicode__(self):
321 335 return u"<%s('%s:%s[%s]')>" % (
322 336 self.__class__.__name__,
323 337 self.app_settings_name, self.app_settings_value,
324 338 self.app_settings_type
325 339 )
326 340
327 341
328 342 class RhodeCodeUi(Base, BaseModel):
329 343 __tablename__ = 'rhodecode_ui'
330 344 __table_args__ = (
331 345 UniqueConstraint('ui_key'),
332 346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
333 347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
334 348 )
335 349
336 350 HOOK_REPO_SIZE = 'changegroup.repo_size'
337 351 # HG
338 352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
339 353 HOOK_PULL = 'outgoing.pull_logger'
340 354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
341 355 HOOK_PUSH = 'changegroup.push_logger'
342 356
343 357 # TODO: johbo: Unify way how hooks are configured for git and hg,
344 358 # git part is currently hardcoded.
345 359
346 360 # SVN PATTERNS
347 361 SVN_BRANCH_ID = 'vcs_svn_branch'
348 362 SVN_TAG_ID = 'vcs_svn_tag'
349 363
350 364 ui_id = Column(
351 365 "ui_id", Integer(), nullable=False, unique=True, default=None,
352 366 primary_key=True)
353 367 ui_section = Column(
354 368 "ui_section", String(255), nullable=True, unique=None, default=None)
355 369 ui_key = Column(
356 370 "ui_key", String(255), nullable=True, unique=None, default=None)
357 371 ui_value = Column(
358 372 "ui_value", String(255), nullable=True, unique=None, default=None)
359 373 ui_active = Column(
360 374 "ui_active", Boolean(), nullable=True, unique=None, default=True)
361 375
362 376 def __repr__(self):
363 377 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
364 378 self.ui_key, self.ui_value)
365 379
366 380
367 381 class RepoRhodeCodeSetting(Base, BaseModel):
368 382 __tablename__ = 'repo_rhodecode_settings'
369 383 __table_args__ = (
370 384 UniqueConstraint(
371 385 'app_settings_name', 'repository_id',
372 386 name='uq_repo_rhodecode_setting_name_repo_id'),
373 387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
374 388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
375 389 )
376 390
377 391 repository_id = Column(
378 392 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
379 393 nullable=False)
380 394 app_settings_id = Column(
381 395 "app_settings_id", Integer(), nullable=False, unique=True,
382 396 default=None, primary_key=True)
383 397 app_settings_name = Column(
384 398 "app_settings_name", String(255), nullable=True, unique=None,
385 399 default=None)
386 400 _app_settings_value = Column(
387 401 "app_settings_value", String(4096), nullable=True, unique=None,
388 402 default=None)
389 403 _app_settings_type = Column(
390 404 "app_settings_type", String(255), nullable=True, unique=None,
391 405 default=None)
392 406
393 407 repository = relationship('Repository')
394 408
395 409 def __init__(self, repository_id, key='', val='', type='unicode'):
396 410 self.repository_id = repository_id
397 411 self.app_settings_name = key
398 412 self.app_settings_type = type
399 413 self.app_settings_value = val
400 414
401 415 @validates('_app_settings_value')
402 416 def validate_settings_value(self, key, val):
403 417 assert type(val) == unicode
404 418 return val
405 419
406 420 @hybrid_property
407 421 def app_settings_value(self):
408 422 v = self._app_settings_value
409 423 type_ = self.app_settings_type
410 424 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
411 425 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
412 426 return converter(v)
413 427
414 428 @app_settings_value.setter
415 429 def app_settings_value(self, val):
416 430 """
417 431 Setter that will always make sure we use unicode in app_settings_value
418 432
419 433 :param val:
420 434 """
421 435 self._app_settings_value = safe_unicode(val)
422 436
423 437 @hybrid_property
424 438 def app_settings_type(self):
425 439 return self._app_settings_type
426 440
427 441 @app_settings_type.setter
428 442 def app_settings_type(self, val):
429 443 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 444 if val not in SETTINGS_TYPES:
431 445 raise Exception('type must be one of %s got %s'
432 446 % (SETTINGS_TYPES.keys(), val))
433 447 self._app_settings_type = val
434 448
435 449 def __unicode__(self):
436 450 return u"<%s('%s:%s:%s[%s]')>" % (
437 451 self.__class__.__name__, self.repository.repo_name,
438 452 self.app_settings_name, self.app_settings_value,
439 453 self.app_settings_type
440 454 )
441 455
442 456
443 457 class RepoRhodeCodeUi(Base, BaseModel):
444 458 __tablename__ = 'repo_rhodecode_ui'
445 459 __table_args__ = (
446 460 UniqueConstraint(
447 461 'repository_id', 'ui_section', 'ui_key',
448 462 name='uq_repo_rhodecode_ui_repository_id_section_key'),
449 463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
450 464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
451 465 )
452 466
453 467 repository_id = Column(
454 468 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
455 469 nullable=False)
456 470 ui_id = Column(
457 471 "ui_id", Integer(), nullable=False, unique=True, default=None,
458 472 primary_key=True)
459 473 ui_section = Column(
460 474 "ui_section", String(255), nullable=True, unique=None, default=None)
461 475 ui_key = Column(
462 476 "ui_key", String(255), nullable=True, unique=None, default=None)
463 477 ui_value = Column(
464 478 "ui_value", String(255), nullable=True, unique=None, default=None)
465 479 ui_active = Column(
466 480 "ui_active", Boolean(), nullable=True, unique=None, default=True)
467 481
468 482 repository = relationship('Repository')
469 483
470 484 def __repr__(self):
471 485 return '<%s[%s:%s]%s=>%s]>' % (
472 486 self.__class__.__name__, self.repository.repo_name,
473 487 self.ui_section, self.ui_key, self.ui_value)
474 488
475 489
476 490 class User(Base, BaseModel):
477 491 __tablename__ = 'users'
478 492 __table_args__ = (
479 493 UniqueConstraint('username'), UniqueConstraint('email'),
480 494 Index('u_username_idx', 'username'),
481 495 Index('u_email_idx', 'email'),
482 496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
483 497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
484 498 )
485 499 DEFAULT_USER = 'default'
486 500 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
487 501 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
488 502
489 503 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 504 username = Column("username", String(255), nullable=True, unique=None, default=None)
491 505 password = Column("password", String(255), nullable=True, unique=None, default=None)
492 506 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
493 507 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
494 508 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
495 509 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
496 510 _email = Column("email", String(255), nullable=True, unique=None, default=None)
497 511 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
498 512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
499 513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
500 514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
501 515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
502 516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
503 517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
504 518
505 519 user_log = relationship('UserLog')
506 520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
507 521
508 522 repositories = relationship('Repository')
509 523 repository_groups = relationship('RepoGroup')
510 524 user_groups = relationship('UserGroup')
511 525
512 526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
513 527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
514 528
515 529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
516 530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
517 531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
518 532
519 533 group_member = relationship('UserGroupMember', cascade='all')
520 534
521 535 notifications = relationship('UserNotification', cascade='all')
522 536 # notifications assigned to this user
523 537 user_created_notifications = relationship('Notification', cascade='all')
524 538 # comments created by this user
525 539 user_comments = relationship('ChangesetComment', cascade='all')
526 540 # user profile extra info
527 541 user_emails = relationship('UserEmailMap', cascade='all')
528 542 user_ip_map = relationship('UserIpMap', cascade='all')
529 543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
530 544 # gists
531 545 user_gists = relationship('Gist', cascade='all')
532 546 # user pull requests
533 547 user_pull_requests = relationship('PullRequest', cascade='all')
534 548 # external identities
535 549 extenal_identities = relationship(
536 550 'ExternalIdentity',
537 551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
538 552 cascade='all')
539 553
540 554 def __unicode__(self):
541 555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
542 556 self.user_id, self.username)
543 557
544 558 @hybrid_property
545 559 def email(self):
546 560 return self._email
547 561
548 562 @email.setter
549 563 def email(self, val):
550 564 self._email = val.lower() if val else None
551 565
552 566 @property
553 567 def firstname(self):
554 568 # alias for future
555 569 return self.name
556 570
557 571 @property
558 572 def emails(self):
559 573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
560 574 return [self.email] + [x.email for x in other]
561 575
562 576 @property
563 577 def auth_tokens(self):
564 578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
565 579
566 580 @property
567 581 def extra_auth_tokens(self):
568 582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
569 583
570 584 @property
571 585 def feed_token(self):
572 586 feed_tokens = UserApiKeys.query()\
573 587 .filter(UserApiKeys.user == self)\
574 588 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
575 589 .all()
576 590 if feed_tokens:
577 591 return feed_tokens[0].api_key
578 592 else:
579 593 # use the main token so we don't end up with nothing...
580 594 return self.api_key
581 595
582 596 @classmethod
583 597 def extra_valid_auth_tokens(cls, user, role=None):
584 598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
585 599 .filter(or_(UserApiKeys.expires == -1,
586 600 UserApiKeys.expires >= time.time()))
587 601 if role:
588 602 tokens = tokens.filter(or_(UserApiKeys.role == role,
589 603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
590 604 return tokens.all()
591 605
592 606 @property
593 607 def ip_addresses(self):
594 608 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
595 609 return [x.ip_addr for x in ret]
596 610
597 611 @property
598 612 def username_and_name(self):
599 613 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
600 614
601 615 @property
602 616 def username_or_name_or_email(self):
603 617 full_name = self.full_name if self.full_name is not ' ' else None
604 618 return self.username or full_name or self.email
605 619
606 620 @property
607 621 def full_name(self):
608 622 return '%s %s' % (self.firstname, self.lastname)
609 623
610 624 @property
611 625 def full_name_or_username(self):
612 626 return ('%s %s' % (self.firstname, self.lastname)
613 627 if (self.firstname and self.lastname) else self.username)
614 628
615 629 @property
616 630 def full_contact(self):
617 631 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
618 632
619 633 @property
620 634 def short_contact(self):
621 635 return '%s %s' % (self.firstname, self.lastname)
622 636
623 637 @property
624 638 def is_admin(self):
625 639 return self.admin
626 640
627 641 @property
628 642 def AuthUser(self):
629 643 """
630 644 Returns instance of AuthUser for this user
631 645 """
632 646 from rhodecode.lib.auth import AuthUser
633 647 return AuthUser(user_id=self.user_id, api_key=self.api_key,
634 648 username=self.username)
635 649
636 650 @hybrid_property
637 651 def user_data(self):
638 652 if not self._user_data:
639 653 return {}
640 654
641 655 try:
642 656 return json.loads(self._user_data)
643 657 except TypeError:
644 658 return {}
645 659
646 660 @user_data.setter
647 661 def user_data(self, val):
648 662 if not isinstance(val, dict):
649 663 raise Exception('user_data must be dict, got %s' % type(val))
650 664 try:
651 665 self._user_data = json.dumps(val)
652 666 except Exception:
653 667 log.error(traceback.format_exc())
654 668
655 669 @classmethod
656 670 def get_by_username(cls, username, case_insensitive=False,
657 671 cache=False, identity_cache=False):
658 672 session = Session()
659 673
660 674 if case_insensitive:
661 675 q = cls.query().filter(
662 676 func.lower(cls.username) == func.lower(username))
663 677 else:
664 678 q = cls.query().filter(cls.username == username)
665 679
666 680 if cache:
667 681 if identity_cache:
668 682 val = cls.identity_cache(session, 'username', username)
669 683 if val:
670 684 return val
671 685 else:
672 686 q = q.options(
673 687 FromCache("sql_cache_short",
674 688 "get_user_by_name_%s" % _hash_key(username)))
675 689
676 690 return q.scalar()
677 691
678 692 @classmethod
679 693 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
680 694 q = cls.query().filter(cls.api_key == auth_token)
681 695
682 696 if cache:
683 697 q = q.options(FromCache("sql_cache_short",
684 698 "get_auth_token_%s" % auth_token))
685 699 res = q.scalar()
686 700
687 701 if fallback and not res:
688 702 #fallback to additional keys
689 703 _res = UserApiKeys.query()\
690 704 .filter(UserApiKeys.api_key == auth_token)\
691 705 .filter(or_(UserApiKeys.expires == -1,
692 706 UserApiKeys.expires >= time.time()))\
693 707 .first()
694 708 if _res:
695 709 res = _res.user
696 710 return res
697 711
698 712 @classmethod
699 713 def get_by_email(cls, email, case_insensitive=False, cache=False):
700 714
701 715 if case_insensitive:
702 716 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
703 717
704 718 else:
705 719 q = cls.query().filter(cls.email == email)
706 720
707 721 if cache:
708 722 q = q.options(FromCache("sql_cache_short",
709 723 "get_email_key_%s" % email))
710 724
711 725 ret = q.scalar()
712 726 if ret is None:
713 727 q = UserEmailMap.query()
714 728 # try fetching in alternate email map
715 729 if case_insensitive:
716 730 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
717 731 else:
718 732 q = q.filter(UserEmailMap.email == email)
719 733 q = q.options(joinedload(UserEmailMap.user))
720 734 if cache:
721 735 q = q.options(FromCache("sql_cache_short",
722 736 "get_email_map_key_%s" % email))
723 737 ret = getattr(q.scalar(), 'user', None)
724 738
725 739 return ret
726 740
727 741 @classmethod
728 742 def get_from_cs_author(cls, author):
729 743 """
730 744 Tries to get User objects out of commit author string
731 745
732 746 :param author:
733 747 """
734 748 from rhodecode.lib.helpers import email, author_name
735 749 # Valid email in the attribute passed, see if they're in the system
736 750 _email = email(author)
737 751 if _email:
738 752 user = cls.get_by_email(_email, case_insensitive=True)
739 753 if user:
740 754 return user
741 755 # Maybe we can match by username?
742 756 _author = author_name(author)
743 757 user = cls.get_by_username(_author, case_insensitive=True)
744 758 if user:
745 759 return user
746 760
747 761 def update_userdata(self, **kwargs):
748 762 usr = self
749 763 old = usr.user_data
750 764 old.update(**kwargs)
751 765 usr.user_data = old
752 766 Session().add(usr)
753 767 log.debug('updated userdata with ', kwargs)
754 768
755 769 def update_lastlogin(self):
756 770 """Update user lastlogin"""
757 771 self.last_login = datetime.datetime.now()
758 772 Session().add(self)
759 773 log.debug('updated user %s lastlogin', self.username)
760 774
761 775 def update_lastactivity(self):
762 776 """Update user lastactivity"""
763 777 usr = self
764 778 old = usr.user_data
765 779 old.update({'last_activity': time.time()})
766 780 usr.user_data = old
767 781 Session().add(usr)
768 782 log.debug('updated user %s lastactivity', usr.username)
769 783
770 784 def update_password(self, new_password, change_api_key=False):
771 785 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
772 786
773 787 self.password = get_crypt_password(new_password)
774 788 if change_api_key:
775 789 self.api_key = generate_auth_token(self.username)
776 790 Session().add(self)
777 791
778 792 @classmethod
779 793 def get_first_super_admin(cls):
780 794 user = User.query().filter(User.admin == true()).first()
781 795 if user is None:
782 796 raise Exception('FATAL: Missing administrative account!')
783 797 return user
784 798
785 799 @classmethod
786 800 def get_all_super_admins(cls):
787 801 """
788 802 Returns all admin accounts sorted by username
789 803 """
790 804 return User.query().filter(User.admin == true())\
791 805 .order_by(User.username.asc()).all()
792 806
793 807 @classmethod
794 808 def get_default_user(cls, cache=False):
795 809 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
796 810 if user is None:
797 811 raise Exception('FATAL: Missing default account!')
798 812 return user
799 813
800 814 def _get_default_perms(self, user, suffix=''):
801 815 from rhodecode.model.permission import PermissionModel
802 816 return PermissionModel().get_default_perms(user.user_perms, suffix)
803 817
804 818 def get_default_perms(self, suffix=''):
805 819 return self._get_default_perms(self, suffix)
806 820
807 821 def get_api_data(self, include_secrets=False, details='full'):
808 822 """
809 823 Common function for generating user related data for API
810 824
811 825 :param include_secrets: By default secrets in the API data will be replaced
812 826 by a placeholder value to prevent exposing this data by accident. In case
813 827 this data shall be exposed, set this flag to ``True``.
814 828
815 829 :param details: details can be 'basic|full' basic gives only a subset of
816 830 the available user information that includes user_id, name and emails.
817 831 """
818 832 user = self
819 833 user_data = self.user_data
820 834 data = {
821 835 'user_id': user.user_id,
822 836 'username': user.username,
823 837 'firstname': user.name,
824 838 'lastname': user.lastname,
825 839 'email': user.email,
826 840 'emails': user.emails,
827 841 }
828 842 if details == 'basic':
829 843 return data
830 844
831 845 api_key_length = 40
832 846 api_key_replacement = '*' * api_key_length
833 847
834 848 extras = {
835 849 'api_key': api_key_replacement,
836 850 'api_keys': [api_key_replacement],
837 851 'active': user.active,
838 852 'admin': user.admin,
839 853 'extern_type': user.extern_type,
840 854 'extern_name': user.extern_name,
841 855 'last_login': user.last_login,
842 856 'ip_addresses': user.ip_addresses,
843 857 'language': user_data.get('language')
844 858 }
845 859 data.update(extras)
846 860
847 861 if include_secrets:
848 862 data['api_key'] = user.api_key
849 863 data['api_keys'] = user.auth_tokens
850 864 return data
851 865
852 866 def __json__(self):
853 867 data = {
854 868 'full_name': self.full_name,
855 869 'full_name_or_username': self.full_name_or_username,
856 870 'short_contact': self.short_contact,
857 871 'full_contact': self.full_contact,
858 872 }
859 873 data.update(self.get_api_data())
860 874 return data
861 875
862 876
863 877 class UserApiKeys(Base, BaseModel):
864 878 __tablename__ = 'user_api_keys'
865 879 __table_args__ = (
866 880 Index('uak_api_key_idx', 'api_key'),
867 881 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
868 882 UniqueConstraint('api_key'),
869 883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
870 884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
871 885 )
872 886 __mapper_args__ = {}
873 887
874 888 # ApiKey role
875 889 ROLE_ALL = 'token_role_all'
876 890 ROLE_HTTP = 'token_role_http'
877 891 ROLE_VCS = 'token_role_vcs'
878 892 ROLE_API = 'token_role_api'
879 893 ROLE_FEED = 'token_role_feed'
880 894 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
881 895
882 896 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
883 897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
884 898 api_key = Column("api_key", String(255), nullable=False, unique=True)
885 899 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
886 900 expires = Column('expires', Float(53), nullable=False)
887 901 role = Column('role', String(255), nullable=True)
888 902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
889 903
890 904 user = relationship('User', lazy='joined')
891 905
892 906 @classmethod
893 907 def _get_role_name(cls, role):
894 908 return {
895 909 cls.ROLE_ALL: _('all'),
896 910 cls.ROLE_HTTP: _('http/web interface'),
897 911 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
898 912 cls.ROLE_API: _('api calls'),
899 913 cls.ROLE_FEED: _('feed access'),
900 914 }.get(role, role)
901 915
902 916 @property
903 917 def expired(self):
904 918 if self.expires == -1:
905 919 return False
906 920 return time.time() > self.expires
907 921
908 922 @property
909 923 def role_humanized(self):
910 924 return self._get_role_name(self.role)
911 925
912 926
913 927 class UserEmailMap(Base, BaseModel):
914 928 __tablename__ = 'user_email_map'
915 929 __table_args__ = (
916 930 Index('uem_email_idx', 'email'),
917 931 UniqueConstraint('email'),
918 932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
919 933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
920 934 )
921 935 __mapper_args__ = {}
922 936
923 937 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
924 938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
925 939 _email = Column("email", String(255), nullable=True, unique=False, default=None)
926 940 user = relationship('User', lazy='joined')
927 941
928 942 @validates('_email')
929 943 def validate_email(self, key, email):
930 944 # check if this email is not main one
931 945 main_email = Session().query(User).filter(User.email == email).scalar()
932 946 if main_email is not None:
933 947 raise AttributeError('email %s is present is user table' % email)
934 948 return email
935 949
936 950 @hybrid_property
937 951 def email(self):
938 952 return self._email
939 953
940 954 @email.setter
941 955 def email(self, val):
942 956 self._email = val.lower() if val else None
943 957
944 958
945 959 class UserIpMap(Base, BaseModel):
946 960 __tablename__ = 'user_ip_map'
947 961 __table_args__ = (
948 962 UniqueConstraint('user_id', 'ip_addr'),
949 963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
950 964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
951 965 )
952 966 __mapper_args__ = {}
953 967
954 968 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
955 969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
956 970 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
957 971 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
958 972 description = Column("description", String(10000), nullable=True, unique=None, default=None)
959 973 user = relationship('User', lazy='joined')
960 974
961 975 @classmethod
962 976 def _get_ip_range(cls, ip_addr):
963 977 net = ipaddress.ip_network(ip_addr, strict=False)
964 978 return [str(net.network_address), str(net.broadcast_address)]
965 979
966 980 def __json__(self):
967 981 return {
968 982 'ip_addr': self.ip_addr,
969 983 'ip_range': self._get_ip_range(self.ip_addr),
970 984 }
971 985
972 986 def __unicode__(self):
973 987 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
974 988 self.user_id, self.ip_addr)
975 989
976 990 class UserLog(Base, BaseModel):
977 991 __tablename__ = 'user_logs'
978 992 __table_args__ = (
979 993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
980 994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
981 995 )
982 996 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
983 997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
984 998 username = Column("username", String(255), nullable=True, unique=None, default=None)
985 999 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
986 1000 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
987 1001 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
988 1002 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
989 1003 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
990 1004
991 1005 def __unicode__(self):
992 1006 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
993 1007 self.repository_name,
994 1008 self.action)
995 1009
996 1010 @property
997 1011 def action_as_day(self):
998 1012 return datetime.date(*self.action_date.timetuple()[:3])
999 1013
1000 1014 user = relationship('User')
1001 1015 repository = relationship('Repository', cascade='')
1002 1016
1003 1017
1004 1018 class UserGroup(Base, BaseModel):
1005 1019 __tablename__ = 'users_groups'
1006 1020 __table_args__ = (
1007 1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1008 1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1009 1023 )
1010 1024
1011 1025 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1012 1026 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1013 1027 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1014 1028 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1015 1029 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1016 1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1017 1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1018 1032 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1019 1033
1020 1034 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1021 1035 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1022 1036 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1023 1037 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1024 1038 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1025 1039 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1026 1040
1027 1041 user = relationship('User')
1028 1042
1029 1043 @hybrid_property
1030 1044 def group_data(self):
1031 1045 if not self._group_data:
1032 1046 return {}
1033 1047
1034 1048 try:
1035 1049 return json.loads(self._group_data)
1036 1050 except TypeError:
1037 1051 return {}
1038 1052
1039 1053 @group_data.setter
1040 1054 def group_data(self, val):
1041 1055 try:
1042 1056 self._group_data = json.dumps(val)
1043 1057 except Exception:
1044 1058 log.error(traceback.format_exc())
1045 1059
1046 1060 def __unicode__(self):
1047 1061 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1048 1062 self.users_group_id,
1049 1063 self.users_group_name)
1050 1064
1051 1065 @classmethod
1052 1066 def get_by_group_name(cls, group_name, cache=False,
1053 1067 case_insensitive=False):
1054 1068 if case_insensitive:
1055 1069 q = cls.query().filter(func.lower(cls.users_group_name) ==
1056 1070 func.lower(group_name))
1057 1071
1058 1072 else:
1059 1073 q = cls.query().filter(cls.users_group_name == group_name)
1060 1074 if cache:
1061 1075 q = q.options(FromCache(
1062 1076 "sql_cache_short",
1063 1077 "get_group_%s" % _hash_key(group_name)))
1064 1078 return q.scalar()
1065 1079
1066 1080 @classmethod
1067 1081 def get(cls, user_group_id, cache=False):
1068 1082 user_group = cls.query()
1069 1083 if cache:
1070 1084 user_group = user_group.options(FromCache("sql_cache_short",
1071 1085 "get_users_group_%s" % user_group_id))
1072 1086 return user_group.get(user_group_id)
1073 1087
1074 1088 def permissions(self, with_admins=True, with_owner=True):
1075 1089 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1076 1090 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1077 1091 joinedload(UserUserGroupToPerm.user),
1078 1092 joinedload(UserUserGroupToPerm.permission),)
1079 1093
1080 1094 # get owners and admins and permissions. We do a trick of re-writing
1081 1095 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1082 1096 # has a global reference and changing one object propagates to all
1083 1097 # others. This means if admin is also an owner admin_row that change
1084 1098 # would propagate to both objects
1085 1099 perm_rows = []
1086 1100 for _usr in q.all():
1087 1101 usr = AttributeDict(_usr.user.get_dict())
1088 1102 usr.permission = _usr.permission.permission_name
1089 1103 perm_rows.append(usr)
1090 1104
1091 1105 # filter the perm rows by 'default' first and then sort them by
1092 1106 # admin,write,read,none permissions sorted again alphabetically in
1093 1107 # each group
1094 1108 perm_rows = sorted(perm_rows, key=display_sort)
1095 1109
1096 1110 _admin_perm = 'usergroup.admin'
1097 1111 owner_row = []
1098 1112 if with_owner:
1099 1113 usr = AttributeDict(self.user.get_dict())
1100 1114 usr.owner_row = True
1101 1115 usr.permission = _admin_perm
1102 1116 owner_row.append(usr)
1103 1117
1104 1118 super_admin_rows = []
1105 1119 if with_admins:
1106 1120 for usr in User.get_all_super_admins():
1107 1121 # if this admin is also owner, don't double the record
1108 1122 if usr.user_id == owner_row[0].user_id:
1109 1123 owner_row[0].admin_row = True
1110 1124 else:
1111 1125 usr = AttributeDict(usr.get_dict())
1112 1126 usr.admin_row = True
1113 1127 usr.permission = _admin_perm
1114 1128 super_admin_rows.append(usr)
1115 1129
1116 1130 return super_admin_rows + owner_row + perm_rows
1117 1131
1118 1132 def permission_user_groups(self):
1119 1133 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1120 1134 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1121 1135 joinedload(UserGroupUserGroupToPerm.target_user_group),
1122 1136 joinedload(UserGroupUserGroupToPerm.permission),)
1123 1137
1124 1138 perm_rows = []
1125 1139 for _user_group in q.all():
1126 1140 usr = AttributeDict(_user_group.user_group.get_dict())
1127 1141 usr.permission = _user_group.permission.permission_name
1128 1142 perm_rows.append(usr)
1129 1143
1130 1144 return perm_rows
1131 1145
1132 1146 def _get_default_perms(self, user_group, suffix=''):
1133 1147 from rhodecode.model.permission import PermissionModel
1134 1148 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1135 1149
1136 1150 def get_default_perms(self, suffix=''):
1137 1151 return self._get_default_perms(self, suffix)
1138 1152
1139 1153 def get_api_data(self, with_group_members=True, include_secrets=False):
1140 1154 """
1141 1155 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1142 1156 basically forwarded.
1143 1157
1144 1158 """
1145 1159 user_group = self
1146 1160
1147 1161 data = {
1148 1162 'users_group_id': user_group.users_group_id,
1149 1163 'group_name': user_group.users_group_name,
1150 1164 'group_description': user_group.user_group_description,
1151 1165 'active': user_group.users_group_active,
1152 1166 'owner': user_group.user.username,
1153 1167 }
1154 1168 if with_group_members:
1155 1169 users = []
1156 1170 for user in user_group.members:
1157 1171 user = user.user
1158 1172 users.append(user.get_api_data(include_secrets=include_secrets))
1159 1173 data['users'] = users
1160 1174
1161 1175 return data
1162 1176
1163 1177
1164 1178 class UserGroupMember(Base, BaseModel):
1165 1179 __tablename__ = 'users_groups_members'
1166 1180 __table_args__ = (
1167 1181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1168 1182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1169 1183 )
1170 1184
1171 1185 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 1186 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1173 1187 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1174 1188
1175 1189 user = relationship('User', lazy='joined')
1176 1190 users_group = relationship('UserGroup')
1177 1191
1178 1192 def __init__(self, gr_id='', u_id=''):
1179 1193 self.users_group_id = gr_id
1180 1194 self.user_id = u_id
1181 1195
1182 1196
1183 1197 class RepositoryField(Base, BaseModel):
1184 1198 __tablename__ = 'repositories_fields'
1185 1199 __table_args__ = (
1186 1200 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1187 1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1188 1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1189 1203 )
1190 1204 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1191 1205
1192 1206 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1193 1207 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1194 1208 field_key = Column("field_key", String(250))
1195 1209 field_label = Column("field_label", String(1024), nullable=False)
1196 1210 field_value = Column("field_value", String(10000), nullable=False)
1197 1211 field_desc = Column("field_desc", String(1024), nullable=False)
1198 1212 field_type = Column("field_type", String(255), nullable=False, unique=None)
1199 1213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1200 1214
1201 1215 repository = relationship('Repository')
1202 1216
1203 1217 @property
1204 1218 def field_key_prefixed(self):
1205 1219 return 'ex_%s' % self.field_key
1206 1220
1207 1221 @classmethod
1208 1222 def un_prefix_key(cls, key):
1209 1223 if key.startswith(cls.PREFIX):
1210 1224 return key[len(cls.PREFIX):]
1211 1225 return key
1212 1226
1213 1227 @classmethod
1214 1228 def get_by_key_name(cls, key, repo):
1215 1229 row = cls.query()\
1216 1230 .filter(cls.repository == repo)\
1217 1231 .filter(cls.field_key == key).scalar()
1218 1232 return row
1219 1233
1220 1234
1221 1235 class Repository(Base, BaseModel):
1222 1236 __tablename__ = 'repositories'
1223 1237 __table_args__ = (
1224 1238 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1225 1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1226 1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1227 1241 )
1228 1242 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1229 1243 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1230 1244
1231 1245 STATE_CREATED = 'repo_state_created'
1232 1246 STATE_PENDING = 'repo_state_pending'
1233 1247 STATE_ERROR = 'repo_state_error'
1234 1248
1235 1249 LOCK_AUTOMATIC = 'lock_auto'
1236 1250 LOCK_API = 'lock_api'
1237 1251 LOCK_WEB = 'lock_web'
1238 1252 LOCK_PULL = 'lock_pull'
1239 1253
1240 1254 NAME_SEP = URL_SEP
1241 1255
1242 1256 repo_id = Column(
1243 1257 "repo_id", Integer(), nullable=False, unique=True, default=None,
1244 1258 primary_key=True)
1245 1259 _repo_name = Column(
1246 1260 "repo_name", Text(), nullable=False, default=None)
1247 1261 _repo_name_hash = Column(
1248 1262 "repo_name_hash", String(255), nullable=False, unique=True)
1249 1263 repo_state = Column("repo_state", String(255), nullable=True)
1250 1264
1251 1265 clone_uri = Column(
1252 1266 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1253 1267 default=None)
1254 1268 repo_type = Column(
1255 1269 "repo_type", String(255), nullable=False, unique=False, default=None)
1256 1270 user_id = Column(
1257 1271 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1258 1272 unique=False, default=None)
1259 1273 private = Column(
1260 1274 "private", Boolean(), nullable=True, unique=None, default=None)
1261 1275 enable_statistics = Column(
1262 1276 "statistics", Boolean(), nullable=True, unique=None, default=True)
1263 1277 enable_downloads = Column(
1264 1278 "downloads", Boolean(), nullable=True, unique=None, default=True)
1265 1279 description = Column(
1266 1280 "description", String(10000), nullable=True, unique=None, default=None)
1267 1281 created_on = Column(
1268 1282 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1269 1283 default=datetime.datetime.now)
1270 1284 updated_on = Column(
1271 1285 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1272 1286 default=datetime.datetime.now)
1273 1287 _landing_revision = Column(
1274 1288 "landing_revision", String(255), nullable=False, unique=False,
1275 1289 default=None)
1276 1290 enable_locking = Column(
1277 1291 "enable_locking", Boolean(), nullable=False, unique=None,
1278 1292 default=False)
1279 1293 _locked = Column(
1280 1294 "locked", String(255), nullable=True, unique=False, default=None)
1281 1295 _changeset_cache = Column(
1282 1296 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1283 1297
1284 1298 fork_id = Column(
1285 1299 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1286 1300 nullable=True, unique=False, default=None)
1287 1301 group_id = Column(
1288 1302 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1289 1303 unique=False, default=None)
1290 1304
1291 1305 user = relationship('User', lazy='joined')
1292 1306 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1293 1307 group = relationship('RepoGroup', lazy='joined')
1294 1308 repo_to_perm = relationship(
1295 1309 'UserRepoToPerm', cascade='all',
1296 1310 order_by='UserRepoToPerm.repo_to_perm_id')
1297 1311 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1298 1312 stats = relationship('Statistics', cascade='all', uselist=False)
1299 1313
1300 1314 followers = relationship(
1301 1315 'UserFollowing',
1302 1316 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1303 1317 cascade='all')
1304 1318 extra_fields = relationship(
1305 1319 'RepositoryField', cascade="all, delete, delete-orphan")
1306 1320 logs = relationship('UserLog')
1307 1321 comments = relationship(
1308 1322 'ChangesetComment', cascade="all, delete, delete-orphan")
1309 1323 pull_requests_source = relationship(
1310 1324 'PullRequest',
1311 1325 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1312 1326 cascade="all, delete, delete-orphan")
1313 1327 pull_requests_target = relationship(
1314 1328 'PullRequest',
1315 1329 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1316 1330 cascade="all, delete, delete-orphan")
1317 1331 ui = relationship('RepoRhodeCodeUi', cascade="all")
1318 1332 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1319 1333
1320 1334 def __unicode__(self):
1321 1335 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1322 1336 safe_unicode(self.repo_name))
1323 1337
1324 1338 @hybrid_property
1325 1339 def landing_rev(self):
1326 1340 # always should return [rev_type, rev]
1327 1341 if self._landing_revision:
1328 1342 _rev_info = self._landing_revision.split(':')
1329 1343 if len(_rev_info) < 2:
1330 1344 _rev_info.insert(0, 'rev')
1331 1345 return [_rev_info[0], _rev_info[1]]
1332 1346 return [None, None]
1333 1347
1334 1348 @landing_rev.setter
1335 1349 def landing_rev(self, val):
1336 1350 if ':' not in val:
1337 1351 raise ValueError('value must be delimited with `:` and consist '
1338 1352 'of <rev_type>:<rev>, got %s instead' % val)
1339 1353 self._landing_revision = val
1340 1354
1341 1355 @hybrid_property
1342 1356 def locked(self):
1343 1357 if self._locked:
1344 1358 user_id, timelocked, reason = self._locked.split(':')
1345 1359 lock_values = int(user_id), timelocked, reason
1346 1360 else:
1347 1361 lock_values = [None, None, None]
1348 1362 return lock_values
1349 1363
1350 1364 @locked.setter
1351 1365 def locked(self, val):
1352 1366 if val and isinstance(val, (list, tuple)):
1353 1367 self._locked = ':'.join(map(str, val))
1354 1368 else:
1355 1369 self._locked = None
1356 1370
1357 1371 @hybrid_property
1358 1372 def changeset_cache(self):
1359 1373 from rhodecode.lib.vcs.backends.base import EmptyCommit
1360 1374 dummy = EmptyCommit().__json__()
1361 1375 if not self._changeset_cache:
1362 1376 return dummy
1363 1377 try:
1364 1378 return json.loads(self._changeset_cache)
1365 1379 except TypeError:
1366 1380 return dummy
1367 1381 except Exception:
1368 1382 log.error(traceback.format_exc())
1369 1383 return dummy
1370 1384
1371 1385 @changeset_cache.setter
1372 1386 def changeset_cache(self, val):
1373 1387 try:
1374 1388 self._changeset_cache = json.dumps(val)
1375 1389 except Exception:
1376 1390 log.error(traceback.format_exc())
1377 1391
1378 1392 @hybrid_property
1379 1393 def repo_name(self):
1380 1394 return self._repo_name
1381 1395
1382 1396 @repo_name.setter
1383 1397 def repo_name(self, value):
1384 1398 self._repo_name = value
1385 1399 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1386 1400
1387 1401 @classmethod
1388 1402 def normalize_repo_name(cls, repo_name):
1389 1403 """
1390 1404 Normalizes os specific repo_name to the format internally stored inside
1391 1405 database using URL_SEP
1392 1406
1393 1407 :param cls:
1394 1408 :param repo_name:
1395 1409 """
1396 1410 return cls.NAME_SEP.join(repo_name.split(os.sep))
1397 1411
1398 1412 @classmethod
1399 1413 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1400 1414 session = Session()
1401 1415 q = session.query(cls).filter(cls.repo_name == repo_name)
1402 1416
1403 1417 if cache:
1404 1418 if identity_cache:
1405 1419 val = cls.identity_cache(session, 'repo_name', repo_name)
1406 1420 if val:
1407 1421 return val
1408 1422 else:
1409 1423 q = q.options(
1410 1424 FromCache("sql_cache_short",
1411 1425 "get_repo_by_name_%s" % _hash_key(repo_name)))
1412 1426
1413 1427 return q.scalar()
1414 1428
1415 1429 @classmethod
1416 1430 def get_by_full_path(cls, repo_full_path):
1417 1431 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1418 1432 repo_name = cls.normalize_repo_name(repo_name)
1419 1433 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1420 1434
1421 1435 @classmethod
1422 1436 def get_repo_forks(cls, repo_id):
1423 1437 return cls.query().filter(Repository.fork_id == repo_id)
1424 1438
1425 1439 @classmethod
1426 1440 def base_path(cls):
1427 1441 """
1428 1442 Returns base path when all repos are stored
1429 1443
1430 1444 :param cls:
1431 1445 """
1432 1446 q = Session().query(RhodeCodeUi)\
1433 1447 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1434 1448 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1435 1449 return q.one().ui_value
1436 1450
1437 1451 @classmethod
1438 1452 def is_valid(cls, repo_name):
1439 1453 """
1440 1454 returns True if given repo name is a valid filesystem repository
1441 1455
1442 1456 :param cls:
1443 1457 :param repo_name:
1444 1458 """
1445 1459 from rhodecode.lib.utils import is_valid_repo
1446 1460
1447 1461 return is_valid_repo(repo_name, cls.base_path())
1448 1462
1449 1463 @classmethod
1450 1464 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1451 1465 case_insensitive=True):
1452 1466 q = Repository.query()
1453 1467
1454 1468 if not isinstance(user_id, Optional):
1455 1469 q = q.filter(Repository.user_id == user_id)
1456 1470
1457 1471 if not isinstance(group_id, Optional):
1458 1472 q = q.filter(Repository.group_id == group_id)
1459 1473
1460 1474 if case_insensitive:
1461 1475 q = q.order_by(func.lower(Repository.repo_name))
1462 1476 else:
1463 1477 q = q.order_by(Repository.repo_name)
1464 1478 return q.all()
1465 1479
1466 1480 @property
1467 1481 def forks(self):
1468 1482 """
1469 1483 Return forks of this repo
1470 1484 """
1471 1485 return Repository.get_repo_forks(self.repo_id)
1472 1486
1473 1487 @property
1474 1488 def parent(self):
1475 1489 """
1476 1490 Returns fork parent
1477 1491 """
1478 1492 return self.fork
1479 1493
1480 1494 @property
1481 1495 def just_name(self):
1482 1496 return self.repo_name.split(self.NAME_SEP)[-1]
1483 1497
1484 1498 @property
1485 1499 def groups_with_parents(self):
1486 1500 groups = []
1487 1501 if self.group is None:
1488 1502 return groups
1489 1503
1490 1504 cur_gr = self.group
1491 1505 groups.insert(0, cur_gr)
1492 1506 while 1:
1493 1507 gr = getattr(cur_gr, 'parent_group', None)
1494 1508 cur_gr = cur_gr.parent_group
1495 1509 if gr is None:
1496 1510 break
1497 1511 groups.insert(0, gr)
1498 1512
1499 1513 return groups
1500 1514
1501 1515 @property
1502 1516 def groups_and_repo(self):
1503 1517 return self.groups_with_parents, self
1504 1518
1505 1519 @LazyProperty
1506 1520 def repo_path(self):
1507 1521 """
1508 1522 Returns base full path for that repository means where it actually
1509 1523 exists on a filesystem
1510 1524 """
1511 1525 q = Session().query(RhodeCodeUi).filter(
1512 1526 RhodeCodeUi.ui_key == self.NAME_SEP)
1513 1527 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1514 1528 return q.one().ui_value
1515 1529
1516 1530 @property
1517 1531 def repo_full_path(self):
1518 1532 p = [self.repo_path]
1519 1533 # we need to split the name by / since this is how we store the
1520 1534 # names in the database, but that eventually needs to be converted
1521 1535 # into a valid system path
1522 1536 p += self.repo_name.split(self.NAME_SEP)
1523 1537 return os.path.join(*map(safe_unicode, p))
1524 1538
1525 1539 @property
1526 1540 def cache_keys(self):
1527 1541 """
1528 1542 Returns associated cache keys for that repo
1529 1543 """
1530 1544 return CacheKey.query()\
1531 1545 .filter(CacheKey.cache_args == self.repo_name)\
1532 1546 .order_by(CacheKey.cache_key)\
1533 1547 .all()
1534 1548
1535 1549 def get_new_name(self, repo_name):
1536 1550 """
1537 1551 returns new full repository name based on assigned group and new new
1538 1552
1539 1553 :param group_name:
1540 1554 """
1541 1555 path_prefix = self.group.full_path_splitted if self.group else []
1542 1556 return self.NAME_SEP.join(path_prefix + [repo_name])
1543 1557
1544 1558 @property
1545 1559 def _config(self):
1546 1560 """
1547 1561 Returns db based config object.
1548 1562 """
1549 1563 from rhodecode.lib.utils import make_db_config
1550 1564 return make_db_config(clear_session=False, repo=self)
1551 1565
1552 1566 def permissions(self, with_admins=True, with_owner=True):
1553 1567 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1554 1568 q = q.options(joinedload(UserRepoToPerm.repository),
1555 1569 joinedload(UserRepoToPerm.user),
1556 1570 joinedload(UserRepoToPerm.permission),)
1557 1571
1558 1572 # get owners and admins and permissions. We do a trick of re-writing
1559 1573 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1560 1574 # has a global reference and changing one object propagates to all
1561 1575 # others. This means if admin is also an owner admin_row that change
1562 1576 # would propagate to both objects
1563 1577 perm_rows = []
1564 1578 for _usr in q.all():
1565 1579 usr = AttributeDict(_usr.user.get_dict())
1566 1580 usr.permission = _usr.permission.permission_name
1567 1581 perm_rows.append(usr)
1568 1582
1569 1583 # filter the perm rows by 'default' first and then sort them by
1570 1584 # admin,write,read,none permissions sorted again alphabetically in
1571 1585 # each group
1572 1586 perm_rows = sorted(perm_rows, key=display_sort)
1573 1587
1574 1588 _admin_perm = 'repository.admin'
1575 1589 owner_row = []
1576 1590 if with_owner:
1577 1591 usr = AttributeDict(self.user.get_dict())
1578 1592 usr.owner_row = True
1579 1593 usr.permission = _admin_perm
1580 1594 owner_row.append(usr)
1581 1595
1582 1596 super_admin_rows = []
1583 1597 if with_admins:
1584 1598 for usr in User.get_all_super_admins():
1585 1599 # if this admin is also owner, don't double the record
1586 1600 if usr.user_id == owner_row[0].user_id:
1587 1601 owner_row[0].admin_row = True
1588 1602 else:
1589 1603 usr = AttributeDict(usr.get_dict())
1590 1604 usr.admin_row = True
1591 1605 usr.permission = _admin_perm
1592 1606 super_admin_rows.append(usr)
1593 1607
1594 1608 return super_admin_rows + owner_row + perm_rows
1595 1609
1596 1610 def permission_user_groups(self):
1597 1611 q = UserGroupRepoToPerm.query().filter(
1598 1612 UserGroupRepoToPerm.repository == self)
1599 1613 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1600 1614 joinedload(UserGroupRepoToPerm.users_group),
1601 1615 joinedload(UserGroupRepoToPerm.permission),)
1602 1616
1603 1617 perm_rows = []
1604 1618 for _user_group in q.all():
1605 1619 usr = AttributeDict(_user_group.users_group.get_dict())
1606 1620 usr.permission = _user_group.permission.permission_name
1607 1621 perm_rows.append(usr)
1608 1622
1609 1623 return perm_rows
1610 1624
1611 1625 def get_api_data(self, include_secrets=False):
1612 1626 """
1613 1627 Common function for generating repo api data
1614 1628
1615 1629 :param include_secrets: See :meth:`User.get_api_data`.
1616 1630
1617 1631 """
1618 1632 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1619 1633 # move this methods on models level.
1620 1634 from rhodecode.model.settings import SettingsModel
1621 1635
1622 1636 repo = self
1623 1637 _user_id, _time, _reason = self.locked
1624 1638
1625 1639 data = {
1626 1640 'repo_id': repo.repo_id,
1627 1641 'repo_name': repo.repo_name,
1628 1642 'repo_type': repo.repo_type,
1629 1643 'clone_uri': repo.clone_uri or '',
1630 1644 'private': repo.private,
1631 1645 'created_on': repo.created_on,
1632 1646 'description': repo.description,
1633 1647 'landing_rev': repo.landing_rev,
1634 1648 'owner': repo.user.username,
1635 1649 'fork_of': repo.fork.repo_name if repo.fork else None,
1636 1650 'enable_statistics': repo.enable_statistics,
1637 1651 'enable_locking': repo.enable_locking,
1638 1652 'enable_downloads': repo.enable_downloads,
1639 1653 'last_changeset': repo.changeset_cache,
1640 1654 'locked_by': User.get(_user_id).get_api_data(
1641 1655 include_secrets=include_secrets) if _user_id else None,
1642 1656 'locked_date': time_to_datetime(_time) if _time else None,
1643 1657 'lock_reason': _reason if _reason else None,
1644 1658 }
1645 1659
1646 1660 # TODO: mikhail: should be per-repo settings here
1647 1661 rc_config = SettingsModel().get_all_settings()
1648 1662 repository_fields = str2bool(
1649 1663 rc_config.get('rhodecode_repository_fields'))
1650 1664 if repository_fields:
1651 1665 for f in self.extra_fields:
1652 1666 data[f.field_key_prefixed] = f.field_value
1653 1667
1654 1668 return data
1655 1669
1656 1670 @classmethod
1657 1671 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1658 1672 if not lock_time:
1659 1673 lock_time = time.time()
1660 1674 if not lock_reason:
1661 1675 lock_reason = cls.LOCK_AUTOMATIC
1662 1676 repo.locked = [user_id, lock_time, lock_reason]
1663 1677 Session().add(repo)
1664 1678 Session().commit()
1665 1679
1666 1680 @classmethod
1667 1681 def unlock(cls, repo):
1668 1682 repo.locked = None
1669 1683 Session().add(repo)
1670 1684 Session().commit()
1671 1685
1672 1686 @classmethod
1673 1687 def getlock(cls, repo):
1674 1688 return repo.locked
1675 1689
1676 1690 def is_user_lock(self, user_id):
1677 1691 if self.lock[0]:
1678 1692 lock_user_id = safe_int(self.lock[0])
1679 1693 user_id = safe_int(user_id)
1680 1694 # both are ints, and they are equal
1681 1695 return all([lock_user_id, user_id]) and lock_user_id == user_id
1682 1696
1683 1697 return False
1684 1698
1685 1699 def get_locking_state(self, action, user_id, only_when_enabled=True):
1686 1700 """
1687 1701 Checks locking on this repository, if locking is enabled and lock is
1688 1702 present returns a tuple of make_lock, locked, locked_by.
1689 1703 make_lock can have 3 states None (do nothing) True, make lock
1690 1704 False release lock, This value is later propagated to hooks, which
1691 1705 do the locking. Think about this as signals passed to hooks what to do.
1692 1706
1693 1707 """
1694 1708 # TODO: johbo: This is part of the business logic and should be moved
1695 1709 # into the RepositoryModel.
1696 1710
1697 1711 if action not in ('push', 'pull'):
1698 1712 raise ValueError("Invalid action value: %s" % repr(action))
1699 1713
1700 1714 # defines if locked error should be thrown to user
1701 1715 currently_locked = False
1702 1716 # defines if new lock should be made, tri-state
1703 1717 make_lock = None
1704 1718 repo = self
1705 1719 user = User.get(user_id)
1706 1720
1707 1721 lock_info = repo.locked
1708 1722
1709 1723 if repo and (repo.enable_locking or not only_when_enabled):
1710 1724 if action == 'push':
1711 1725 # check if it's already locked !, if it is compare users
1712 1726 locked_by_user_id = lock_info[0]
1713 1727 if user.user_id == locked_by_user_id:
1714 1728 log.debug(
1715 1729 'Got `push` action from user %s, now unlocking', user)
1716 1730 # unlock if we have push from user who locked
1717 1731 make_lock = False
1718 1732 else:
1719 1733 # we're not the same user who locked, ban with
1720 1734 # code defined in settings (default is 423 HTTP Locked) !
1721 1735 log.debug('Repo %s is currently locked by %s', repo, user)
1722 1736 currently_locked = True
1723 1737 elif action == 'pull':
1724 1738 # [0] user [1] date
1725 1739 if lock_info[0] and lock_info[1]:
1726 1740 log.debug('Repo %s is currently locked by %s', repo, user)
1727 1741 currently_locked = True
1728 1742 else:
1729 1743 log.debug('Setting lock on repo %s by %s', repo, user)
1730 1744 make_lock = True
1731 1745
1732 1746 else:
1733 1747 log.debug('Repository %s do not have locking enabled', repo)
1734 1748
1735 1749 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1736 1750 make_lock, currently_locked, lock_info)
1737 1751
1738 1752 from rhodecode.lib.auth import HasRepoPermissionAny
1739 1753 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1740 1754 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1741 1755 # if we don't have at least write permission we cannot make a lock
1742 1756 log.debug('lock state reset back to FALSE due to lack '
1743 1757 'of at least read permission')
1744 1758 make_lock = False
1745 1759
1746 1760 return make_lock, currently_locked, lock_info
1747 1761
1748 1762 @property
1749 1763 def last_db_change(self):
1750 1764 return self.updated_on
1751 1765
1752 1766 @property
1753 1767 def clone_uri_hidden(self):
1754 1768 clone_uri = self.clone_uri
1755 1769 if clone_uri:
1756 1770 import urlobject
1757 url_obj = urlobject.URLObject(self.clone_uri)
1771 url_obj = urlobject.URLObject(clone_uri)
1758 1772 if url_obj.password:
1759 1773 clone_uri = url_obj.with_password('*****')
1760 1774 return clone_uri
1761 1775
1762 1776 def clone_url(self, **override):
1763 1777 qualified_home_url = url('home', qualified=True)
1764 1778
1765 1779 uri_tmpl = None
1766 1780 if 'with_id' in override:
1767 1781 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1768 1782 del override['with_id']
1769 1783
1770 1784 if 'uri_tmpl' in override:
1771 1785 uri_tmpl = override['uri_tmpl']
1772 1786 del override['uri_tmpl']
1773 1787
1774 1788 # we didn't override our tmpl from **overrides
1775 1789 if not uri_tmpl:
1776 1790 uri_tmpl = self.DEFAULT_CLONE_URI
1777 1791 try:
1778 1792 from pylons import tmpl_context as c
1779 1793 uri_tmpl = c.clone_uri_tmpl
1780 1794 except Exception:
1781 1795 # in any case if we call this outside of request context,
1782 1796 # ie, not having tmpl_context set up
1783 1797 pass
1784 1798
1785 1799 return get_clone_url(uri_tmpl=uri_tmpl,
1786 1800 qualifed_home_url=qualified_home_url,
1787 1801 repo_name=self.repo_name,
1788 1802 repo_id=self.repo_id, **override)
1789 1803
1790 1804 def set_state(self, state):
1791 1805 self.repo_state = state
1792 1806 Session().add(self)
1793 1807 #==========================================================================
1794 1808 # SCM PROPERTIES
1795 1809 #==========================================================================
1796 1810
1797 1811 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1798 1812 return get_commit_safe(
1799 1813 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1800 1814
1801 1815 def get_changeset(self, rev=None, pre_load=None):
1802 1816 warnings.warn("Use get_commit", DeprecationWarning)
1803 1817 commit_id = None
1804 1818 commit_idx = None
1805 1819 if isinstance(rev, basestring):
1806 1820 commit_id = rev
1807 1821 else:
1808 1822 commit_idx = rev
1809 1823 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1810 1824 pre_load=pre_load)
1811 1825
1812 1826 def get_landing_commit(self):
1813 1827 """
1814 1828 Returns landing commit, or if that doesn't exist returns the tip
1815 1829 """
1816 1830 _rev_type, _rev = self.landing_rev
1817 1831 commit = self.get_commit(_rev)
1818 1832 if isinstance(commit, EmptyCommit):
1819 1833 return self.get_commit()
1820 1834 return commit
1821 1835
1822 1836 def update_commit_cache(self, cs_cache=None, config=None):
1823 1837 """
1824 1838 Update cache of last changeset for repository, keys should be::
1825 1839
1826 1840 short_id
1827 1841 raw_id
1828 1842 revision
1829 1843 parents
1830 1844 message
1831 1845 date
1832 1846 author
1833 1847
1834 1848 :param cs_cache:
1835 1849 """
1836 1850 from rhodecode.lib.vcs.backends.base import BaseChangeset
1837 1851 if cs_cache is None:
1838 1852 # use no-cache version here
1839 1853 scm_repo = self.scm_instance(cache=False, config=config)
1840 1854 if scm_repo:
1841 1855 cs_cache = scm_repo.get_commit(
1842 1856 pre_load=["author", "date", "message", "parents"])
1843 1857 else:
1844 1858 cs_cache = EmptyCommit()
1845 1859
1846 1860 if isinstance(cs_cache, BaseChangeset):
1847 1861 cs_cache = cs_cache.__json__()
1848 1862
1849 1863 def is_outdated(new_cs_cache):
1850 1864 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1851 1865 return True
1852 1866 return False
1853 1867
1854 1868 # check if we have maybe already latest cached revision
1855 1869 if is_outdated(cs_cache) or not self.changeset_cache:
1856 1870 _default = datetime.datetime.fromtimestamp(0)
1857 1871 last_change = cs_cache.get('date') or _default
1858 1872 log.debug('updated repo %s with new cs cache %s',
1859 1873 self.repo_name, cs_cache)
1860 1874 self.updated_on = last_change
1861 1875 self.changeset_cache = cs_cache
1862 1876 Session().add(self)
1863 1877 Session().commit()
1864 1878 else:
1865 1879 log.debug('Skipping update_commit_cache for repo:`%s` '
1866 1880 'commit already with latest changes', self.repo_name)
1867 1881
1868 1882 @property
1869 1883 def tip(self):
1870 1884 return self.get_commit('tip')
1871 1885
1872 1886 @property
1873 1887 def author(self):
1874 1888 return self.tip.author
1875 1889
1876 1890 @property
1877 1891 def last_change(self):
1878 1892 return self.scm_instance().last_change
1879 1893
1880 1894 def get_comments(self, revisions=None):
1881 1895 """
1882 1896 Returns comments for this repository grouped by revisions
1883 1897
1884 1898 :param revisions: filter query by revisions only
1885 1899 """
1886 1900 cmts = ChangesetComment.query()\
1887 1901 .filter(ChangesetComment.repo == self)
1888 1902 if revisions:
1889 1903 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1890 1904 grouped = collections.defaultdict(list)
1891 1905 for cmt in cmts.all():
1892 1906 grouped[cmt.revision].append(cmt)
1893 1907 return grouped
1894 1908
1895 1909 def statuses(self, revisions=None):
1896 1910 """
1897 1911 Returns statuses for this repository
1898 1912
1899 1913 :param revisions: list of revisions to get statuses for
1900 1914 """
1901 1915 statuses = ChangesetStatus.query()\
1902 1916 .filter(ChangesetStatus.repo == self)\
1903 1917 .filter(ChangesetStatus.version == 0)
1904 1918
1905 1919 if revisions:
1906 1920 # Try doing the filtering in chunks to avoid hitting limits
1907 1921 size = 500
1908 1922 status_results = []
1909 1923 for chunk in xrange(0, len(revisions), size):
1910 1924 status_results += statuses.filter(
1911 1925 ChangesetStatus.revision.in_(
1912 1926 revisions[chunk: chunk+size])
1913 1927 ).all()
1914 1928 else:
1915 1929 status_results = statuses.all()
1916 1930
1917 1931 grouped = {}
1918 1932
1919 1933 # maybe we have open new pullrequest without a status?
1920 1934 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1921 1935 status_lbl = ChangesetStatus.get_status_lbl(stat)
1922 1936 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1923 1937 for rev in pr.revisions:
1924 1938 pr_id = pr.pull_request_id
1925 1939 pr_repo = pr.target_repo.repo_name
1926 1940 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1927 1941
1928 1942 for stat in status_results:
1929 1943 pr_id = pr_repo = None
1930 1944 if stat.pull_request:
1931 1945 pr_id = stat.pull_request.pull_request_id
1932 1946 pr_repo = stat.pull_request.target_repo.repo_name
1933 1947 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1934 1948 pr_id, pr_repo]
1935 1949 return grouped
1936 1950
1937 1951 # ==========================================================================
1938 1952 # SCM CACHE INSTANCE
1939 1953 # ==========================================================================
1940 1954
1941 1955 def scm_instance(self, **kwargs):
1942 1956 import rhodecode
1943 1957
1944 1958 # Passing a config will not hit the cache currently only used
1945 1959 # for repo2dbmapper
1946 1960 config = kwargs.pop('config', None)
1947 1961 cache = kwargs.pop('cache', None)
1948 1962 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1949 1963 # if cache is NOT defined use default global, else we have a full
1950 1964 # control over cache behaviour
1951 1965 if cache is None and full_cache and not config:
1952 1966 return self._get_instance_cached()
1953 1967 return self._get_instance(cache=bool(cache), config=config)
1954 1968
1955 1969 def _get_instance_cached(self):
1956 1970 @cache_region('long_term')
1957 1971 def _get_repo(cache_key):
1958 1972 return self._get_instance()
1959 1973
1960 1974 invalidator_context = CacheKey.repo_context_cache(
1961 1975 _get_repo, self.repo_name, None)
1962 1976
1963 1977 with invalidator_context as context:
1964 1978 context.invalidate()
1965 1979 repo = context.compute()
1966 1980
1967 1981 return repo
1968 1982
1969 1983 def _get_instance(self, cache=True, config=None):
1970 1984 repo_full_path = self.repo_full_path
1971 1985 try:
1972 1986 vcs_alias = get_scm(repo_full_path)[0]
1973 1987 log.debug(
1974 1988 'Creating instance of %s repository from %s',
1975 1989 vcs_alias, repo_full_path)
1976 1990 backend = get_backend(vcs_alias)
1977 1991 except VCSError:
1978 1992 log.exception(
1979 1993 'Perhaps this repository is in db and not in '
1980 1994 'filesystem run rescan repositories with '
1981 1995 '"destroy old data" option from admin panel')
1982 1996 return
1983 1997
1984 1998 config = config or self._config
1985 1999 custom_wire = {
1986 2000 'cache': cache # controls the vcs.remote cache
1987 2001 }
1988 2002 repo = backend(
1989 2003 safe_str(repo_full_path), config=config, create=False,
1990 2004 with_wire=custom_wire)
1991 2005
1992 2006 return repo
1993 2007
1994 2008 def __json__(self):
1995 2009 return {'landing_rev': self.landing_rev}
1996 2010
1997 2011 def get_dict(self):
1998 2012
1999 2013 # Since we transformed `repo_name` to a hybrid property, we need to
2000 2014 # keep compatibility with the code which uses `repo_name` field.
2001 2015
2002 2016 result = super(Repository, self).get_dict()
2003 2017 result['repo_name'] = result.pop('_repo_name', None)
2004 2018 return result
2005 2019
2006 2020
2007 2021 class RepoGroup(Base, BaseModel):
2008 2022 __tablename__ = 'groups'
2009 2023 __table_args__ = (
2010 2024 UniqueConstraint('group_name', 'group_parent_id'),
2011 2025 CheckConstraint('group_id != group_parent_id'),
2012 2026 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2013 2027 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2014 2028 )
2015 2029 __mapper_args__ = {'order_by': 'group_name'}
2016 2030
2017 2031 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2018 2032
2019 2033 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2020 2034 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2021 2035 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2022 2036 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2023 2037 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2024 2038 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2025 2039 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2026 2040
2027 2041 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2028 2042 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2029 2043 parent_group = relationship('RepoGroup', remote_side=group_id)
2030 2044 user = relationship('User')
2031 2045
2032 2046 def __init__(self, group_name='', parent_group=None):
2033 2047 self.group_name = group_name
2034 2048 self.parent_group = parent_group
2035 2049
2036 2050 def __unicode__(self):
2037 2051 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2038 2052 self.group_name)
2039 2053
2040 2054 @classmethod
2041 2055 def _generate_choice(cls, repo_group):
2042 2056 from webhelpers.html import literal as _literal
2043 2057 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2044 2058 return repo_group.group_id, _name(repo_group.full_path_splitted)
2045 2059
2046 2060 @classmethod
2047 2061 def groups_choices(cls, groups=None, show_empty_group=True):
2048 2062 if not groups:
2049 2063 groups = cls.query().all()
2050 2064
2051 2065 repo_groups = []
2052 2066 if show_empty_group:
2053 2067 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2054 2068
2055 2069 repo_groups.extend([cls._generate_choice(x) for x in groups])
2056 2070
2057 2071 repo_groups = sorted(
2058 2072 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2059 2073 return repo_groups
2060 2074
2061 2075 @classmethod
2062 2076 def url_sep(cls):
2063 2077 return URL_SEP
2064 2078
2065 2079 @classmethod
2066 2080 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2067 2081 if case_insensitive:
2068 2082 gr = cls.query().filter(func.lower(cls.group_name)
2069 2083 == func.lower(group_name))
2070 2084 else:
2071 2085 gr = cls.query().filter(cls.group_name == group_name)
2072 2086 if cache:
2073 2087 gr = gr.options(FromCache(
2074 2088 "sql_cache_short",
2075 2089 "get_group_%s" % _hash_key(group_name)))
2076 2090 return gr.scalar()
2077 2091
2078 2092 @classmethod
2079 2093 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2080 2094 case_insensitive=True):
2081 2095 q = RepoGroup.query()
2082 2096
2083 2097 if not isinstance(user_id, Optional):
2084 2098 q = q.filter(RepoGroup.user_id == user_id)
2085 2099
2086 2100 if not isinstance(group_id, Optional):
2087 2101 q = q.filter(RepoGroup.group_parent_id == group_id)
2088 2102
2089 2103 if case_insensitive:
2090 2104 q = q.order_by(func.lower(RepoGroup.group_name))
2091 2105 else:
2092 2106 q = q.order_by(RepoGroup.group_name)
2093 2107 return q.all()
2094 2108
2095 2109 @property
2096 2110 def parents(self):
2097 2111 parents_recursion_limit = 10
2098 2112 groups = []
2099 2113 if self.parent_group is None:
2100 2114 return groups
2101 2115 cur_gr = self.parent_group
2102 2116 groups.insert(0, cur_gr)
2103 2117 cnt = 0
2104 2118 while 1:
2105 2119 cnt += 1
2106 2120 gr = getattr(cur_gr, 'parent_group', None)
2107 2121 cur_gr = cur_gr.parent_group
2108 2122 if gr is None:
2109 2123 break
2110 2124 if cnt == parents_recursion_limit:
2111 2125 # this will prevent accidental infinit loops
2112 2126 log.error(('more than %s parents found for group %s, stopping '
2113 2127 'recursive parent fetching' % (parents_recursion_limit, self)))
2114 2128 break
2115 2129
2116 2130 groups.insert(0, gr)
2117 2131 return groups
2118 2132
2119 2133 @property
2120 2134 def children(self):
2121 2135 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2122 2136
2123 2137 @property
2124 2138 def name(self):
2125 2139 return self.group_name.split(RepoGroup.url_sep())[-1]
2126 2140
2127 2141 @property
2128 2142 def full_path(self):
2129 2143 return self.group_name
2130 2144
2131 2145 @property
2132 2146 def full_path_splitted(self):
2133 2147 return self.group_name.split(RepoGroup.url_sep())
2134 2148
2135 2149 @property
2136 2150 def repositories(self):
2137 2151 return Repository.query()\
2138 2152 .filter(Repository.group == self)\
2139 2153 .order_by(Repository.repo_name)
2140 2154
2141 2155 @property
2142 2156 def repositories_recursive_count(self):
2143 2157 cnt = self.repositories.count()
2144 2158
2145 2159 def children_count(group):
2146 2160 cnt = 0
2147 2161 for child in group.children:
2148 2162 cnt += child.repositories.count()
2149 2163 cnt += children_count(child)
2150 2164 return cnt
2151 2165
2152 2166 return cnt + children_count(self)
2153 2167
2154 2168 def _recursive_objects(self, include_repos=True):
2155 2169 all_ = []
2156 2170
2157 2171 def _get_members(root_gr):
2158 2172 if include_repos:
2159 2173 for r in root_gr.repositories:
2160 2174 all_.append(r)
2161 2175 childs = root_gr.children.all()
2162 2176 if childs:
2163 2177 for gr in childs:
2164 2178 all_.append(gr)
2165 2179 _get_members(gr)
2166 2180
2167 2181 _get_members(self)
2168 2182 return [self] + all_
2169 2183
2170 2184 def recursive_groups_and_repos(self):
2171 2185 """
2172 2186 Recursive return all groups, with repositories in those groups
2173 2187 """
2174 2188 return self._recursive_objects()
2175 2189
2176 2190 def recursive_groups(self):
2177 2191 """
2178 2192 Returns all children groups for this group including children of children
2179 2193 """
2180 2194 return self._recursive_objects(include_repos=False)
2181 2195
2182 2196 def get_new_name(self, group_name):
2183 2197 """
2184 2198 returns new full group name based on parent and new name
2185 2199
2186 2200 :param group_name:
2187 2201 """
2188 2202 path_prefix = (self.parent_group.full_path_splitted if
2189 2203 self.parent_group else [])
2190 2204 return RepoGroup.url_sep().join(path_prefix + [group_name])
2191 2205
2192 2206 def permissions(self, with_admins=True, with_owner=True):
2193 2207 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2194 2208 q = q.options(joinedload(UserRepoGroupToPerm.group),
2195 2209 joinedload(UserRepoGroupToPerm.user),
2196 2210 joinedload(UserRepoGroupToPerm.permission),)
2197 2211
2198 2212 # get owners and admins and permissions. We do a trick of re-writing
2199 2213 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2200 2214 # has a global reference and changing one object propagates to all
2201 2215 # others. This means if admin is also an owner admin_row that change
2202 2216 # would propagate to both objects
2203 2217 perm_rows = []
2204 2218 for _usr in q.all():
2205 2219 usr = AttributeDict(_usr.user.get_dict())
2206 2220 usr.permission = _usr.permission.permission_name
2207 2221 perm_rows.append(usr)
2208 2222
2209 2223 # filter the perm rows by 'default' first and then sort them by
2210 2224 # admin,write,read,none permissions sorted again alphabetically in
2211 2225 # each group
2212 2226 perm_rows = sorted(perm_rows, key=display_sort)
2213 2227
2214 2228 _admin_perm = 'group.admin'
2215 2229 owner_row = []
2216 2230 if with_owner:
2217 2231 usr = AttributeDict(self.user.get_dict())
2218 2232 usr.owner_row = True
2219 2233 usr.permission = _admin_perm
2220 2234 owner_row.append(usr)
2221 2235
2222 2236 super_admin_rows = []
2223 2237 if with_admins:
2224 2238 for usr in User.get_all_super_admins():
2225 2239 # if this admin is also owner, don't double the record
2226 2240 if usr.user_id == owner_row[0].user_id:
2227 2241 owner_row[0].admin_row = True
2228 2242 else:
2229 2243 usr = AttributeDict(usr.get_dict())
2230 2244 usr.admin_row = True
2231 2245 usr.permission = _admin_perm
2232 2246 super_admin_rows.append(usr)
2233 2247
2234 2248 return super_admin_rows + owner_row + perm_rows
2235 2249
2236 2250 def permission_user_groups(self):
2237 2251 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2238 2252 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2239 2253 joinedload(UserGroupRepoGroupToPerm.users_group),
2240 2254 joinedload(UserGroupRepoGroupToPerm.permission),)
2241 2255
2242 2256 perm_rows = []
2243 2257 for _user_group in q.all():
2244 2258 usr = AttributeDict(_user_group.users_group.get_dict())
2245 2259 usr.permission = _user_group.permission.permission_name
2246 2260 perm_rows.append(usr)
2247 2261
2248 2262 return perm_rows
2249 2263
2250 2264 def get_api_data(self):
2251 2265 """
2252 2266 Common function for generating api data
2253 2267
2254 2268 """
2255 2269 group = self
2256 2270 data = {
2257 2271 'group_id': group.group_id,
2258 2272 'group_name': group.group_name,
2259 2273 'group_description': group.group_description,
2260 2274 'parent_group': group.parent_group.group_name if group.parent_group else None,
2261 2275 'repositories': [x.repo_name for x in group.repositories],
2262 2276 'owner': group.user.username,
2263 2277 }
2264 2278 return data
2265 2279
2266 2280
2267 2281 class Permission(Base, BaseModel):
2268 2282 __tablename__ = 'permissions'
2269 2283 __table_args__ = (
2270 2284 Index('p_perm_name_idx', 'permission_name'),
2271 2285 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2272 2286 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2273 2287 )
2274 2288 PERMS = [
2275 2289 ('hg.admin', _('RhodeCode Super Administrator')),
2276 2290
2277 2291 ('repository.none', _('Repository no access')),
2278 2292 ('repository.read', _('Repository read access')),
2279 2293 ('repository.write', _('Repository write access')),
2280 2294 ('repository.admin', _('Repository admin access')),
2281 2295
2282 2296 ('group.none', _('Repository group no access')),
2283 2297 ('group.read', _('Repository group read access')),
2284 2298 ('group.write', _('Repository group write access')),
2285 2299 ('group.admin', _('Repository group admin access')),
2286 2300
2287 2301 ('usergroup.none', _('User group no access')),
2288 2302 ('usergroup.read', _('User group read access')),
2289 2303 ('usergroup.write', _('User group write access')),
2290 2304 ('usergroup.admin', _('User group admin access')),
2291 2305
2292 2306 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2293 2307 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2294 2308
2295 2309 ('hg.usergroup.create.false', _('User Group creation disabled')),
2296 2310 ('hg.usergroup.create.true', _('User Group creation enabled')),
2297 2311
2298 2312 ('hg.create.none', _('Repository creation disabled')),
2299 2313 ('hg.create.repository', _('Repository creation enabled')),
2300 2314 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2301 2315 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2302 2316
2303 2317 ('hg.fork.none', _('Repository forking disabled')),
2304 2318 ('hg.fork.repository', _('Repository forking enabled')),
2305 2319
2306 2320 ('hg.register.none', _('Registration disabled')),
2307 2321 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2308 2322 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2309 2323
2310 2324 ('hg.extern_activate.manual', _('Manual activation of external account')),
2311 2325 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2312 2326
2313 2327 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2314 2328 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2315 2329 ]
2316 2330
2317 2331 # definition of system default permissions for DEFAULT user
2318 2332 DEFAULT_USER_PERMISSIONS = [
2319 2333 'repository.read',
2320 2334 'group.read',
2321 2335 'usergroup.read',
2322 2336 'hg.create.repository',
2323 2337 'hg.repogroup.create.false',
2324 2338 'hg.usergroup.create.false',
2325 2339 'hg.create.write_on_repogroup.true',
2326 2340 'hg.fork.repository',
2327 2341 'hg.register.manual_activate',
2328 2342 'hg.extern_activate.auto',
2329 2343 'hg.inherit_default_perms.true',
2330 2344 ]
2331 2345
2332 2346 # defines which permissions are more important higher the more important
2333 2347 # Weight defines which permissions are more important.
2334 2348 # The higher number the more important.
2335 2349 PERM_WEIGHTS = {
2336 2350 'repository.none': 0,
2337 2351 'repository.read': 1,
2338 2352 'repository.write': 3,
2339 2353 'repository.admin': 4,
2340 2354
2341 2355 'group.none': 0,
2342 2356 'group.read': 1,
2343 2357 'group.write': 3,
2344 2358 'group.admin': 4,
2345 2359
2346 2360 'usergroup.none': 0,
2347 2361 'usergroup.read': 1,
2348 2362 'usergroup.write': 3,
2349 2363 'usergroup.admin': 4,
2350 2364
2351 2365 'hg.repogroup.create.false': 0,
2352 2366 'hg.repogroup.create.true': 1,
2353 2367
2354 2368 'hg.usergroup.create.false': 0,
2355 2369 'hg.usergroup.create.true': 1,
2356 2370
2357 2371 'hg.fork.none': 0,
2358 2372 'hg.fork.repository': 1,
2359 2373 'hg.create.none': 0,
2360 2374 'hg.create.repository': 1
2361 2375 }
2362 2376
2363 2377 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2364 2378 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2365 2379 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2366 2380
2367 2381 def __unicode__(self):
2368 2382 return u"<%s('%s:%s')>" % (
2369 2383 self.__class__.__name__, self.permission_id, self.permission_name
2370 2384 )
2371 2385
2372 2386 @classmethod
2373 2387 def get_by_key(cls, key):
2374 2388 return cls.query().filter(cls.permission_name == key).scalar()
2375 2389
2376 2390 @classmethod
2377 2391 def get_default_repo_perms(cls, user_id, repo_id=None):
2378 2392 q = Session().query(UserRepoToPerm, Repository, Permission)\
2379 2393 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2380 2394 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2381 2395 .filter(UserRepoToPerm.user_id == user_id)
2382 2396 if repo_id:
2383 2397 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2384 2398 return q.all()
2385 2399
2386 2400 @classmethod
2387 2401 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2388 2402 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2389 2403 .join(
2390 2404 Permission,
2391 2405 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2392 2406 .join(
2393 2407 Repository,
2394 2408 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2395 2409 .join(
2396 2410 UserGroup,
2397 2411 UserGroupRepoToPerm.users_group_id ==
2398 2412 UserGroup.users_group_id)\
2399 2413 .join(
2400 2414 UserGroupMember,
2401 2415 UserGroupRepoToPerm.users_group_id ==
2402 2416 UserGroupMember.users_group_id)\
2403 2417 .filter(
2404 2418 UserGroupMember.user_id == user_id,
2405 2419 UserGroup.users_group_active == true())
2406 2420 if repo_id:
2407 2421 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2408 2422 return q.all()
2409 2423
2410 2424 @classmethod
2411 2425 def get_default_group_perms(cls, user_id, repo_group_id=None):
2412 2426 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2413 2427 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2414 2428 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2415 2429 .filter(UserRepoGroupToPerm.user_id == user_id)
2416 2430 if repo_group_id:
2417 2431 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2418 2432 return q.all()
2419 2433
2420 2434 @classmethod
2421 2435 def get_default_group_perms_from_user_group(
2422 2436 cls, user_id, repo_group_id=None):
2423 2437 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2424 2438 .join(
2425 2439 Permission,
2426 2440 UserGroupRepoGroupToPerm.permission_id ==
2427 2441 Permission.permission_id)\
2428 2442 .join(
2429 2443 RepoGroup,
2430 2444 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2431 2445 .join(
2432 2446 UserGroup,
2433 2447 UserGroupRepoGroupToPerm.users_group_id ==
2434 2448 UserGroup.users_group_id)\
2435 2449 .join(
2436 2450 UserGroupMember,
2437 2451 UserGroupRepoGroupToPerm.users_group_id ==
2438 2452 UserGroupMember.users_group_id)\
2439 2453 .filter(
2440 2454 UserGroupMember.user_id == user_id,
2441 2455 UserGroup.users_group_active == true())
2442 2456 if repo_group_id:
2443 2457 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2444 2458 return q.all()
2445 2459
2446 2460 @classmethod
2447 2461 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2448 2462 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2449 2463 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2450 2464 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2451 2465 .filter(UserUserGroupToPerm.user_id == user_id)
2452 2466 if user_group_id:
2453 2467 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2454 2468 return q.all()
2455 2469
2456 2470 @classmethod
2457 2471 def get_default_user_group_perms_from_user_group(
2458 2472 cls, user_id, user_group_id=None):
2459 2473 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2460 2474 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2461 2475 .join(
2462 2476 Permission,
2463 2477 UserGroupUserGroupToPerm.permission_id ==
2464 2478 Permission.permission_id)\
2465 2479 .join(
2466 2480 TargetUserGroup,
2467 2481 UserGroupUserGroupToPerm.target_user_group_id ==
2468 2482 TargetUserGroup.users_group_id)\
2469 2483 .join(
2470 2484 UserGroup,
2471 2485 UserGroupUserGroupToPerm.user_group_id ==
2472 2486 UserGroup.users_group_id)\
2473 2487 .join(
2474 2488 UserGroupMember,
2475 2489 UserGroupUserGroupToPerm.user_group_id ==
2476 2490 UserGroupMember.users_group_id)\
2477 2491 .filter(
2478 2492 UserGroupMember.user_id == user_id,
2479 2493 UserGroup.users_group_active == true())
2480 2494 if user_group_id:
2481 2495 q = q.filter(
2482 2496 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2483 2497
2484 2498 return q.all()
2485 2499
2486 2500
2487 2501 class UserRepoToPerm(Base, BaseModel):
2488 2502 __tablename__ = 'repo_to_perm'
2489 2503 __table_args__ = (
2490 2504 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2491 2505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2492 2506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2493 2507 )
2494 2508 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2495 2509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2496 2510 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2497 2511 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2498 2512
2499 2513 user = relationship('User')
2500 2514 repository = relationship('Repository')
2501 2515 permission = relationship('Permission')
2502 2516
2503 2517 @classmethod
2504 2518 def create(cls, user, repository, permission):
2505 2519 n = cls()
2506 2520 n.user = user
2507 2521 n.repository = repository
2508 2522 n.permission = permission
2509 2523 Session().add(n)
2510 2524 return n
2511 2525
2512 2526 def __unicode__(self):
2513 2527 return u'<%s => %s >' % (self.user, self.repository)
2514 2528
2515 2529
2516 2530 class UserUserGroupToPerm(Base, BaseModel):
2517 2531 __tablename__ = 'user_user_group_to_perm'
2518 2532 __table_args__ = (
2519 2533 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2520 2534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2521 2535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2522 2536 )
2523 2537 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2524 2538 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2525 2539 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2526 2540 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2527 2541
2528 2542 user = relationship('User')
2529 2543 user_group = relationship('UserGroup')
2530 2544 permission = relationship('Permission')
2531 2545
2532 2546 @classmethod
2533 2547 def create(cls, user, user_group, permission):
2534 2548 n = cls()
2535 2549 n.user = user
2536 2550 n.user_group = user_group
2537 2551 n.permission = permission
2538 2552 Session().add(n)
2539 2553 return n
2540 2554
2541 2555 def __unicode__(self):
2542 2556 return u'<%s => %s >' % (self.user, self.user_group)
2543 2557
2544 2558
2545 2559 class UserToPerm(Base, BaseModel):
2546 2560 __tablename__ = 'user_to_perm'
2547 2561 __table_args__ = (
2548 2562 UniqueConstraint('user_id', 'permission_id'),
2549 2563 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2550 2564 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2551 2565 )
2552 2566 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2553 2567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2554 2568 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2555 2569
2556 2570 user = relationship('User')
2557 2571 permission = relationship('Permission', lazy='joined')
2558 2572
2559 2573 def __unicode__(self):
2560 2574 return u'<%s => %s >' % (self.user, self.permission)
2561 2575
2562 2576
2563 2577 class UserGroupRepoToPerm(Base, BaseModel):
2564 2578 __tablename__ = 'users_group_repo_to_perm'
2565 2579 __table_args__ = (
2566 2580 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2567 2581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2568 2582 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2569 2583 )
2570 2584 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2571 2585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2572 2586 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2573 2587 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2574 2588
2575 2589 users_group = relationship('UserGroup')
2576 2590 permission = relationship('Permission')
2577 2591 repository = relationship('Repository')
2578 2592
2579 2593 @classmethod
2580 2594 def create(cls, users_group, repository, permission):
2581 2595 n = cls()
2582 2596 n.users_group = users_group
2583 2597 n.repository = repository
2584 2598 n.permission = permission
2585 2599 Session().add(n)
2586 2600 return n
2587 2601
2588 2602 def __unicode__(self):
2589 2603 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2590 2604
2591 2605
2592 2606 class UserGroupUserGroupToPerm(Base, BaseModel):
2593 2607 __tablename__ = 'user_group_user_group_to_perm'
2594 2608 __table_args__ = (
2595 2609 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2596 2610 CheckConstraint('target_user_group_id != user_group_id'),
2597 2611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2598 2612 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2599 2613 )
2600 2614 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2601 2615 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2602 2616 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2603 2617 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2604 2618
2605 2619 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2606 2620 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2607 2621 permission = relationship('Permission')
2608 2622
2609 2623 @classmethod
2610 2624 def create(cls, target_user_group, user_group, permission):
2611 2625 n = cls()
2612 2626 n.target_user_group = target_user_group
2613 2627 n.user_group = user_group
2614 2628 n.permission = permission
2615 2629 Session().add(n)
2616 2630 return n
2617 2631
2618 2632 def __unicode__(self):
2619 2633 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2620 2634
2621 2635
2622 2636 class UserGroupToPerm(Base, BaseModel):
2623 2637 __tablename__ = 'users_group_to_perm'
2624 2638 __table_args__ = (
2625 2639 UniqueConstraint('users_group_id', 'permission_id',),
2626 2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2627 2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2628 2642 )
2629 2643 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2630 2644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2631 2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2632 2646
2633 2647 users_group = relationship('UserGroup')
2634 2648 permission = relationship('Permission')
2635 2649
2636 2650
2637 2651 class UserRepoGroupToPerm(Base, BaseModel):
2638 2652 __tablename__ = 'user_repo_group_to_perm'
2639 2653 __table_args__ = (
2640 2654 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2641 2655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2642 2656 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2643 2657 )
2644 2658
2645 2659 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2646 2660 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2647 2661 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2648 2662 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2649 2663
2650 2664 user = relationship('User')
2651 2665 group = relationship('RepoGroup')
2652 2666 permission = relationship('Permission')
2653 2667
2654 2668 @classmethod
2655 2669 def create(cls, user, repository_group, permission):
2656 2670 n = cls()
2657 2671 n.user = user
2658 2672 n.group = repository_group
2659 2673 n.permission = permission
2660 2674 Session().add(n)
2661 2675 return n
2662 2676
2663 2677
2664 2678 class UserGroupRepoGroupToPerm(Base, BaseModel):
2665 2679 __tablename__ = 'users_group_repo_group_to_perm'
2666 2680 __table_args__ = (
2667 2681 UniqueConstraint('users_group_id', 'group_id'),
2668 2682 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2669 2683 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2670 2684 )
2671 2685
2672 2686 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2673 2687 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2674 2688 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2675 2689 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2676 2690
2677 2691 users_group = relationship('UserGroup')
2678 2692 permission = relationship('Permission')
2679 2693 group = relationship('RepoGroup')
2680 2694
2681 2695 @classmethod
2682 2696 def create(cls, user_group, repository_group, permission):
2683 2697 n = cls()
2684 2698 n.users_group = user_group
2685 2699 n.group = repository_group
2686 2700 n.permission = permission
2687 2701 Session().add(n)
2688 2702 return n
2689 2703
2690 2704 def __unicode__(self):
2691 2705 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2692 2706
2693 2707
2694 2708 class Statistics(Base, BaseModel):
2695 2709 __tablename__ = 'statistics'
2696 2710 __table_args__ = (
2697 2711 UniqueConstraint('repository_id'),
2698 2712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2699 2713 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2700 2714 )
2701 2715 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2702 2716 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2703 2717 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2704 2718 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2705 2719 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2706 2720 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2707 2721
2708 2722 repository = relationship('Repository', single_parent=True)
2709 2723
2710 2724
2711 2725 class UserFollowing(Base, BaseModel):
2712 2726 __tablename__ = 'user_followings'
2713 2727 __table_args__ = (
2714 2728 UniqueConstraint('user_id', 'follows_repository_id'),
2715 2729 UniqueConstraint('user_id', 'follows_user_id'),
2716 2730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2717 2731 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2718 2732 )
2719 2733
2720 2734 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2721 2735 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2722 2736 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2723 2737 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2724 2738 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2725 2739
2726 2740 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2727 2741
2728 2742 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2729 2743 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2730 2744
2731 2745 @classmethod
2732 2746 def get_repo_followers(cls, repo_id):
2733 2747 return cls.query().filter(cls.follows_repo_id == repo_id)
2734 2748
2735 2749
2736 2750 class CacheKey(Base, BaseModel):
2737 2751 __tablename__ = 'cache_invalidation'
2738 2752 __table_args__ = (
2739 2753 UniqueConstraint('cache_key'),
2740 2754 Index('key_idx', 'cache_key'),
2741 2755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2742 2756 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2743 2757 )
2744 2758 CACHE_TYPE_ATOM = 'ATOM'
2745 2759 CACHE_TYPE_RSS = 'RSS'
2746 2760 CACHE_TYPE_README = 'README'
2747 2761
2748 2762 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2749 2763 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2750 2764 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2751 2765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2752 2766
2753 2767 def __init__(self, cache_key, cache_args=''):
2754 2768 self.cache_key = cache_key
2755 2769 self.cache_args = cache_args
2756 2770 self.cache_active = False
2757 2771
2758 2772 def __unicode__(self):
2759 2773 return u"<%s('%s:%s[%s]')>" % (
2760 2774 self.__class__.__name__,
2761 2775 self.cache_id, self.cache_key, self.cache_active)
2762 2776
2763 2777 def _cache_key_partition(self):
2764 2778 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2765 2779 return prefix, repo_name, suffix
2766 2780
2767 2781 def get_prefix(self):
2768 2782 """
2769 2783 Try to extract prefix from existing cache key. The key could consist
2770 2784 of prefix, repo_name, suffix
2771 2785 """
2772 2786 # this returns prefix, repo_name, suffix
2773 2787 return self._cache_key_partition()[0]
2774 2788
2775 2789 def get_suffix(self):
2776 2790 """
2777 2791 get suffix that might have been used in _get_cache_key to
2778 2792 generate self.cache_key. Only used for informational purposes
2779 2793 in repo_edit.html.
2780 2794 """
2781 2795 # prefix, repo_name, suffix
2782 2796 return self._cache_key_partition()[2]
2783 2797
2784 2798 @classmethod
2785 2799 def delete_all_cache(cls):
2786 2800 """
2787 2801 Delete all cache keys from database.
2788 2802 Should only be run when all instances are down and all entries
2789 2803 thus stale.
2790 2804 """
2791 2805 cls.query().delete()
2792 2806 Session().commit()
2793 2807
2794 2808 @classmethod
2795 2809 def get_cache_key(cls, repo_name, cache_type):
2796 2810 """
2797 2811
2798 2812 Generate a cache key for this process of RhodeCode instance.
2799 2813 Prefix most likely will be process id or maybe explicitly set
2800 2814 instance_id from .ini file.
2801 2815 """
2802 2816 import rhodecode
2803 2817 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2804 2818
2805 2819 repo_as_unicode = safe_unicode(repo_name)
2806 2820 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2807 2821 if cache_type else repo_as_unicode
2808 2822
2809 2823 return u'{}{}'.format(prefix, key)
2810 2824
2811 2825 @classmethod
2812 2826 def set_invalidate(cls, repo_name, delete=False):
2813 2827 """
2814 2828 Mark all caches of a repo as invalid in the database.
2815 2829 """
2816 2830
2817 2831 try:
2818 2832 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2819 2833 if delete:
2820 2834 log.debug('cache objects deleted for repo %s',
2821 2835 safe_str(repo_name))
2822 2836 qry.delete()
2823 2837 else:
2824 2838 log.debug('cache objects marked as invalid for repo %s',
2825 2839 safe_str(repo_name))
2826 2840 qry.update({"cache_active": False})
2827 2841
2828 2842 Session().commit()
2829 2843 except Exception:
2830 2844 log.exception(
2831 2845 'Cache key invalidation failed for repository %s',
2832 2846 safe_str(repo_name))
2833 2847 Session().rollback()
2834 2848
2835 2849 @classmethod
2836 2850 def get_active_cache(cls, cache_key):
2837 2851 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2838 2852 if inv_obj:
2839 2853 return inv_obj
2840 2854 return None
2841 2855
2842 2856 @classmethod
2843 2857 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2844 2858 """
2845 2859 @cache_region('long_term')
2846 2860 def _heavy_calculation(cache_key):
2847 2861 return 'result'
2848 2862
2849 2863 cache_context = CacheKey.repo_context_cache(
2850 2864 _heavy_calculation, repo_name, cache_type)
2851 2865
2852 2866 with cache_context as context:
2853 2867 context.invalidate()
2854 2868 computed = context.compute()
2855 2869
2856 2870 assert computed == 'result'
2857 2871 """
2858 2872 from rhodecode.lib import caches
2859 2873 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2860 2874
2861 2875
2862 2876 class ChangesetComment(Base, BaseModel):
2863 2877 __tablename__ = 'changeset_comments'
2864 2878 __table_args__ = (
2865 2879 Index('cc_revision_idx', 'revision'),
2866 2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2867 2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2868 2882 )
2869 2883
2870 2884 COMMENT_OUTDATED = u'comment_outdated'
2871 2885
2872 2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2873 2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2874 2888 revision = Column('revision', String(40), nullable=True)
2875 2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2876 2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2877 2891 line_no = Column('line_no', Unicode(10), nullable=True)
2878 2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2879 2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2880 2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2881 2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2882 2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2883 2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2884 2898 renderer = Column('renderer', Unicode(64), nullable=True)
2885 2899 display_state = Column('display_state', Unicode(128), nullable=True)
2886 2900
2887 2901 author = relationship('User', lazy='joined')
2888 2902 repo = relationship('Repository')
2889 2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2890 2904 pull_request = relationship('PullRequest', lazy='joined')
2891 2905 pull_request_version = relationship('PullRequestVersion')
2892 2906
2893 2907 @classmethod
2894 2908 def get_users(cls, revision=None, pull_request_id=None):
2895 2909 """
2896 2910 Returns user associated with this ChangesetComment. ie those
2897 2911 who actually commented
2898 2912
2899 2913 :param cls:
2900 2914 :param revision:
2901 2915 """
2902 2916 q = Session().query(User)\
2903 2917 .join(ChangesetComment.author)
2904 2918 if revision:
2905 2919 q = q.filter(cls.revision == revision)
2906 2920 elif pull_request_id:
2907 2921 q = q.filter(cls.pull_request_id == pull_request_id)
2908 2922 return q.all()
2909 2923
2910 2924 def render(self, mentions=False):
2911 2925 from rhodecode.lib import helpers as h
2912 2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2913 2927
2914 2928 def __repr__(self):
2915 2929 if self.comment_id:
2916 2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2917 2931 else:
2918 2932 return '<DB:ChangesetComment at %#x>' % id(self)
2919 2933
2920 2934
2921 2935 class ChangesetStatus(Base, BaseModel):
2922 2936 __tablename__ = 'changeset_statuses'
2923 2937 __table_args__ = (
2924 2938 Index('cs_revision_idx', 'revision'),
2925 2939 Index('cs_version_idx', 'version'),
2926 2940 UniqueConstraint('repo_id', 'revision', 'version'),
2927 2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2928 2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2929 2943 )
2930 2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2931 2945 STATUS_APPROVED = 'approved'
2932 2946 STATUS_REJECTED = 'rejected'
2933 2947 STATUS_UNDER_REVIEW = 'under_review'
2934 2948
2935 2949 STATUSES = [
2936 2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2937 2951 (STATUS_APPROVED, _("Approved")),
2938 2952 (STATUS_REJECTED, _("Rejected")),
2939 2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2940 2954 ]
2941 2955
2942 2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2943 2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2944 2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2945 2959 revision = Column('revision', String(40), nullable=False)
2946 2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2947 2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2948 2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2949 2963 version = Column('version', Integer(), nullable=False, default=0)
2950 2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2951 2965
2952 2966 author = relationship('User', lazy='joined')
2953 2967 repo = relationship('Repository')
2954 2968 comment = relationship('ChangesetComment', lazy='joined')
2955 2969 pull_request = relationship('PullRequest', lazy='joined')
2956 2970
2957 2971 def __unicode__(self):
2958 2972 return u"<%s('%s[%s]:%s')>" % (
2959 2973 self.__class__.__name__,
2960 2974 self.status, self.version, self.author
2961 2975 )
2962 2976
2963 2977 @classmethod
2964 2978 def get_status_lbl(cls, value):
2965 2979 return dict(cls.STATUSES).get(value)
2966 2980
2967 2981 @property
2968 2982 def status_lbl(self):
2969 2983 return ChangesetStatus.get_status_lbl(self.status)
2970 2984
2971 2985
2972 2986 class _PullRequestBase(BaseModel):
2973 2987 """
2974 2988 Common attributes of pull request and version entries.
2975 2989 """
2976 2990
2977 2991 # .status values
2978 2992 STATUS_NEW = u'new'
2979 2993 STATUS_OPEN = u'open'
2980 2994 STATUS_CLOSED = u'closed'
2981 2995
2982 2996 title = Column('title', Unicode(255), nullable=True)
2983 2997 description = Column(
2984 2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2985 2999 nullable=True)
2986 3000 # new/open/closed status of pull request (not approve/reject/etc)
2987 3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
2988 3002 created_on = Column(
2989 3003 'created_on', DateTime(timezone=False), nullable=False,
2990 3004 default=datetime.datetime.now)
2991 3005 updated_on = Column(
2992 3006 'updated_on', DateTime(timezone=False), nullable=False,
2993 3007 default=datetime.datetime.now)
2994 3008
2995 3009 @declared_attr
2996 3010 def user_id(cls):
2997 3011 return Column(
2998 3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
2999 3013 unique=None)
3000 3014
3001 3015 # 500 revisions max
3002 3016 _revisions = Column(
3003 3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3004 3018
3005 3019 @declared_attr
3006 3020 def source_repo_id(cls):
3007 3021 # TODO: dan: rename column to source_repo_id
3008 3022 return Column(
3009 3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3010 3024 nullable=False)
3011 3025
3012 3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3013 3027
3014 3028 @declared_attr
3015 3029 def target_repo_id(cls):
3016 3030 # TODO: dan: rename column to target_repo_id
3017 3031 return Column(
3018 3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3019 3033 nullable=False)
3020 3034
3021 3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3022 3036
3023 3037 # TODO: dan: rename column to last_merge_source_rev
3024 3038 _last_merge_source_rev = Column(
3025 3039 'last_merge_org_rev', String(40), nullable=True)
3026 3040 # TODO: dan: rename column to last_merge_target_rev
3027 3041 _last_merge_target_rev = Column(
3028 3042 'last_merge_other_rev', String(40), nullable=True)
3029 3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3030 3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3031 3045
3032 3046 @hybrid_property
3033 3047 def revisions(self):
3034 3048 return self._revisions.split(':') if self._revisions else []
3035 3049
3036 3050 @revisions.setter
3037 3051 def revisions(self, val):
3038 3052 self._revisions = ':'.join(val)
3039 3053
3040 3054 @declared_attr
3041 3055 def author(cls):
3042 3056 return relationship('User', lazy='joined')
3043 3057
3044 3058 @declared_attr
3045 3059 def source_repo(cls):
3046 3060 return relationship(
3047 3061 'Repository',
3048 3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3049 3063
3050 3064 @property
3051 3065 def source_ref_parts(self):
3052 3066 refs = self.source_ref.split(':')
3053 3067 return Reference(refs[0], refs[1], refs[2])
3054 3068
3055 3069 @declared_attr
3056 3070 def target_repo(cls):
3057 3071 return relationship(
3058 3072 'Repository',
3059 3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3060 3074
3061 3075 @property
3062 3076 def target_ref_parts(self):
3063 3077 refs = self.target_ref.split(':')
3064 3078 return Reference(refs[0], refs[1], refs[2])
3065 3079
3066 3080
3067 3081 class PullRequest(Base, _PullRequestBase):
3068 3082 __tablename__ = 'pull_requests'
3069 3083 __table_args__ = (
3070 3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3071 3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3072 3086 )
3073 3087
3074 3088 pull_request_id = Column(
3075 3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3076 3090
3077 3091 def __repr__(self):
3078 3092 if self.pull_request_id:
3079 3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3080 3094 else:
3081 3095 return '<DB:PullRequest at %#x>' % id(self)
3082 3096
3083 3097 reviewers = relationship('PullRequestReviewers',
3084 3098 cascade="all, delete, delete-orphan")
3085 3099 statuses = relationship('ChangesetStatus')
3086 3100 comments = relationship('ChangesetComment',
3087 3101 cascade="all, delete, delete-orphan")
3088 3102 versions = relationship('PullRequestVersion',
3089 3103 cascade="all, delete, delete-orphan")
3090 3104
3091 3105 def is_closed(self):
3092 3106 return self.status == self.STATUS_CLOSED
3093 3107
3094 3108 def get_api_data(self):
3095 3109 from rhodecode.model.pull_request import PullRequestModel
3096 3110 pull_request = self
3097 3111 merge_status = PullRequestModel().merge_status(pull_request)
3098 3112 data = {
3099 3113 'pull_request_id': pull_request.pull_request_id,
3100 3114 'url': url('pullrequest_show',
3101 3115 repo_name=pull_request.target_repo.repo_name,
3102 3116 pull_request_id=pull_request.pull_request_id,
3103 3117 qualified=True),
3104 3118 'title': pull_request.title,
3105 3119 'description': pull_request.description,
3106 3120 'status': pull_request.status,
3107 3121 'created_on': pull_request.created_on,
3108 3122 'updated_on': pull_request.updated_on,
3109 3123 'commit_ids': pull_request.revisions,
3110 3124 'review_status': pull_request.calculated_review_status(),
3111 3125 'mergeable': {
3112 3126 'status': merge_status[0],
3113 3127 'message': unicode(merge_status[1]),
3114 3128 },
3115 3129 'source': {
3116 3130 'clone_url': pull_request.source_repo.clone_url(),
3117 3131 'repository': pull_request.source_repo.repo_name,
3118 3132 'reference': {
3119 3133 'name': pull_request.source_ref_parts.name,
3120 3134 'type': pull_request.source_ref_parts.type,
3121 3135 'commit_id': pull_request.source_ref_parts.commit_id,
3122 3136 },
3123 3137 },
3124 3138 'target': {
3125 3139 'clone_url': pull_request.target_repo.clone_url(),
3126 3140 'repository': pull_request.target_repo.repo_name,
3127 3141 'reference': {
3128 3142 'name': pull_request.target_ref_parts.name,
3129 3143 'type': pull_request.target_ref_parts.type,
3130 3144 'commit_id': pull_request.target_ref_parts.commit_id,
3131 3145 },
3132 3146 },
3133 3147 'author': pull_request.author.get_api_data(include_secrets=False,
3134 3148 details='basic'),
3135 3149 'reviewers': [
3136 3150 {
3137 3151 'user': reviewer.get_api_data(include_secrets=False,
3138 3152 details='basic'),
3139 3153 'review_status': st[0][1].status if st else 'not_reviewed',
3140 3154 }
3141 3155 for reviewer, st in pull_request.reviewers_statuses()
3142 3156 ]
3143 3157 }
3144 3158
3145 3159 return data
3146 3160
3147 3161 def __json__(self):
3148 3162 return {
3149 3163 'revisions': self.revisions,
3150 3164 }
3151 3165
3152 3166 def calculated_review_status(self):
3153 3167 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3154 3168 # because it's tricky on how to use ChangesetStatusModel from there
3155 3169 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3156 3170 from rhodecode.model.changeset_status import ChangesetStatusModel
3157 3171 return ChangesetStatusModel().calculated_review_status(self)
3158 3172
3159 3173 def reviewers_statuses(self):
3160 3174 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3161 3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3162 3176 return ChangesetStatusModel().reviewers_statuses(self)
3163 3177
3164 3178
3165 3179 class PullRequestVersion(Base, _PullRequestBase):
3166 3180 __tablename__ = 'pull_request_versions'
3167 3181 __table_args__ = (
3168 3182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3169 3183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3170 3184 )
3171 3185
3172 3186 pull_request_version_id = Column(
3173 3187 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3174 3188 pull_request_id = Column(
3175 3189 'pull_request_id', Integer(),
3176 3190 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3177 3191 pull_request = relationship('PullRequest')
3178 3192
3179 3193 def __repr__(self):
3180 3194 if self.pull_request_version_id:
3181 3195 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3182 3196 else:
3183 3197 return '<DB:PullRequestVersion at %#x>' % id(self)
3184 3198
3185 3199
3186 3200 class PullRequestReviewers(Base, BaseModel):
3187 3201 __tablename__ = 'pull_request_reviewers'
3188 3202 __table_args__ = (
3189 3203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3190 3204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3191 3205 )
3192 3206
3193 3207 def __init__(self, user=None, pull_request=None):
3194 3208 self.user = user
3195 3209 self.pull_request = pull_request
3196 3210
3197 3211 pull_requests_reviewers_id = Column(
3198 3212 'pull_requests_reviewers_id', Integer(), nullable=False,
3199 3213 primary_key=True)
3200 3214 pull_request_id = Column(
3201 3215 "pull_request_id", Integer(),
3202 3216 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3203 3217 user_id = Column(
3204 3218 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3205 3219
3206 3220 user = relationship('User')
3207 3221 pull_request = relationship('PullRequest')
3208 3222
3209 3223
3210 3224 class Notification(Base, BaseModel):
3211 3225 __tablename__ = 'notifications'
3212 3226 __table_args__ = (
3213 3227 Index('notification_type_idx', 'type'),
3214 3228 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3215 3229 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3216 3230 )
3217 3231
3218 3232 TYPE_CHANGESET_COMMENT = u'cs_comment'
3219 3233 TYPE_MESSAGE = u'message'
3220 3234 TYPE_MENTION = u'mention'
3221 3235 TYPE_REGISTRATION = u'registration'
3222 3236 TYPE_PULL_REQUEST = u'pull_request'
3223 3237 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3224 3238
3225 3239 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3226 3240 subject = Column('subject', Unicode(512), nullable=True)
3227 3241 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3228 3242 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3229 3243 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3230 3244 type_ = Column('type', Unicode(255))
3231 3245
3232 3246 created_by_user = relationship('User')
3233 3247 notifications_to_users = relationship('UserNotification', lazy='joined',
3234 3248 cascade="all, delete, delete-orphan")
3235 3249
3236 3250 @property
3237 3251 def recipients(self):
3238 3252 return [x.user for x in UserNotification.query()\
3239 3253 .filter(UserNotification.notification == self)\
3240 3254 .order_by(UserNotification.user_id.asc()).all()]
3241 3255
3242 3256 @classmethod
3243 3257 def create(cls, created_by, subject, body, recipients, type_=None):
3244 3258 if type_ is None:
3245 3259 type_ = Notification.TYPE_MESSAGE
3246 3260
3247 3261 notification = cls()
3248 3262 notification.created_by_user = created_by
3249 3263 notification.subject = subject
3250 3264 notification.body = body
3251 3265 notification.type_ = type_
3252 3266 notification.created_on = datetime.datetime.now()
3253 3267
3254 3268 for u in recipients:
3255 3269 assoc = UserNotification()
3256 3270 assoc.notification = notification
3257 3271
3258 3272 # if created_by is inside recipients mark his notification
3259 3273 # as read
3260 3274 if u.user_id == created_by.user_id:
3261 3275 assoc.read = True
3262 3276
3263 3277 u.notifications.append(assoc)
3264 3278 Session().add(notification)
3265 3279
3266 3280 return notification
3267 3281
3268 3282 @property
3269 3283 def description(self):
3270 3284 from rhodecode.model.notification import NotificationModel
3271 3285 return NotificationModel().make_description(self)
3272 3286
3273 3287
3274 3288 class UserNotification(Base, BaseModel):
3275 3289 __tablename__ = 'user_to_notification'
3276 3290 __table_args__ = (
3277 3291 UniqueConstraint('user_id', 'notification_id'),
3278 3292 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3279 3293 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3280 3294 )
3281 3295 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3282 3296 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3283 3297 read = Column('read', Boolean, default=False)
3284 3298 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3285 3299
3286 3300 user = relationship('User', lazy="joined")
3287 3301 notification = relationship('Notification', lazy="joined",
3288 3302 order_by=lambda: Notification.created_on.desc(),)
3289 3303
3290 3304 def mark_as_read(self):
3291 3305 self.read = True
3292 3306 Session().add(self)
3293 3307
3294 3308
3295 3309 class Gist(Base, BaseModel):
3296 3310 __tablename__ = 'gists'
3297 3311 __table_args__ = (
3298 3312 Index('g_gist_access_id_idx', 'gist_access_id'),
3299 3313 Index('g_created_on_idx', 'created_on'),
3300 3314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3301 3315 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3302 3316 )
3303 3317 GIST_PUBLIC = u'public'
3304 3318 GIST_PRIVATE = u'private'
3305 3319 DEFAULT_FILENAME = u'gistfile1.txt'
3306 3320
3307 3321 ACL_LEVEL_PUBLIC = u'acl_public'
3308 3322 ACL_LEVEL_PRIVATE = u'acl_private'
3309 3323
3310 3324 gist_id = Column('gist_id', Integer(), primary_key=True)
3311 3325 gist_access_id = Column('gist_access_id', Unicode(250))
3312 3326 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3313 3327 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3314 3328 gist_expires = Column('gist_expires', Float(53), nullable=False)
3315 3329 gist_type = Column('gist_type', Unicode(128), nullable=False)
3316 3330 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3317 3331 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3318 3332 acl_level = Column('acl_level', Unicode(128), nullable=True)
3319 3333
3320 3334 owner = relationship('User')
3321 3335
3322 3336 def __repr__(self):
3323 3337 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3324 3338
3325 3339 @classmethod
3326 3340 def get_or_404(cls, id_):
3327 3341 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3328 3342 if not res:
3329 3343 raise HTTPNotFound
3330 3344 return res
3331 3345
3332 3346 @classmethod
3333 3347 def get_by_access_id(cls, gist_access_id):
3334 3348 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3335 3349
3336 3350 def gist_url(self):
3337 3351 import rhodecode
3338 3352 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3339 3353 if alias_url:
3340 3354 return alias_url.replace('{gistid}', self.gist_access_id)
3341 3355
3342 3356 return url('gist', gist_id=self.gist_access_id, qualified=True)
3343 3357
3344 3358 @classmethod
3345 3359 def base_path(cls):
3346 3360 """
3347 3361 Returns base path when all gists are stored
3348 3362
3349 3363 :param cls:
3350 3364 """
3351 3365 from rhodecode.model.gist import GIST_STORE_LOC
3352 3366 q = Session().query(RhodeCodeUi)\
3353 3367 .filter(RhodeCodeUi.ui_key == URL_SEP)
3354 3368 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3355 3369 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3356 3370
3357 3371 def get_api_data(self):
3358 3372 """
3359 3373 Common function for generating gist related data for API
3360 3374 """
3361 3375 gist = self
3362 3376 data = {
3363 3377 'gist_id': gist.gist_id,
3364 3378 'type': gist.gist_type,
3365 3379 'access_id': gist.gist_access_id,
3366 3380 'description': gist.gist_description,
3367 3381 'url': gist.gist_url(),
3368 3382 'expires': gist.gist_expires,
3369 3383 'created_on': gist.created_on,
3370 3384 'modified_at': gist.modified_at,
3371 3385 'content': None,
3372 3386 'acl_level': gist.acl_level,
3373 3387 }
3374 3388 return data
3375 3389
3376 3390 def __json__(self):
3377 3391 data = dict(
3378 3392 )
3379 3393 data.update(self.get_api_data())
3380 3394 return data
3381 3395 # SCM functions
3382 3396
3383 3397 def scm_instance(self, **kwargs):
3384 3398 from rhodecode.lib.vcs import get_repo
3385 3399 base_path = self.base_path()
3386 3400 return get_repo(os.path.join(*map(safe_str,
3387 3401 [base_path, self.gist_access_id])))
3388 3402
3389 3403
3390 3404 class DbMigrateVersion(Base, BaseModel):
3391 3405 __tablename__ = 'db_migrate_version'
3392 3406 __table_args__ = (
3393 3407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3394 3408 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3395 3409 )
3396 3410 repository_id = Column('repository_id', String(250), primary_key=True)
3397 3411 repository_path = Column('repository_path', Text)
3398 3412 version = Column('version', Integer)
3399 3413
3400 3414
3401 3415 class ExternalIdentity(Base, BaseModel):
3402 3416 __tablename__ = 'external_identities'
3403 3417 __table_args__ = (
3404 3418 Index('local_user_id_idx', 'local_user_id'),
3405 3419 Index('external_id_idx', 'external_id'),
3406 3420 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3407 3421 'mysql_charset': 'utf8'})
3408 3422
3409 3423 external_id = Column('external_id', Unicode(255), default=u'',
3410 3424 primary_key=True)
3411 3425 external_username = Column('external_username', Unicode(1024), default=u'')
3412 3426 local_user_id = Column('local_user_id', Integer(),
3413 3427 ForeignKey('users.user_id'), primary_key=True)
3414 3428 provider_name = Column('provider_name', Unicode(255), default=u'',
3415 3429 primary_key=True)
3416 3430 access_token = Column('access_token', String(1024), default=u'')
3417 3431 alt_token = Column('alt_token', String(1024), default=u'')
3418 3432 token_secret = Column('token_secret', String(1024), default=u'')
3419 3433
3420 3434 @classmethod
3421 3435 def by_external_id_and_provider(cls, external_id, provider_name,
3422 3436 local_user_id=None):
3423 3437 """
3424 3438 Returns ExternalIdentity instance based on search params
3425 3439
3426 3440 :param external_id:
3427 3441 :param provider_name:
3428 3442 :return: ExternalIdentity
3429 3443 """
3430 3444 query = cls.query()
3431 3445 query = query.filter(cls.external_id == external_id)
3432 3446 query = query.filter(cls.provider_name == provider_name)
3433 3447 if local_user_id:
3434 3448 query = query.filter(cls.local_user_id == local_user_id)
3435 3449 return query.first()
3436 3450
3437 3451 @classmethod
3438 3452 def user_by_external_id_and_provider(cls, external_id, provider_name):
3439 3453 """
3440 3454 Returns User instance based on search params
3441 3455
3442 3456 :param external_id:
3443 3457 :param provider_name:
3444 3458 :return: User
3445 3459 """
3446 3460 query = User.query()
3447 3461 query = query.filter(cls.external_id == external_id)
3448 3462 query = query.filter(cls.provider_name == provider_name)
3449 3463 query = query.filter(User.user_id == cls.local_user_id)
3450 3464 return query.first()
3451 3465
3452 3466 @classmethod
3453 3467 def by_local_user_id(cls, local_user_id):
3454 3468 """
3455 3469 Returns all tokens for user
3456 3470
3457 3471 :param local_user_id:
3458 3472 :return: ExternalIdentity
3459 3473 """
3460 3474 query = cls.query()
3461 3475 query = query.filter(cls.local_user_id == local_user_id)
3462 3476 return query
@@ -1,655 +1,655 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 ${self.body()}
20 20 </div>
21 21 </div>
22 22 ${self.menu_bar_subnav()}
23 23 <!-- END HEADER -->
24 24
25 25 <!-- CONTENT -->
26 26 <div id="content" class="wrapper">
27 27 ${self.flash_msg()}
28 28 <div class="main">
29 29 ${next.main()}
30 30 </div>
31 31 </div>
32 32 <!-- END CONTENT -->
33 33
34 34 </div>
35 35 <!-- FOOTER -->
36 36 <div id="footer">
37 37 <div id="footer-inner" class="title wrapper">
38 38 <div>
39 39 <p class="footer-link-right">
40 40 % if c.visual.show_version:
41 41 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 42 % endif
43 43 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 44 % if c.visual.rhodecode_support_url:
45 45 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 46 % endif
47 47 </p>
48 48 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 49 <p class="server-instance" style="display:${sid}">
50 50 ## display hidden instance ID if specially defined
51 51 % if c.rhodecode_instanceid:
52 52 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 53 % endif
54 54 </p>
55 55 </div>
56 56 </div>
57 57 </div>
58 58
59 59 <!-- END FOOTER -->
60 60
61 61 ### MAKO DEFS ###
62 62
63 63 <%def name="menu_bar_subnav()">
64 64 </%def>
65 65
66 66 <%def name="flash_msg()">
67 67 <%include file="/base/flash_msg.html"/>
68 68 </%def>
69 69
70 70 <%def name="breadcrumbs(class_='breadcrumbs')">
71 71 <div class="${class_}">
72 72 ${self.breadcrumbs_links()}
73 73 </div>
74 74 </%def>
75 75
76 76 <%def name="admin_menu()">
77 77 <ul class="admin_menu submenu">
78 78 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
79 79 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
80 80 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
81 81 <li><a href="${h.url('users')}">${_('Users')}</a></li>
82 82 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
83 83 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
84 84 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
85 85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
87 87 </ul>
88 88 </%def>
89 89
90 90
91 91 <%def name="dt_info_panel(elements)">
92 92 <dl class="dl-horizontal">
93 93 %for dt, dd, title, show_items in elements:
94 94 <dt>${dt}:</dt>
95 95 <dd title="${title}">
96 96 %if callable(dd):
97 97 ## allow lazy evaluation of elements
98 98 ${dd()}
99 99 %else:
100 100 ${dd}
101 101 %endif
102 102 %if show_items:
103 103 <span class="btn-collapse" data-toggle="item-${h.md5(dt)[:6]}-details">${_('Show More')} </span>
104 104 %endif
105 105 </dd>
106 106
107 107 %if show_items:
108 108 <div class="collapsable-content" data-toggle="item-${h.md5(dt)[:6]}-details" style="display: none">
109 109 %for item in show_items:
110 110 <dt></dt>
111 111 <dd>${item}</dd>
112 112 %endfor
113 113 </div>
114 114 %endif
115 115
116 116 %endfor
117 117 </dl>
118 118 </%def>
119 119
120 120
121 121 <%def name="gravatar(email, size=16)">
122 122 <%
123 123 if (size > 16):
124 124 gravatar_class = 'gravatar gravatar-large'
125 125 else:
126 126 gravatar_class = 'gravatar'
127 127 %>
128 128 <%doc>
129 129 TODO: johbo: For now we serve double size images to make it smooth
130 130 for retina. This is how it worked until now. Should be replaced
131 131 with a better solution at some point.
132 132 </%doc>
133 133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 134 </%def>
135 135
136 136
137 137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 138 <div class="rc-user tooltip" title="${contact}">
139 139 ${self.gravatar(h.email_or_none(contact), size)}
140 140 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 141 </div>
142 142 </%def>
143 143
144 144
145 145 ## admin menu used for people that have some admin resources
146 146 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 147 <ul class="submenu">
148 148 %if repositories:
149 149 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
150 150 %endif
151 151 %if repository_groups:
152 152 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
153 153 %endif
154 154 %if user_groups:
155 155 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
156 156 %endif
157 157 </ul>
158 158 </%def>
159 159
160 160 <%def name="repo_page_title(repo_instance)">
161 161 <div class="title-content">
162 162 <div class="title-main">
163 163 ## SVN/HG/GIT icons
164 164 %if h.is_hg(repo_instance):
165 165 <i class="icon-hg"></i>
166 166 %endif
167 167 %if h.is_git(repo_instance):
168 168 <i class="icon-git"></i>
169 169 %endif
170 170 %if h.is_svn(repo_instance):
171 171 <i class="icon-svn"></i>
172 172 %endif
173 173
174 174 ## public/private
175 175 %if repo_instance.private:
176 176 <i class="icon-repo-private"></i>
177 177 %else:
178 178 <i class="icon-repo-public"></i>
179 179 %endif
180 180
181 181 ## repo name with group name
182 182 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183 183
184 184 </div>
185 185
186 186 ## FORKED
187 187 %if repo_instance.fork:
188 188 <p>
189 189 <i class="icon-code-fork"></i> ${_('Fork of')}
190 190 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
191 191 </p>
192 192 %endif
193 193
194 194 ## IMPORTED FROM REMOTE
195 195 %if repo_instance.clone_uri:
196 196 <p>
197 197 <i class="icon-code-fork"></i> ${_('Clone from')}
198 <a href="${h.url(str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 199 </p>
200 200 %endif
201 201
202 202 ## LOCKING STATUS
203 203 %if repo_instance.locked[0]:
204 204 <p class="locking_locked">
205 205 <i class="icon-repo-lock"></i>
206 206 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 207 </p>
208 208 %elif repo_instance.enable_locking:
209 209 <p class="locking_unlocked">
210 210 <i class="icon-repo-unlock"></i>
211 211 ${_('Repository not locked. Pull repository to lock it.')}
212 212 </p>
213 213 %endif
214 214
215 215 </div>
216 216 </%def>
217 217
218 218 <%def name="repo_menu(active=None)">
219 219 <%
220 220 def is_active(selected):
221 221 if selected == active:
222 222 return "active"
223 223 %>
224 224
225 225 <!--- CONTEXT BAR -->
226 226 <div id="context-bar">
227 227 <div class="wrapper">
228 228 <ul id="context-pages" class="horizontal-list navigation">
229 229 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 230 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 231 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
232 232 <li class="${is_active('compare')}">
233 233 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
234 234 </li>
235 235 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
236 236 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
237 237 <li class="${is_active('showpullrequest')}">
238 238 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
239 239 %if c.repository_pull_requests:
240 240 <span class="pr_notifications">${c.repository_pull_requests}</span>
241 241 %endif
242 242 <div class="menulabel">${_('Pull Requests')}</div>
243 243 </a>
244 244 </li>
245 245 %endif
246 246 <li class="${is_active('options')}">
247 247 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
248 248 <ul class="submenu">
249 249 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 250 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 251 %endif
252 252 %if c.rhodecode_db_repo.fork:
253 253 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
254 254 ${_('Compare fork')}</a></li>
255 255 %endif
256 256
257 257 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
258 258
259 259 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 260 %if c.rhodecode_db_repo.locked[0]:
261 261 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
262 262 %else:
263 263 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
264 264 %endif
265 265 %endif
266 266 %if c.rhodecode_user.username != h.DEFAULT_USER:
267 267 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
268 268 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
269 269 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
270 270 %endif
271 271 %endif
272 272 </ul>
273 273 </li>
274 274 </ul>
275 275 </div>
276 276 <div class="clear"></div>
277 277 </div>
278 278 <!--- END CONTEXT BAR -->
279 279
280 280 </%def>
281 281
282 282 <%def name="usermenu()">
283 283 ## USER MENU
284 284 <li id="quick_login_li">
285 285 <a id="quick_login_link" class="menulink childs">
286 286 ${gravatar(c.rhodecode_user.email, 20)}
287 287 <span class="user">
288 288 %if c.rhodecode_user.username != h.DEFAULT_USER:
289 289 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
290 290 %else:
291 291 <span>${_('Sign in')}</span>
292 292 %endif
293 293 </span>
294 294 </a>
295 295
296 296 <div class="user-menu submenu">
297 297 <div id="quick_login">
298 298 %if c.rhodecode_user.username == h.DEFAULT_USER:
299 299 <h4>${_('Sign in to your account')}</h4>
300 300 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
301 301 <div class="form form-vertical">
302 302 <div class="fields">
303 303 <div class="field">
304 304 <div class="label">
305 305 <label for="username">${_('Username')}:</label>
306 306 </div>
307 307 <div class="input">
308 308 ${h.text('username',class_='focus',tabindex=1)}
309 309 </div>
310 310
311 311 </div>
312 312 <div class="field">
313 313 <div class="label">
314 314 <label for="password">${_('Password')}:</label>
315 315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
316 316 </div>
317 317 <div class="input">
318 318 ${h.password('password',class_='focus',tabindex=2)}
319 319 </div>
320 320 </div>
321 321 <div class="buttons">
322 322 <div class="register">
323 323 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
324 324 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
325 325 %endif
326 326 </div>
327 327 <div class="submit">
328 328 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
329 329 </div>
330 330 </div>
331 331 </div>
332 332 </div>
333 333 ${h.end_form()}
334 334 %else:
335 335 <div class="">
336 336 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
337 337 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
338 338 <div class="email">${c.rhodecode_user.email}</div>
339 339 </div>
340 340 <div class="">
341 341 <ol class="links">
342 342 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
343 343 <li class="logout">
344 344 ${h.secure_form(h.route_path('logout'))}
345 345 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
346 346 ${h.end_form()}
347 347 </li>
348 348 </ol>
349 349 </div>
350 350 %endif
351 351 </div>
352 352 </div>
353 353 %if c.rhodecode_user.username != h.DEFAULT_USER:
354 354 <div class="pill_container">
355 355 % if c.unread_notifications == 0:
356 356 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
357 357 % else:
358 358 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
359 359 % endif
360 360 </div>
361 361 % endif
362 362 </li>
363 363 </%def>
364 364
365 365 <%def name="menu_items(active=None)">
366 366 <%
367 367 def is_active(selected):
368 368 if selected == active:
369 369 return "active"
370 370 return ""
371 371 %>
372 372 <ul id="quick" class="main_nav navigation horizontal-list">
373 373 <!-- repo switcher -->
374 374 <li class="${is_active('repositories')} repo_switcher_li has_select2">
375 375 <input id="repo_switcher" name="repo_switcher" type="hidden">
376 376 </li>
377 377
378 378 ## ROOT MENU
379 379 %if c.rhodecode_user.username != h.DEFAULT_USER:
380 380 <li class="${is_active('journal')}">
381 381 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
382 382 <div class="menulabel">${_('Journal')}</div>
383 383 </a>
384 384 </li>
385 385 %else:
386 386 <li class="${is_active('journal')}">
387 387 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
388 388 <div class="menulabel">${_('Public journal')}</div>
389 389 </a>
390 390 </li>
391 391 %endif
392 392 <li class="${is_active('gists')}">
393 393 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
394 394 <div class="menulabel">${_('Gists')}</div>
395 395 </a>
396 396 </li>
397 397 <li class="${is_active('search')}">
398 398 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
399 399 <div class="menulabel">${_('Search')}</div>
400 400 </a>
401 401 </li>
402 402 % if h.HasPermissionAll('hg.admin')('access admin main page'):
403 403 <li class="${is_active('admin')}">
404 404 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
405 405 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
406 406 </a>
407 407 ${admin_menu()}
408 408 </li>
409 409 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
410 410 <li class="${is_active('admin')}">
411 411 <a class="menulink childs" title="${_('Delegated Admin settings')}">
412 412 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
413 413 </a>
414 414 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
415 415 c.rhodecode_user.repository_groups_admin,
416 416 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
417 417 </li>
418 418 % endif
419 419 % if c.debug_style:
420 420 <li class="${is_active('debug_style')}">
421 421 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
422 422 <div class="menulabel">${_('Style')}</div>
423 423 </a>
424 424 </li>
425 425 % endif
426 426 ## render extra user menu
427 427 ${usermenu()}
428 428 </ul>
429 429
430 430 <script type="text/javascript">
431 431 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
432 432
433 433 /*format the look of items in the list*/
434 434 var format = function(state, escapeMarkup){
435 435 if (!state.id){
436 436 return state.text; // optgroup
437 437 }
438 438 var obj_dict = state.obj;
439 439 var tmpl = '';
440 440
441 441 if(obj_dict && state.type == 'repo'){
442 442 if(obj_dict['repo_type'] === 'hg'){
443 443 tmpl += '<i class="icon-hg"></i> ';
444 444 }
445 445 else if(obj_dict['repo_type'] === 'git'){
446 446 tmpl += '<i class="icon-git"></i> ';
447 447 }
448 448 else if(obj_dict['repo_type'] === 'svn'){
449 449 tmpl += '<i class="icon-svn"></i> ';
450 450 }
451 451 if(obj_dict['private']){
452 452 tmpl += '<i class="icon-lock" ></i> ';
453 453 }
454 454 else if(visual_show_public_icon){
455 455 tmpl += '<i class="icon-unlock-alt"></i> ';
456 456 }
457 457 }
458 458 if(obj_dict && state.type == 'commit') {
459 459 tmpl += '<i class="icon-tag"></i>';
460 460 }
461 461 if(obj_dict && state.type == 'group'){
462 462 tmpl += '<i class="icon-folder-close"></i> ';
463 463 }
464 464 tmpl += escapeMarkup(state.text);
465 465 return tmpl;
466 466 };
467 467
468 468 var formatResult = function(result, container, query, escapeMarkup) {
469 469 return format(result, escapeMarkup);
470 470 };
471 471
472 472 var formatSelection = function(data, container, escapeMarkup) {
473 473 return format(data, escapeMarkup);
474 474 };
475 475
476 476 $("#repo_switcher").select2({
477 477 cachedDataSource: {},
478 478 minimumInputLength: 2,
479 479 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
480 480 dropdownAutoWidth: true,
481 481 formatResult: formatResult,
482 482 formatSelection: formatSelection,
483 483 containerCssClass: "repo-switcher",
484 484 dropdownCssClass: "repo-switcher-dropdown",
485 485 escapeMarkup: function(m){
486 486 // don't escape our custom placeholder
487 487 if(m.substr(0,23) == '<div class="menulabel">'){
488 488 return m;
489 489 }
490 490
491 491 return Select2.util.escapeMarkup(m);
492 492 },
493 493 query: $.debounce(250, function(query){
494 494 self = this;
495 495 var cacheKey = query.term;
496 496 var cachedData = self.cachedDataSource[cacheKey];
497 497
498 498 if (cachedData) {
499 499 query.callback({results: cachedData.results});
500 500 } else {
501 501 $.ajax({
502 502 url: "${h.url('goto_switcher_data')}",
503 503 data: {'query': query.term},
504 504 dataType: 'json',
505 505 type: 'GET',
506 506 success: function(data) {
507 507 self.cachedDataSource[cacheKey] = data;
508 508 query.callback({results: data.results});
509 509 },
510 510 error: function(data, textStatus, errorThrown) {
511 511 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
512 512 }
513 513 })
514 514 }
515 515 })
516 516 });
517 517
518 518 $("#repo_switcher").on('select2-selecting', function(e){
519 519 e.preventDefault();
520 520 window.location = e.choice.url;
521 521 });
522 522
523 523 ## Global mouse bindings ##
524 524
525 525 // general help "?"
526 526 Mousetrap.bind(['?'], function(e) {
527 527 $('#help_kb').modal({})
528 528 });
529 529
530 530 // / open the quick filter
531 531 Mousetrap.bind(['/'], function(e) {
532 532 $("#repo_switcher").select2("open");
533 533
534 534 // return false to prevent default browser behavior
535 535 // and stop event from bubbling
536 536 return false;
537 537 });
538 538
539 539 // general nav g + action
540 540 Mousetrap.bind(['g h'], function(e) {
541 541 window.location = pyroutes.url('home');
542 542 });
543 543 Mousetrap.bind(['g g'], function(e) {
544 544 window.location = pyroutes.url('gists', {'private':1});
545 545 });
546 546 Mousetrap.bind(['g G'], function(e) {
547 547 window.location = pyroutes.url('gists', {'public':1});
548 548 });
549 549 Mousetrap.bind(['n g'], function(e) {
550 550 window.location = pyroutes.url('new_gist');
551 551 });
552 552 Mousetrap.bind(['n r'], function(e) {
553 553 window.location = pyroutes.url('new_repo');
554 554 });
555 555
556 556 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
557 557 // nav in repo context
558 558 Mousetrap.bind(['g s'], function(e) {
559 559 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
560 560 });
561 561 Mousetrap.bind(['g c'], function(e) {
562 562 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
563 563 });
564 564 Mousetrap.bind(['g F'], function(e) {
565 565 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
566 566 });
567 567 Mousetrap.bind(['g f'], function(e) {
568 568 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
569 569 });
570 570 Mousetrap.bind(['g p'], function(e) {
571 571 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
572 572 });
573 573 Mousetrap.bind(['g o'], function(e) {
574 574 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
575 575 });
576 576 Mousetrap.bind(['g O'], function(e) {
577 577 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
578 578 });
579 579 % endif
580 580
581 581 </script>
582 582 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
583 583 </%def>
584 584
585 585 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
586 586 <div class="modal-dialog">
587 587 <div class="modal-content">
588 588 <div class="modal-header">
589 589 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
590 590 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
591 591 </div>
592 592 <div class="modal-body">
593 593 <div class="block-left">
594 594 <table class="keyboard-mappings">
595 595 <tbody>
596 596 <tr>
597 597 <th></th>
598 598 <th>${_('Site-wide shortcuts')}</th>
599 599 </tr>
600 600 <%
601 601 elems = [
602 602 ('/', 'Open quick search box'),
603 603 ('g h', 'Goto home page'),
604 604 ('g g', 'Goto my private gists page'),
605 605 ('g G', 'Goto my public gists page'),
606 606 ('n r', 'New repository page'),
607 607 ('n g', 'New gist page'),
608 608 ]
609 609 %>
610 610 %for key, desc in elems:
611 611 <tr>
612 612 <td class="keys">
613 613 <span class="key tag">${key}</span>
614 614 </td>
615 615 <td>${desc}</td>
616 616 </tr>
617 617 %endfor
618 618 </tbody>
619 619 </table>
620 620 </div>
621 621 <div class="block-left">
622 622 <table class="keyboard-mappings">
623 623 <tbody>
624 624 <tr>
625 625 <th></th>
626 626 <th>${_('Repositories')}</th>
627 627 </tr>
628 628 <%
629 629 elems = [
630 630 ('g s', 'Goto summary page'),
631 631 ('g c', 'Goto changelog page'),
632 632 ('g f', 'Goto files page'),
633 633 ('g F', 'Goto files page with file search activated'),
634 634 ('g p', 'Goto pull requests page'),
635 635 ('g o', 'Goto repository settings'),
636 636 ('g O', 'Goto repository permissions settings'),
637 637 ]
638 638 %>
639 639 %for key, desc in elems:
640 640 <tr>
641 641 <td class="keys">
642 642 <span class="key tag">${key}</span>
643 643 </td>
644 644 <td>${desc}</td>
645 645 </tr>
646 646 %endfor
647 647 </tbody>
648 648 </table>
649 649 </div>
650 650 </div>
651 651 <div class="modal-footer">
652 652 </div>
653 653 </div><!-- /.modal-content -->
654 654 </div><!-- /.modal-dialog -->
655 655 </div><!-- /.modal -->
@@ -1,40 +1,76 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 from rhodecode.lib.encrypt import AESCipher
23 from rhodecode.lib.encrypt import (
24 AESCipher, SignatureVerificationError, InvalidDecryptedValue)
24 25
25 26
26 27 class TestEncryptModule(object):
27 28
28 29 @pytest.mark.parametrize(
29 30 "key, text",
30 31 [
31 32 ('a', 'short'),
32 33 ('a'*64, 'too long(trimmed to 32)'),
33 34 ('a'*32, 'just enough'),
34 35 ('ąćęćę', 'non asci'),
35 36 ('$asa$asa', 'special $ used'),
36 37 ]
37 38 )
38 39 def test_encryption(self, key, text):
39 40 enc = AESCipher(key).encrypt(text)
40 41 assert AESCipher(key).decrypt(enc) == text
42
43 def test_encryption_with_hmac(self):
44 key = 'secret'
45 text = 'ihatemysql'
46 enc = AESCipher(key, hmac=True).encrypt(text)
47 assert AESCipher(key, hmac=True).decrypt(enc) == text
48
49 def test_encryption_with_hmac_with_bad_key(self):
50 key = 'secretstring'
51 text = 'ihatemysql'
52 enc = AESCipher(key, hmac=True).encrypt(text)
53
54 with pytest.raises(SignatureVerificationError) as e:
55 assert AESCipher('differentsecret', hmac=True).decrypt(enc) == ''
56
57 assert 'Encryption signature verification failed' in str(e)
58
59 def test_encryption_with_hmac_with_bad_data(self):
60 key = 'secret'
61 text = 'ihatemysql'
62 enc = AESCipher(key, hmac=True).encrypt(text)
63 enc = 'xyz' + enc[3:]
64 with pytest.raises(SignatureVerificationError) as e:
65 assert AESCipher(key, hmac=True).decrypt(enc) == text
66
67 assert 'Encryption signature verification failed' in str(e)
68
69 def test_encryption_with_hmac_with_bad_key_not_strict(self):
70 key = 'secretstring'
71 text = 'ihatemysql'
72 enc = AESCipher(key, hmac=True).encrypt(text)
73
74 assert isinstance(AESCipher(
75 'differentsecret', hmac=True, strict_verification=False
76 ).decrypt(enc), InvalidDecryptedValue)
General Comments 0
You need to be logged in to leave comments. Login now