##// END OF EJS Templates
encryption: use common method to fetch encryption key for encrypted fields.
marcink -
r261:c66c7ec2 default
parent child Browse files
Show More
@@ -1,103 +1,103 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 os
22 22 import shlex
23 23 import Pyro4
24 24 import platform
25 25
26 26 from rhodecode.model import init_model
27 27
28 28
29 29 def configure_pyro4(config):
30 30 """
31 31 Configure Pyro4 based on `config`.
32 32
33 33 This will mainly set the different configuration parameters of the Pyro4
34 34 library based on the settings in our INI files. The Pyro4 documentation
35 35 lists more details about the specific settings and their meaning.
36 36 """
37 37 Pyro4.config.COMMTIMEOUT = float(config['vcs.connection_timeout'])
38 38 Pyro4.config.SERIALIZER = 'pickle'
39 39 Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
40 40
41 41 # Note: We need server configuration in the WSGI processes
42 42 # because we provide a callback server in certain vcs operations.
43 43 Pyro4.config.SERVERTYPE = "multiplex"
44 44 Pyro4.config.POLLTIMEOUT = 0.01
45 45
46 46
47 47 def configure_vcs(config):
48 48 """
49 49 Patch VCS config with some RhodeCode specific stuff
50 50 """
51 51 from rhodecode.lib.vcs import conf
52 52 from rhodecode.lib.utils2 import aslist
53 53 conf.settings.BACKENDS = {
54 54 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
55 55 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
56 56 'svn': 'rhodecode.lib.vcs.backends.svn.SubversionRepository',
57 57 }
58 58
59 59 conf.settings.HG_USE_REBASE_FOR_MERGING = config.get(
60 60 'rhodecode_hg_use_rebase_for_merging', False)
61 61 conf.settings.GIT_REV_FILTER = shlex.split(
62 62 config.get('git_rev_filter', '--all').strip())
63 63 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
64 64 'UTF-8'), sep=',')
65 65 conf.settings.ALIASES[:] = config.get('vcs.backends')
66 66 conf.settings.SVN_COMPATIBLE_VERSION = config.get(
67 67 'vcs.svn.compatible_version')
68 68
69 69
70 70 def initialize_database(config):
71 from rhodecode.lib.utils2 import engine_from_config
71 from rhodecode.lib.utils2 import engine_from_config, get_encryption_key
72 72 engine = engine_from_config(config, 'sqlalchemy.db1.')
73 init_model(engine, encryption_key=config['beaker.session.secret'])
73 init_model(engine, encryption_key=get_encryption_key(config))
74 74
75 75
76 76 def initialize_test_environment(settings, test_env=None):
77 77 if test_env is None:
78 78 test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0))
79 79
80 80 from rhodecode.lib.utils import (
81 81 create_test_directory, create_test_database, create_test_repositories,
82 82 create_test_index)
83 83 from rhodecode.tests import TESTS_TMP_PATH
84 84 # test repos
85 85 if test_env:
86 86 create_test_directory(TESTS_TMP_PATH)
87 87 create_test_database(TESTS_TMP_PATH, settings)
88 88 create_test_repositories(TESTS_TMP_PATH, settings)
89 89 create_test_index(TESTS_TMP_PATH, settings)
90 90
91 91
92 92 def get_vcs_server_protocol(config):
93 93 protocol = config.get('vcs.server.protocol', 'pyro4')
94 94 return protocol
95 95
96 96
97 97 def set_instance_id(config):
98 98 """ Sets a dynamic generated config['instance_id'] if missing or '*' """
99 99
100 100 config['instance_id'] = config.get('instance_id') or ''
101 101 if config['instance_id'] == '*' or not config['instance_id']:
102 102 _platform_id = platform.uname()[1] or 'instance'
103 103 config['instance_id'] = '%s-%s' % (_platform_id, os.getpid())
@@ -1,854 +1,858 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-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 Some simple helper functions
24 24 """
25 25
26 26
27 27 import collections
28 28 import datetime
29 29 import dateutil.relativedelta
30 30 import hashlib
31 31 import logging
32 32 import re
33 33 import sys
34 34 import time
35 35 import threading
36 36 import urllib
37 37 import urlobject
38 38 import uuid
39 39
40 40 import pygments.lexers
41 41 import sqlalchemy
42 42 import sqlalchemy.engine.url
43 43 import webob
44 44
45 45 import rhodecode
46 46
47 47
48 48 def md5(s):
49 49 return hashlib.md5(s).hexdigest()
50 50
51 51
52 52 def md5_safe(s):
53 53 return md5(safe_str(s))
54 54
55 55
56 56 def __get_lem():
57 57 """
58 58 Get language extension map based on what's inside pygments lexers
59 59 """
60 60 d = collections.defaultdict(lambda: [])
61 61
62 62 def __clean(s):
63 63 s = s.lstrip('*')
64 64 s = s.lstrip('.')
65 65
66 66 if s.find('[') != -1:
67 67 exts = []
68 68 start, stop = s.find('['), s.find(']')
69 69
70 70 for suffix in s[start + 1:stop]:
71 71 exts.append(s[:s.find('[')] + suffix)
72 72 return [e.lower() for e in exts]
73 73 else:
74 74 return [s.lower()]
75 75
76 76 for lx, t in sorted(pygments.lexers.LEXERS.items()):
77 77 m = map(__clean, t[-2])
78 78 if m:
79 79 m = reduce(lambda x, y: x + y, m)
80 80 for ext in m:
81 81 desc = lx.replace('Lexer', '')
82 82 d[ext].append(desc)
83 83
84 84 return dict(d)
85 85
86 86
87 87 def str2bool(_str):
88 88 """
89 89 returs True/False value from given string, it tries to translate the
90 90 string into boolean
91 91
92 92 :param _str: string value to translate into boolean
93 93 :rtype: boolean
94 94 :returns: boolean from given string
95 95 """
96 96 if _str is None:
97 97 return False
98 98 if _str in (True, False):
99 99 return _str
100 100 _str = str(_str).strip().lower()
101 101 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
102 102
103 103
104 104 def aslist(obj, sep=None, strip=True):
105 105 """
106 106 Returns given string separated by sep as list
107 107
108 108 :param obj:
109 109 :param sep:
110 110 :param strip:
111 111 """
112 112 if isinstance(obj, (basestring)):
113 113 lst = obj.split(sep)
114 114 if strip:
115 115 lst = [v.strip() for v in lst]
116 116 return lst
117 117 elif isinstance(obj, (list, tuple)):
118 118 return obj
119 119 elif obj is None:
120 120 return []
121 121 else:
122 122 return [obj]
123 123
124 124
125 125 def convert_line_endings(line, mode):
126 126 """
127 127 Converts a given line "line end" accordingly to given mode
128 128
129 129 Available modes are::
130 130 0 - Unix
131 131 1 - Mac
132 132 2 - DOS
133 133
134 134 :param line: given line to convert
135 135 :param mode: mode to convert to
136 136 :rtype: str
137 137 :return: converted line according to mode
138 138 """
139 139 if mode == 0:
140 140 line = line.replace('\r\n', '\n')
141 141 line = line.replace('\r', '\n')
142 142 elif mode == 1:
143 143 line = line.replace('\r\n', '\r')
144 144 line = line.replace('\n', '\r')
145 145 elif mode == 2:
146 146 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
147 147 return line
148 148
149 149
150 150 def detect_mode(line, default):
151 151 """
152 152 Detects line break for given line, if line break couldn't be found
153 153 given default value is returned
154 154
155 155 :param line: str line
156 156 :param default: default
157 157 :rtype: int
158 158 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
159 159 """
160 160 if line.endswith('\r\n'):
161 161 return 2
162 162 elif line.endswith('\n'):
163 163 return 0
164 164 elif line.endswith('\r'):
165 165 return 1
166 166 else:
167 167 return default
168 168
169 169
170 170 def safe_int(val, default=None):
171 171 """
172 172 Returns int() of val if val is not convertable to int use default
173 173 instead
174 174
175 175 :param val:
176 176 :param default:
177 177 """
178 178
179 179 try:
180 180 val = int(val)
181 181 except (ValueError, TypeError):
182 182 val = default
183 183
184 184 return val
185 185
186 186
187 187 def safe_unicode(str_, from_encoding=None):
188 188 """
189 189 safe unicode function. Does few trick to turn str_ into unicode
190 190
191 191 In case of UnicodeDecode error, we try to return it with encoding detected
192 192 by chardet library if it fails fallback to unicode with errors replaced
193 193
194 194 :param str_: string to decode
195 195 :rtype: unicode
196 196 :returns: unicode object
197 197 """
198 198 if isinstance(str_, unicode):
199 199 return str_
200 200
201 201 if not from_encoding:
202 202 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
203 203 'utf8'), sep=',')
204 204 from_encoding = DEFAULT_ENCODINGS
205 205
206 206 if not isinstance(from_encoding, (list, tuple)):
207 207 from_encoding = [from_encoding]
208 208
209 209 try:
210 210 return unicode(str_)
211 211 except UnicodeDecodeError:
212 212 pass
213 213
214 214 for enc in from_encoding:
215 215 try:
216 216 return unicode(str_, enc)
217 217 except UnicodeDecodeError:
218 218 pass
219 219
220 220 try:
221 221 import chardet
222 222 encoding = chardet.detect(str_)['encoding']
223 223 if encoding is None:
224 224 raise Exception()
225 225 return str_.decode(encoding)
226 226 except (ImportError, UnicodeDecodeError, Exception):
227 227 return unicode(str_, from_encoding[0], 'replace')
228 228
229 229
230 230 def safe_str(unicode_, to_encoding=None):
231 231 """
232 232 safe str function. Does few trick to turn unicode_ into string
233 233
234 234 In case of UnicodeEncodeError, we try to return it with encoding detected
235 235 by chardet library if it fails fallback to string with errors replaced
236 236
237 237 :param unicode_: unicode to encode
238 238 :rtype: str
239 239 :returns: str object
240 240 """
241 241
242 242 # if it's not basestr cast to str
243 243 if not isinstance(unicode_, basestring):
244 244 return str(unicode_)
245 245
246 246 if isinstance(unicode_, str):
247 247 return unicode_
248 248
249 249 if not to_encoding:
250 250 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
251 251 'utf8'), sep=',')
252 252 to_encoding = DEFAULT_ENCODINGS
253 253
254 254 if not isinstance(to_encoding, (list, tuple)):
255 255 to_encoding = [to_encoding]
256 256
257 257 for enc in to_encoding:
258 258 try:
259 259 return unicode_.encode(enc)
260 260 except UnicodeEncodeError:
261 261 pass
262 262
263 263 try:
264 264 import chardet
265 265 encoding = chardet.detect(unicode_)['encoding']
266 266 if encoding is None:
267 267 raise UnicodeEncodeError()
268 268
269 269 return unicode_.encode(encoding)
270 270 except (ImportError, UnicodeEncodeError):
271 271 return unicode_.encode(to_encoding[0], 'replace')
272 272
273 273
274 274 def remove_suffix(s, suffix):
275 275 if s.endswith(suffix):
276 276 s = s[:-1 * len(suffix)]
277 277 return s
278 278
279 279
280 280 def remove_prefix(s, prefix):
281 281 if s.startswith(prefix):
282 282 s = s[len(prefix):]
283 283 return s
284 284
285 285
286 286 def find_calling_context(ignore_modules=None):
287 287 """
288 288 Look through the calling stack and return the frame which called
289 289 this function and is part of core module ( ie. rhodecode.* )
290 290
291 291 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
292 292 """
293 293
294 294 ignore_modules = ignore_modules or []
295 295
296 296 f = sys._getframe(2)
297 297 while f.f_back is not None:
298 298 name = f.f_globals.get('__name__')
299 299 if name and name.startswith(__name__.split('.')[0]):
300 300 if name not in ignore_modules:
301 301 return f
302 302 f = f.f_back
303 303 return None
304 304
305 305
306 306 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
307 307 """Custom engine_from_config functions."""
308 308 log = logging.getLogger('sqlalchemy.engine')
309 309 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
310 310
311 311 def color_sql(sql):
312 312 color_seq = '\033[1;33m' # This is yellow: code 33
313 313 normal = '\x1b[0m'
314 314 return ''.join([color_seq, sql, normal])
315 315
316 316 if configuration['debug']:
317 317 # attach events only for debug configuration
318 318
319 319 def before_cursor_execute(conn, cursor, statement,
320 320 parameters, context, executemany):
321 321 setattr(conn, 'query_start_time', time.time())
322 322 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
323 323 calling_context = find_calling_context(ignore_modules=[
324 324 'rhodecode.lib.caching_query',
325 325 'rhodecode.model.settings',
326 326 ])
327 327 if calling_context:
328 328 log.info(color_sql('call context %s:%s' % (
329 329 calling_context.f_code.co_filename,
330 330 calling_context.f_lineno,
331 331 )))
332 332
333 333 def after_cursor_execute(conn, cursor, statement,
334 334 parameters, context, executemany):
335 335 delattr(conn, 'query_start_time')
336 336
337 337 sqlalchemy.event.listen(engine, "before_cursor_execute",
338 338 before_cursor_execute)
339 339 sqlalchemy.event.listen(engine, "after_cursor_execute",
340 340 after_cursor_execute)
341 341
342 342 return engine
343 343
344 344
345 def get_encryption_key(config):
346 return config['beaker.session.secret']
347
348
345 349 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
346 350 short_format=False):
347 351 """
348 352 Turns a datetime into an age string.
349 353 If show_short_version is True, this generates a shorter string with
350 354 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
351 355
352 356 * IMPORTANT*
353 357 Code of this function is written in special way so it's easier to
354 358 backport it to javascript. If you mean to update it, please also update
355 359 `jquery.timeago-extension.js` file
356 360
357 361 :param prevdate: datetime object
358 362 :param now: get current time, if not define we use
359 363 `datetime.datetime.now()`
360 364 :param show_short_version: if it should approximate the date and
361 365 return a shorter string
362 366 :param show_suffix:
363 367 :param short_format: show short format, eg 2D instead of 2 days
364 368 :rtype: unicode
365 369 :returns: unicode words describing age
366 370 """
367 371 from pylons.i18n.translation import _, ungettext
368 372
369 373 def _get_relative_delta(now, prevdate):
370 374 base = dateutil.relativedelta.relativedelta(now, prevdate)
371 375 return {
372 376 'year': base.years,
373 377 'month': base.months,
374 378 'day': base.days,
375 379 'hour': base.hours,
376 380 'minute': base.minutes,
377 381 'second': base.seconds,
378 382 }
379 383
380 384 def _is_leap_year(year):
381 385 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
382 386
383 387 def get_month(prevdate):
384 388 return prevdate.month
385 389
386 390 def get_year(prevdate):
387 391 return prevdate.year
388 392
389 393 now = now or datetime.datetime.now()
390 394 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
391 395 deltas = {}
392 396 future = False
393 397
394 398 if prevdate > now:
395 399 now_old = now
396 400 now = prevdate
397 401 prevdate = now_old
398 402 future = True
399 403 if future:
400 404 prevdate = prevdate.replace(microsecond=0)
401 405 # Get date parts deltas
402 406 for part in order:
403 407 rel_delta = _get_relative_delta(now, prevdate)
404 408 deltas[part] = rel_delta[part]
405 409
406 410 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
407 411 # not 1 hour, -59 minutes and -59 seconds)
408 412 offsets = [[5, 60], [4, 60], [3, 24]]
409 413 for element in offsets: # seconds, minutes, hours
410 414 num = element[0]
411 415 length = element[1]
412 416
413 417 part = order[num]
414 418 carry_part = order[num - 1]
415 419
416 420 if deltas[part] < 0:
417 421 deltas[part] += length
418 422 deltas[carry_part] -= 1
419 423
420 424 # Same thing for days except that the increment depends on the (variable)
421 425 # number of days in the month
422 426 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
423 427 if deltas['day'] < 0:
424 428 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
425 429 deltas['day'] += 29
426 430 else:
427 431 deltas['day'] += month_lengths[get_month(prevdate) - 1]
428 432
429 433 deltas['month'] -= 1
430 434
431 435 if deltas['month'] < 0:
432 436 deltas['month'] += 12
433 437 deltas['year'] -= 1
434 438
435 439 # Format the result
436 440 if short_format:
437 441 fmt_funcs = {
438 442 'year': lambda d: u'%dy' % d,
439 443 'month': lambda d: u'%dm' % d,
440 444 'day': lambda d: u'%dd' % d,
441 445 'hour': lambda d: u'%dh' % d,
442 446 'minute': lambda d: u'%dmin' % d,
443 447 'second': lambda d: u'%dsec' % d,
444 448 }
445 449 else:
446 450 fmt_funcs = {
447 451 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
448 452 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
449 453 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
450 454 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
451 455 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
452 456 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
453 457 }
454 458
455 459 i = 0
456 460 for part in order:
457 461 value = deltas[part]
458 462 if value != 0:
459 463
460 464 if i < 5:
461 465 sub_part = order[i + 1]
462 466 sub_value = deltas[sub_part]
463 467 else:
464 468 sub_value = 0
465 469
466 470 if sub_value == 0 or show_short_version:
467 471 _val = fmt_funcs[part](value)
468 472 if future:
469 473 if show_suffix:
470 474 return _(u'in %s') % _val
471 475 else:
472 476 return _val
473 477
474 478 else:
475 479 if show_suffix:
476 480 return _(u'%s ago') % _val
477 481 else:
478 482 return _val
479 483
480 484 val = fmt_funcs[part](value)
481 485 val_detail = fmt_funcs[sub_part](sub_value)
482 486
483 487 if short_format:
484 488 datetime_tmpl = u'%s, %s'
485 489 if show_suffix:
486 490 datetime_tmpl = _(u'%s, %s ago')
487 491 if future:
488 492 datetime_tmpl = _(u'in %s, %s')
489 493 else:
490 494 datetime_tmpl = _(u'%s and %s')
491 495 if show_suffix:
492 496 datetime_tmpl = _(u'%s and %s ago')
493 497 if future:
494 498 datetime_tmpl = _(u'in %s and %s')
495 499
496 500 return datetime_tmpl % (val, val_detail)
497 501 i += 1
498 502 return _(u'just now')
499 503
500 504
501 505 def uri_filter(uri):
502 506 """
503 507 Removes user:password from given url string
504 508
505 509 :param uri:
506 510 :rtype: unicode
507 511 :returns: filtered list of strings
508 512 """
509 513 if not uri:
510 514 return ''
511 515
512 516 proto = ''
513 517
514 518 for pat in ('https://', 'http://'):
515 519 if uri.startswith(pat):
516 520 uri = uri[len(pat):]
517 521 proto = pat
518 522 break
519 523
520 524 # remove passwords and username
521 525 uri = uri[uri.find('@') + 1:]
522 526
523 527 # get the port
524 528 cred_pos = uri.find(':')
525 529 if cred_pos == -1:
526 530 host, port = uri, None
527 531 else:
528 532 host, port = uri[:cred_pos], uri[cred_pos + 1:]
529 533
530 534 return filter(None, [proto, host, port])
531 535
532 536
533 537 def credentials_filter(uri):
534 538 """
535 539 Returns a url with removed credentials
536 540
537 541 :param uri:
538 542 """
539 543
540 544 uri = uri_filter(uri)
541 545 # check if we have port
542 546 if len(uri) > 2 and uri[2]:
543 547 uri[2] = ':' + uri[2]
544 548
545 549 return ''.join(uri)
546 550
547 551
548 552 def get_clone_url(uri_tmpl, qualifed_home_url, repo_name, repo_id, **override):
549 553 parsed_url = urlobject.URLObject(qualifed_home_url)
550 554 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
551 555 args = {
552 556 'scheme': parsed_url.scheme,
553 557 'user': '',
554 558 # path if we use proxy-prefix
555 559 'netloc': parsed_url.netloc+decoded_path,
556 560 'prefix': decoded_path,
557 561 'repo': repo_name,
558 562 'repoid': str(repo_id)
559 563 }
560 564 args.update(override)
561 565 args['user'] = urllib.quote(safe_str(args['user']))
562 566
563 567 for k, v in args.items():
564 568 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
565 569
566 570 # remove leading @ sign if it's present. Case of empty user
567 571 url_obj = urlobject.URLObject(uri_tmpl)
568 572 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
569 573
570 574 return safe_unicode(url)
571 575
572 576
573 577 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
574 578 """
575 579 Safe version of get_commit if this commit doesn't exists for a
576 580 repository it returns a Dummy one instead
577 581
578 582 :param repo: repository instance
579 583 :param commit_id: commit id as str
580 584 :param pre_load: optional list of commit attributes to load
581 585 """
582 586 # TODO(skreft): remove these circular imports
583 587 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
584 588 from rhodecode.lib.vcs.exceptions import RepositoryError
585 589 if not isinstance(repo, BaseRepository):
586 590 raise Exception('You must pass an Repository '
587 591 'object as first argument got %s', type(repo))
588 592
589 593 try:
590 594 commit = repo.get_commit(
591 595 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
592 596 except (RepositoryError, LookupError):
593 597 commit = EmptyCommit()
594 598 return commit
595 599
596 600
597 601 def datetime_to_time(dt):
598 602 if dt:
599 603 return time.mktime(dt.timetuple())
600 604
601 605
602 606 def time_to_datetime(tm):
603 607 if tm:
604 608 if isinstance(tm, basestring):
605 609 try:
606 610 tm = float(tm)
607 611 except ValueError:
608 612 return
609 613 return datetime.datetime.fromtimestamp(tm)
610 614
611 615
612 616 def time_to_utcdatetime(tm):
613 617 if tm:
614 618 if isinstance(tm, basestring):
615 619 try:
616 620 tm = float(tm)
617 621 except ValueError:
618 622 return
619 623 return datetime.datetime.utcfromtimestamp(tm)
620 624
621 625
622 626 MENTIONS_REGEX = re.compile(
623 627 # ^@ or @ without any special chars in front
624 628 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
625 629 # main body starts with letter, then can be . - _
626 630 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
627 631 re.VERBOSE | re.MULTILINE)
628 632
629 633
630 634 def extract_mentioned_users(s):
631 635 """
632 636 Returns unique usernames from given string s that have @mention
633 637
634 638 :param s: string to get mentions
635 639 """
636 640 usrs = set()
637 641 for username in MENTIONS_REGEX.findall(s):
638 642 usrs.add(username)
639 643
640 644 return sorted(list(usrs), key=lambda k: k.lower())
641 645
642 646
643 647 class AttributeDict(dict):
644 648 def __getattr__(self, attr):
645 649 return self.get(attr, None)
646 650 __setattr__ = dict.__setitem__
647 651 __delattr__ = dict.__delitem__
648 652
649 653
650 654 def fix_PATH(os_=None):
651 655 """
652 656 Get current active python path, and append it to PATH variable to fix
653 657 issues of subprocess calls and different python versions
654 658 """
655 659 if os_ is None:
656 660 import os
657 661 else:
658 662 os = os_
659 663
660 664 cur_path = os.path.split(sys.executable)[0]
661 665 if not os.environ['PATH'].startswith(cur_path):
662 666 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
663 667
664 668
665 669 def obfuscate_url_pw(engine):
666 670 _url = engine or ''
667 671 try:
668 672 _url = sqlalchemy.engine.url.make_url(engine)
669 673 if _url.password:
670 674 _url.password = 'XXXXX'
671 675 except Exception:
672 676 pass
673 677 return unicode(_url)
674 678
675 679
676 680 def get_server_url(environ):
677 681 req = webob.Request(environ)
678 682 return req.host_url + req.script_name
679 683
680 684
681 685 def unique_id(hexlen=32):
682 686 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
683 687 return suuid(truncate_to=hexlen, alphabet=alphabet)
684 688
685 689
686 690 def suuid(url=None, truncate_to=22, alphabet=None):
687 691 """
688 692 Generate and return a short URL safe UUID.
689 693
690 694 If the url parameter is provided, set the namespace to the provided
691 695 URL and generate a UUID.
692 696
693 697 :param url to get the uuid for
694 698 :truncate_to: truncate the basic 22 UUID to shorter version
695 699
696 700 The IDs won't be universally unique any longer, but the probability of
697 701 a collision will still be very low.
698 702 """
699 703 # Define our alphabet.
700 704 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
701 705
702 706 # If no URL is given, generate a random UUID.
703 707 if url is None:
704 708 unique_id = uuid.uuid4().int
705 709 else:
706 710 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
707 711
708 712 alphabet_length = len(_ALPHABET)
709 713 output = []
710 714 while unique_id > 0:
711 715 digit = unique_id % alphabet_length
712 716 output.append(_ALPHABET[digit])
713 717 unique_id = int(unique_id / alphabet_length)
714 718 return "".join(output)[:truncate_to]
715 719
716 720
717 721 def get_current_rhodecode_user():
718 722 """
719 723 Gets rhodecode user from threadlocal tmpl_context variable if it's
720 724 defined, else returns None.
721 725 """
722 726 from pylons import tmpl_context as c
723 727 if hasattr(c, 'rhodecode_user'):
724 728 return c.rhodecode_user
725 729
726 730 return None
727 731
728 732
729 733 def action_logger_generic(action, namespace=''):
730 734 """
731 735 A generic logger for actions useful to the system overview, tries to find
732 736 an acting user for the context of the call otherwise reports unknown user
733 737
734 738 :param action: logging message eg 'comment 5 deleted'
735 739 :param type: string
736 740
737 741 :param namespace: namespace of the logging message eg. 'repo.comments'
738 742 :param type: string
739 743
740 744 """
741 745
742 746 logger_name = 'rhodecode.actions'
743 747
744 748 if namespace:
745 749 logger_name += '.' + namespace
746 750
747 751 log = logging.getLogger(logger_name)
748 752
749 753 # get a user if we can
750 754 user = get_current_rhodecode_user()
751 755
752 756 logfunc = log.info
753 757
754 758 if not user:
755 759 user = '<unknown user>'
756 760 logfunc = log.warning
757 761
758 762 logfunc('Logging action by {}: {}'.format(user, action))
759 763
760 764
761 765 def escape_split(text, sep=',', maxsplit=-1):
762 766 r"""
763 767 Allows for escaping of the separator: e.g. arg='foo\, bar'
764 768
765 769 It should be noted that the way bash et. al. do command line parsing, those
766 770 single quotes are required.
767 771 """
768 772 escaped_sep = r'\%s' % sep
769 773
770 774 if escaped_sep not in text:
771 775 return text.split(sep, maxsplit)
772 776
773 777 before, _mid, after = text.partition(escaped_sep)
774 778 startlist = before.split(sep, maxsplit) # a regular split is fine here
775 779 unfinished = startlist[-1]
776 780 startlist = startlist[:-1]
777 781
778 782 # recurse because there may be more escaped separators
779 783 endlist = escape_split(after, sep, maxsplit)
780 784
781 785 # finish building the escaped value. we use endlist[0] becaue the first
782 786 # part of the string sent in recursion is the rest of the escaped value.
783 787 unfinished += sep + endlist[0]
784 788
785 789 return startlist + [unfinished] + endlist[1:] # put together all the parts
786 790
787 791
788 792 class OptionalAttr(object):
789 793 """
790 794 Special Optional Option that defines other attribute. Example::
791 795
792 796 def test(apiuser, userid=Optional(OAttr('apiuser')):
793 797 user = Optional.extract(userid)
794 798 # calls
795 799
796 800 """
797 801
798 802 def __init__(self, attr_name):
799 803 self.attr_name = attr_name
800 804
801 805 def __repr__(self):
802 806 return '<OptionalAttr:%s>' % self.attr_name
803 807
804 808 def __call__(self):
805 809 return self
806 810
807 811
808 812 # alias
809 813 OAttr = OptionalAttr
810 814
811 815
812 816 class Optional(object):
813 817 """
814 818 Defines an optional parameter::
815 819
816 820 param = param.getval() if isinstance(param, Optional) else param
817 821 param = param() if isinstance(param, Optional) else param
818 822
819 823 is equivalent of::
820 824
821 825 param = Optional.extract(param)
822 826
823 827 """
824 828
825 829 def __init__(self, type_):
826 830 self.type_ = type_
827 831
828 832 def __repr__(self):
829 833 return '<Optional:%s>' % self.type_.__repr__()
830 834
831 835 def __call__(self):
832 836 return self.getval()
833 837
834 838 def getval(self):
835 839 """
836 840 returns value from this Optional instance
837 841 """
838 842 if isinstance(self.type_, OAttr):
839 843 # use params name
840 844 return self.type_.attr_name
841 845 return self.type_
842 846
843 847 @classmethod
844 848 def extract(cls, val):
845 849 """
846 850 Extracts value from Optional() instance
847 851
848 852 :param val:
849 853 :return: original value if it's not Optional instance else
850 854 value of instance
851 855 """
852 856 if isinstance(val, cls):
853 857 return val.getval()
854 858 return val
@@ -1,164 +1,164 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 The application's model objects
23 23
24 24 :example:
25 25
26 26 .. code-block:: python
27 27
28 28 from paste.deploy import appconfig
29 29 from pylons import config
30 30 from sqlalchemy import engine_from_config
31 31 from rhodecode.config.environment import load_environment
32 32
33 33 conf = appconfig('config:development.ini', relative_to = './../../')
34 34 load_environment(conf.global_conf, conf.local_conf)
35 35
36 36 engine = engine_from_config(config, 'sqlalchemy.')
37 37 init_model(engine)
38 38 # RUN YOUR CODE HERE
39 39
40 40 """
41 41
42 42
43 43 import logging
44 44
45 45 from pylons import config
46 46 from pyramid.threadlocal import get_current_registry
47 47
48 48 from rhodecode.model import meta, db
49 from rhodecode.lib.utils2 import obfuscate_url_pw
49 from rhodecode.lib.utils2 import obfuscate_url_pw, get_encryption_key
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 def init_model(engine, encryption_key=None):
55 55 """
56 56 Initializes db session, bind the engine with the metadata,
57 57 Call this before using any of the tables or classes in the model,
58 58 preferably once in application start
59 59
60 60 :param engine: engine to bind to
61 61 """
62 62 engine_str = obfuscate_url_pw(str(engine.url))
63 63 log.info("initializing db for %s", engine_str)
64 64 meta.Base.metadata.bind = engine
65 65 db.ENCRYPTION_KEY = encryption_key
66 66
67 67
68 68 def init_model_encryption(migration_models):
69 migration_models.ENCRYPTION_KEY = config['beaker.session.secret']
70 db.ENCRYPTION_KEY = config['beaker.session.secret']
69 migration_models.ENCRYPTION_KEY = get_encryption_key(config)
70 db.ENCRYPTION_KEY = get_encryption_key(config)
71 71
72 72
73 73 class BaseModel(object):
74 74 """
75 75 Base Model for all RhodeCode models, it adds sql alchemy session
76 76 into instance of model
77 77
78 78 :param sa: If passed it reuses this session instead of creating a new one
79 79 """
80 80
81 81 cls = None # override in child class
82 82
83 83 def __init__(self, sa=None):
84 84 if sa is not None:
85 85 self.sa = sa
86 86 else:
87 87 self.sa = meta.Session()
88 88
89 89 def _get_instance(self, cls, instance, callback=None):
90 90 """
91 91 Gets instance of given cls using some simple lookup mechanism.
92 92
93 93 :param cls: class to fetch
94 94 :param instance: int or Instance
95 95 :param callback: callback to call if all lookups failed
96 96 """
97 97
98 98 if isinstance(instance, cls):
99 99 return instance
100 100 elif isinstance(instance, (int, long)):
101 101 return cls.get(instance)
102 102 else:
103 103 if instance:
104 104 if callback is None:
105 105 raise Exception(
106 106 'given object must be int, long or Instance of %s '
107 107 'got %s, no callback provided' % (cls, type(instance))
108 108 )
109 109 else:
110 110 return callback(instance)
111 111
112 112 def _get_user(self, user):
113 113 """
114 114 Helper method to get user by ID, or username fallback
115 115
116 116 :param user: UserID, username, or User instance
117 117 """
118 118 return self._get_instance(
119 119 db.User, user, callback=db.User.get_by_username)
120 120
121 121 def _get_user_group(self, user_group):
122 122 """
123 123 Helper method to get user by ID, or username fallback
124 124
125 125 :param user_group: UserGroupID, user_group_name, or UserGroup instance
126 126 """
127 127 return self._get_instance(
128 128 db.UserGroup, user_group, callback=db.UserGroup.get_by_group_name)
129 129
130 130 def _get_repo(self, repository):
131 131 """
132 132 Helper method to get repository by ID, or repository name
133 133
134 134 :param repository: RepoID, repository name or Repository Instance
135 135 """
136 136 return self._get_instance(
137 137 db.Repository, repository, callback=db.Repository.get_by_repo_name)
138 138
139 139 def _get_perm(self, permission):
140 140 """
141 141 Helper method to get permission by ID, or permission name
142 142
143 143 :param permission: PermissionID, permission_name or Permission instance
144 144 """
145 145 return self._get_instance(
146 146 db.Permission, permission, callback=db.Permission.get_by_key)
147 147
148 148 def send_event(self, event):
149 149 """
150 150 Helper method to send an event. This wraps the pyramid logic to send an
151 151 event.
152 152 """
153 153 # For the first step we are using pyramids thread locals here. If the
154 154 # event mechanism works out as a good solution we should think about
155 155 # passing the registry into the constructor to get rid of it.
156 156 registry = get_current_registry()
157 157 registry.notify(event)
158 158
159 159 @classmethod
160 160 def get_all(cls):
161 161 """
162 162 Returns all instances of what is defined in `cls` class variable
163 163 """
164 164 return cls.cls.getAll()
General Comments 0
You need to be logged in to leave comments. Login now