##// END OF EJS Templates
logging: use logger_generic in all suitable places, and fix get_current_rhodecode_user()...
marcink -
r2108:03c7441c default
parent child Browse files
Show More
@@ -1,1002 +1,1007 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 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 import threading
36 35 import urllib
37 36 import urlobject
38 37 import uuid
39 38
40 39 import pygments.lexers
41 40 import sqlalchemy
42 41 import sqlalchemy.engine.url
43 42 import sqlalchemy.exc
44 43 import sqlalchemy.sql
45 44 import webob
46 45 import routes.util
46 import pyramid.threadlocal
47 47
48 48 import rhodecode
49 49 from rhodecode.translation import _, _pluralize
50 50
51 51
52 52 def md5(s):
53 53 return hashlib.md5(s).hexdigest()
54 54
55 55
56 56 def md5_safe(s):
57 57 return md5(safe_str(s))
58 58
59 59
60 60 def __get_lem(extra_mapping=None):
61 61 """
62 62 Get language extension map based on what's inside pygments lexers
63 63 """
64 64 d = collections.defaultdict(lambda: [])
65 65
66 66 def __clean(s):
67 67 s = s.lstrip('*')
68 68 s = s.lstrip('.')
69 69
70 70 if s.find('[') != -1:
71 71 exts = []
72 72 start, stop = s.find('['), s.find(']')
73 73
74 74 for suffix in s[start + 1:stop]:
75 75 exts.append(s[:s.find('[')] + suffix)
76 76 return [e.lower() for e in exts]
77 77 else:
78 78 return [s.lower()]
79 79
80 80 for lx, t in sorted(pygments.lexers.LEXERS.items()):
81 81 m = map(__clean, t[-2])
82 82 if m:
83 83 m = reduce(lambda x, y: x + y, m)
84 84 for ext in m:
85 85 desc = lx.replace('Lexer', '')
86 86 d[ext].append(desc)
87 87
88 88 data = dict(d)
89 89
90 90 extra_mapping = extra_mapping or {}
91 91 if extra_mapping:
92 92 for k, v in extra_mapping.items():
93 93 if k not in data:
94 94 # register new mapping2lexer
95 95 data[k] = [v]
96 96
97 97 return data
98 98
99 99
100 100 def str2bool(_str):
101 101 """
102 102 returns True/False value from given string, it tries to translate the
103 103 string into boolean
104 104
105 105 :param _str: string value to translate into boolean
106 106 :rtype: boolean
107 107 :returns: boolean from given string
108 108 """
109 109 if _str is None:
110 110 return False
111 111 if _str in (True, False):
112 112 return _str
113 113 _str = str(_str).strip().lower()
114 114 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
115 115
116 116
117 117 def aslist(obj, sep=None, strip=True):
118 118 """
119 119 Returns given string separated by sep as list
120 120
121 121 :param obj:
122 122 :param sep:
123 123 :param strip:
124 124 """
125 125 if isinstance(obj, (basestring,)):
126 126 lst = obj.split(sep)
127 127 if strip:
128 128 lst = [v.strip() for v in lst]
129 129 return lst
130 130 elif isinstance(obj, (list, tuple)):
131 131 return obj
132 132 elif obj is None:
133 133 return []
134 134 else:
135 135 return [obj]
136 136
137 137
138 138 def convert_line_endings(line, mode):
139 139 """
140 140 Converts a given line "line end" accordingly to given mode
141 141
142 142 Available modes are::
143 143 0 - Unix
144 144 1 - Mac
145 145 2 - DOS
146 146
147 147 :param line: given line to convert
148 148 :param mode: mode to convert to
149 149 :rtype: str
150 150 :return: converted line according to mode
151 151 """
152 152 if mode == 0:
153 153 line = line.replace('\r\n', '\n')
154 154 line = line.replace('\r', '\n')
155 155 elif mode == 1:
156 156 line = line.replace('\r\n', '\r')
157 157 line = line.replace('\n', '\r')
158 158 elif mode == 2:
159 159 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
160 160 return line
161 161
162 162
163 163 def detect_mode(line, default):
164 164 """
165 165 Detects line break for given line, if line break couldn't be found
166 166 given default value is returned
167 167
168 168 :param line: str line
169 169 :param default: default
170 170 :rtype: int
171 171 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
172 172 """
173 173 if line.endswith('\r\n'):
174 174 return 2
175 175 elif line.endswith('\n'):
176 176 return 0
177 177 elif line.endswith('\r'):
178 178 return 1
179 179 else:
180 180 return default
181 181
182 182
183 183 def safe_int(val, default=None):
184 184 """
185 185 Returns int() of val if val is not convertable to int use default
186 186 instead
187 187
188 188 :param val:
189 189 :param default:
190 190 """
191 191
192 192 try:
193 193 val = int(val)
194 194 except (ValueError, TypeError):
195 195 val = default
196 196
197 197 return val
198 198
199 199
200 200 def safe_unicode(str_, from_encoding=None):
201 201 """
202 202 safe unicode function. Does few trick to turn str_ into unicode
203 203
204 204 In case of UnicodeDecode error, we try to return it with encoding detected
205 205 by chardet library if it fails fallback to unicode with errors replaced
206 206
207 207 :param str_: string to decode
208 208 :rtype: unicode
209 209 :returns: unicode object
210 210 """
211 211 if isinstance(str_, unicode):
212 212 return str_
213 213
214 214 if not from_encoding:
215 215 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
216 216 'utf8'), sep=',')
217 217 from_encoding = DEFAULT_ENCODINGS
218 218
219 219 if not isinstance(from_encoding, (list, tuple)):
220 220 from_encoding = [from_encoding]
221 221
222 222 try:
223 223 return unicode(str_)
224 224 except UnicodeDecodeError:
225 225 pass
226 226
227 227 for enc in from_encoding:
228 228 try:
229 229 return unicode(str_, enc)
230 230 except UnicodeDecodeError:
231 231 pass
232 232
233 233 try:
234 234 import chardet
235 235 encoding = chardet.detect(str_)['encoding']
236 236 if encoding is None:
237 237 raise Exception()
238 238 return str_.decode(encoding)
239 239 except (ImportError, UnicodeDecodeError, Exception):
240 240 return unicode(str_, from_encoding[0], 'replace')
241 241
242 242
243 243 def safe_str(unicode_, to_encoding=None):
244 244 """
245 245 safe str function. Does few trick to turn unicode_ into string
246 246
247 247 In case of UnicodeEncodeError, we try to return it with encoding detected
248 248 by chardet library if it fails fallback to string with errors replaced
249 249
250 250 :param unicode_: unicode to encode
251 251 :rtype: str
252 252 :returns: str object
253 253 """
254 254
255 255 # if it's not basestr cast to str
256 256 if not isinstance(unicode_, basestring):
257 257 return str(unicode_)
258 258
259 259 if isinstance(unicode_, str):
260 260 return unicode_
261 261
262 262 if not to_encoding:
263 263 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
264 264 'utf8'), sep=',')
265 265 to_encoding = DEFAULT_ENCODINGS
266 266
267 267 if not isinstance(to_encoding, (list, tuple)):
268 268 to_encoding = [to_encoding]
269 269
270 270 for enc in to_encoding:
271 271 try:
272 272 return unicode_.encode(enc)
273 273 except UnicodeEncodeError:
274 274 pass
275 275
276 276 try:
277 277 import chardet
278 278 encoding = chardet.detect(unicode_)['encoding']
279 279 if encoding is None:
280 280 raise UnicodeEncodeError()
281 281
282 282 return unicode_.encode(encoding)
283 283 except (ImportError, UnicodeEncodeError):
284 284 return unicode_.encode(to_encoding[0], 'replace')
285 285
286 286
287 287 def remove_suffix(s, suffix):
288 288 if s.endswith(suffix):
289 289 s = s[:-1 * len(suffix)]
290 290 return s
291 291
292 292
293 293 def remove_prefix(s, prefix):
294 294 if s.startswith(prefix):
295 295 s = s[len(prefix):]
296 296 return s
297 297
298 298
299 299 def find_calling_context(ignore_modules=None):
300 300 """
301 301 Look through the calling stack and return the frame which called
302 302 this function and is part of core module ( ie. rhodecode.* )
303 303
304 304 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
305 305 """
306 306
307 307 ignore_modules = ignore_modules or []
308 308
309 309 f = sys._getframe(2)
310 310 while f.f_back is not None:
311 311 name = f.f_globals.get('__name__')
312 312 if name and name.startswith(__name__.split('.')[0]):
313 313 if name not in ignore_modules:
314 314 return f
315 315 f = f.f_back
316 316 return None
317 317
318 318
319 319 def ping_connection(connection, branch):
320 320 if branch:
321 321 # "branch" refers to a sub-connection of a connection,
322 322 # we don't want to bother pinging on these.
323 323 return
324 324
325 325 # turn off "close with result". This flag is only used with
326 326 # "connectionless" execution, otherwise will be False in any case
327 327 save_should_close_with_result = connection.should_close_with_result
328 328 connection.should_close_with_result = False
329 329
330 330 try:
331 331 # run a SELECT 1. use a core select() so that
332 332 # the SELECT of a scalar value without a table is
333 333 # appropriately formatted for the backend
334 334 connection.scalar(sqlalchemy.sql.select([1]))
335 335 except sqlalchemy.exc.DBAPIError as err:
336 336 # catch SQLAlchemy's DBAPIError, which is a wrapper
337 337 # for the DBAPI's exception. It includes a .connection_invalidated
338 338 # attribute which specifies if this connection is a "disconnect"
339 339 # condition, which is based on inspection of the original exception
340 340 # by the dialect in use.
341 341 if err.connection_invalidated:
342 342 # run the same SELECT again - the connection will re-validate
343 343 # itself and establish a new connection. The disconnect detection
344 344 # here also causes the whole connection pool to be invalidated
345 345 # so that all stale connections are discarded.
346 346 connection.scalar(sqlalchemy.sql.select([1]))
347 347 else:
348 348 raise
349 349 finally:
350 350 # restore "close with result"
351 351 connection.should_close_with_result = save_should_close_with_result
352 352
353 353
354 354 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
355 355 """Custom engine_from_config functions."""
356 356 log = logging.getLogger('sqlalchemy.engine')
357 357 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
358 358
359 359 def color_sql(sql):
360 360 color_seq = '\033[1;33m' # This is yellow: code 33
361 361 normal = '\x1b[0m'
362 362 return ''.join([color_seq, sql, normal])
363 363
364 364 if configuration['debug']:
365 365 # attach events only for debug configuration
366 366
367 367 def before_cursor_execute(conn, cursor, statement,
368 368 parameters, context, executemany):
369 369 setattr(conn, 'query_start_time', time.time())
370 370 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
371 371 calling_context = find_calling_context(ignore_modules=[
372 372 'rhodecode.lib.caching_query',
373 373 'rhodecode.model.settings',
374 374 ])
375 375 if calling_context:
376 376 log.info(color_sql('call context %s:%s' % (
377 377 calling_context.f_code.co_filename,
378 378 calling_context.f_lineno,
379 379 )))
380 380
381 381 def after_cursor_execute(conn, cursor, statement,
382 382 parameters, context, executemany):
383 383 delattr(conn, 'query_start_time')
384 384
385 385 sqlalchemy.event.listen(engine, "engine_connect",
386 386 ping_connection)
387 387 sqlalchemy.event.listen(engine, "before_cursor_execute",
388 388 before_cursor_execute)
389 389 sqlalchemy.event.listen(engine, "after_cursor_execute",
390 390 after_cursor_execute)
391 391
392 392 return engine
393 393
394 394
395 395 def get_encryption_key(config):
396 396 secret = config.get('rhodecode.encrypted_values.secret')
397 397 default = config['beaker.session.secret']
398 398 return secret or default
399 399
400 400
401 401 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
402 402 short_format=False):
403 403 """
404 404 Turns a datetime into an age string.
405 405 If show_short_version is True, this generates a shorter string with
406 406 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
407 407
408 408 * IMPORTANT*
409 409 Code of this function is written in special way so it's easier to
410 410 backport it to javascript. If you mean to update it, please also update
411 411 `jquery.timeago-extension.js` file
412 412
413 413 :param prevdate: datetime object
414 414 :param now: get current time, if not define we use
415 415 `datetime.datetime.now()`
416 416 :param show_short_version: if it should approximate the date and
417 417 return a shorter string
418 418 :param show_suffix:
419 419 :param short_format: show short format, eg 2D instead of 2 days
420 420 :rtype: unicode
421 421 :returns: unicode words describing age
422 422 """
423 423
424 424 def _get_relative_delta(now, prevdate):
425 425 base = dateutil.relativedelta.relativedelta(now, prevdate)
426 426 return {
427 427 'year': base.years,
428 428 'month': base.months,
429 429 'day': base.days,
430 430 'hour': base.hours,
431 431 'minute': base.minutes,
432 432 'second': base.seconds,
433 433 }
434 434
435 435 def _is_leap_year(year):
436 436 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
437 437
438 438 def get_month(prevdate):
439 439 return prevdate.month
440 440
441 441 def get_year(prevdate):
442 442 return prevdate.year
443 443
444 444 now = now or datetime.datetime.now()
445 445 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
446 446 deltas = {}
447 447 future = False
448 448
449 449 if prevdate > now:
450 450 now_old = now
451 451 now = prevdate
452 452 prevdate = now_old
453 453 future = True
454 454 if future:
455 455 prevdate = prevdate.replace(microsecond=0)
456 456 # Get date parts deltas
457 457 for part in order:
458 458 rel_delta = _get_relative_delta(now, prevdate)
459 459 deltas[part] = rel_delta[part]
460 460
461 461 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
462 462 # not 1 hour, -59 minutes and -59 seconds)
463 463 offsets = [[5, 60], [4, 60], [3, 24]]
464 464 for element in offsets: # seconds, minutes, hours
465 465 num = element[0]
466 466 length = element[1]
467 467
468 468 part = order[num]
469 469 carry_part = order[num - 1]
470 470
471 471 if deltas[part] < 0:
472 472 deltas[part] += length
473 473 deltas[carry_part] -= 1
474 474
475 475 # Same thing for days except that the increment depends on the (variable)
476 476 # number of days in the month
477 477 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
478 478 if deltas['day'] < 0:
479 479 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
480 480 deltas['day'] += 29
481 481 else:
482 482 deltas['day'] += month_lengths[get_month(prevdate) - 1]
483 483
484 484 deltas['month'] -= 1
485 485
486 486 if deltas['month'] < 0:
487 487 deltas['month'] += 12
488 488 deltas['year'] -= 1
489 489
490 490 # Format the result
491 491 if short_format:
492 492 fmt_funcs = {
493 493 'year': lambda d: u'%dy' % d,
494 494 'month': lambda d: u'%dm' % d,
495 495 'day': lambda d: u'%dd' % d,
496 496 'hour': lambda d: u'%dh' % d,
497 497 'minute': lambda d: u'%dmin' % d,
498 498 'second': lambda d: u'%dsec' % d,
499 499 }
500 500 else:
501 501 fmt_funcs = {
502 502 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
503 503 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
504 504 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
505 505 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
506 506 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
507 507 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
508 508 }
509 509
510 510 i = 0
511 511 for part in order:
512 512 value = deltas[part]
513 513 if value != 0:
514 514
515 515 if i < 5:
516 516 sub_part = order[i + 1]
517 517 sub_value = deltas[sub_part]
518 518 else:
519 519 sub_value = 0
520 520
521 521 if sub_value == 0 or show_short_version:
522 522 _val = fmt_funcs[part](value)
523 523 if future:
524 524 if show_suffix:
525 525 return _(u'in ${ago}', mapping={'ago': _val})
526 526 else:
527 527 return _(_val)
528 528
529 529 else:
530 530 if show_suffix:
531 531 return _(u'${ago} ago', mapping={'ago': _val})
532 532 else:
533 533 return _(_val)
534 534
535 535 val = fmt_funcs[part](value)
536 536 val_detail = fmt_funcs[sub_part](sub_value)
537 537 mapping = {'val': val, 'detail': val_detail}
538 538
539 539 if short_format:
540 540 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
541 541 if show_suffix:
542 542 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
543 543 if future:
544 544 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
545 545 else:
546 546 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
547 547 if show_suffix:
548 548 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
549 549 if future:
550 550 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
551 551
552 552 return datetime_tmpl
553 553 i += 1
554 554 return _(u'just now')
555 555
556 556
557 557 def cleaned_uri(uri):
558 558 """
559 559 Quotes '[' and ']' from uri if there is only one of them.
560 560 according to RFC3986 we cannot use such chars in uri
561 561 :param uri:
562 562 :return: uri without this chars
563 563 """
564 564 return urllib.quote(uri, safe='@$:/')
565 565
566 566
567 567 def uri_filter(uri):
568 568 """
569 569 Removes user:password from given url string
570 570
571 571 :param uri:
572 572 :rtype: unicode
573 573 :returns: filtered list of strings
574 574 """
575 575 if not uri:
576 576 return ''
577 577
578 578 proto = ''
579 579
580 580 for pat in ('https://', 'http://'):
581 581 if uri.startswith(pat):
582 582 uri = uri[len(pat):]
583 583 proto = pat
584 584 break
585 585
586 586 # remove passwords and username
587 587 uri = uri[uri.find('@') + 1:]
588 588
589 589 # get the port
590 590 cred_pos = uri.find(':')
591 591 if cred_pos == -1:
592 592 host, port = uri, None
593 593 else:
594 594 host, port = uri[:cred_pos], uri[cred_pos + 1:]
595 595
596 596 return filter(None, [proto, host, port])
597 597
598 598
599 599 def credentials_filter(uri):
600 600 """
601 601 Returns a url with removed credentials
602 602
603 603 :param uri:
604 604 """
605 605
606 606 uri = uri_filter(uri)
607 607 # check if we have port
608 608 if len(uri) > 2 and uri[2]:
609 609 uri[2] = ':' + uri[2]
610 610
611 611 return ''.join(uri)
612 612
613 613
614 614 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
615 615 qualifed_home_url = request.route_url('home')
616 616 parsed_url = urlobject.URLObject(qualifed_home_url)
617 617 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
618 618 args = {
619 619 'scheme': parsed_url.scheme,
620 620 'user': '',
621 621 # path if we use proxy-prefix
622 622 'netloc': parsed_url.netloc+decoded_path,
623 623 'prefix': decoded_path,
624 624 'repo': repo_name,
625 625 'repoid': str(repo_id)
626 626 }
627 627 args.update(override)
628 628 args['user'] = urllib.quote(safe_str(args['user']))
629 629
630 630 for k, v in args.items():
631 631 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
632 632
633 633 # remove leading @ sign if it's present. Case of empty user
634 634 url_obj = urlobject.URLObject(uri_tmpl)
635 635 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
636 636
637 637 return safe_unicode(url)
638 638
639 639
640 640 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
641 641 """
642 642 Safe version of get_commit if this commit doesn't exists for a
643 643 repository it returns a Dummy one instead
644 644
645 645 :param repo: repository instance
646 646 :param commit_id: commit id as str
647 647 :param pre_load: optional list of commit attributes to load
648 648 """
649 649 # TODO(skreft): remove these circular imports
650 650 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
651 651 from rhodecode.lib.vcs.exceptions import RepositoryError
652 652 if not isinstance(repo, BaseRepository):
653 653 raise Exception('You must pass an Repository '
654 654 'object as first argument got %s', type(repo))
655 655
656 656 try:
657 657 commit = repo.get_commit(
658 658 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
659 659 except (RepositoryError, LookupError):
660 660 commit = EmptyCommit()
661 661 return commit
662 662
663 663
664 664 def datetime_to_time(dt):
665 665 if dt:
666 666 return time.mktime(dt.timetuple())
667 667
668 668
669 669 def time_to_datetime(tm):
670 670 if tm:
671 671 if isinstance(tm, basestring):
672 672 try:
673 673 tm = float(tm)
674 674 except ValueError:
675 675 return
676 676 return datetime.datetime.fromtimestamp(tm)
677 677
678 678
679 679 def time_to_utcdatetime(tm):
680 680 if tm:
681 681 if isinstance(tm, basestring):
682 682 try:
683 683 tm = float(tm)
684 684 except ValueError:
685 685 return
686 686 return datetime.datetime.utcfromtimestamp(tm)
687 687
688 688
689 689 MENTIONS_REGEX = re.compile(
690 690 # ^@ or @ without any special chars in front
691 691 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
692 692 # main body starts with letter, then can be . - _
693 693 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
694 694 re.VERBOSE | re.MULTILINE)
695 695
696 696
697 697 def extract_mentioned_users(s):
698 698 """
699 699 Returns unique usernames from given string s that have @mention
700 700
701 701 :param s: string to get mentions
702 702 """
703 703 usrs = set()
704 704 for username in MENTIONS_REGEX.findall(s):
705 705 usrs.add(username)
706 706
707 707 return sorted(list(usrs), key=lambda k: k.lower())
708 708
709 709
710 710 class StrictAttributeDict(dict):
711 711 """
712 712 Strict Version of Attribute dict which raises an Attribute error when
713 713 requested attribute is not set
714 714 """
715 715 def __getattr__(self, attr):
716 716 try:
717 717 return self[attr]
718 718 except KeyError:
719 719 raise AttributeError('%s object has no attribute %s' % (
720 720 self.__class__, attr))
721 721 __setattr__ = dict.__setitem__
722 722 __delattr__ = dict.__delitem__
723 723
724 724
725 725 class AttributeDict(dict):
726 726 def __getattr__(self, attr):
727 727 return self.get(attr, None)
728 728 __setattr__ = dict.__setitem__
729 729 __delattr__ = dict.__delitem__
730 730
731 731
732 732 def fix_PATH(os_=None):
733 733 """
734 734 Get current active python path, and append it to PATH variable to fix
735 735 issues of subprocess calls and different python versions
736 736 """
737 737 if os_ is None:
738 738 import os
739 739 else:
740 740 os = os_
741 741
742 742 cur_path = os.path.split(sys.executable)[0]
743 743 if not os.environ['PATH'].startswith(cur_path):
744 744 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
745 745
746 746
747 747 def obfuscate_url_pw(engine):
748 748 _url = engine or ''
749 749 try:
750 750 _url = sqlalchemy.engine.url.make_url(engine)
751 751 if _url.password:
752 752 _url.password = 'XXXXX'
753 753 except Exception:
754 754 pass
755 755 return unicode(_url)
756 756
757 757
758 758 def get_server_url(environ):
759 759 req = webob.Request(environ)
760 760 return req.host_url + req.script_name
761 761
762 762
763 763 def unique_id(hexlen=32):
764 764 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
765 765 return suuid(truncate_to=hexlen, alphabet=alphabet)
766 766
767 767
768 768 def suuid(url=None, truncate_to=22, alphabet=None):
769 769 """
770 770 Generate and return a short URL safe UUID.
771 771
772 772 If the url parameter is provided, set the namespace to the provided
773 773 URL and generate a UUID.
774 774
775 775 :param url to get the uuid for
776 776 :truncate_to: truncate the basic 22 UUID to shorter version
777 777
778 778 The IDs won't be universally unique any longer, but the probability of
779 779 a collision will still be very low.
780 780 """
781 781 # Define our alphabet.
782 782 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
783 783
784 784 # If no URL is given, generate a random UUID.
785 785 if url is None:
786 786 unique_id = uuid.uuid4().int
787 787 else:
788 788 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
789 789
790 790 alphabet_length = len(_ALPHABET)
791 791 output = []
792 792 while unique_id > 0:
793 793 digit = unique_id % alphabet_length
794 794 output.append(_ALPHABET[digit])
795 795 unique_id = int(unique_id / alphabet_length)
796 796 return "".join(output)[:truncate_to]
797 797
798 798
799 799 def get_current_rhodecode_user():
800 800 """
801 Gets rhodecode user from threadlocal tmpl_context variable if it's
802 defined, else returns None.
801 Gets rhodecode user from request
803 802 """
804 from pylons import tmpl_context as c
805 if hasattr(c, 'rhodecode_user'):
806 return c.rhodecode_user
803 pyramid_request = pyramid.threadlocal.get_current_request()
804
805 # web case
806 if pyramid_request and hasattr(pyramid_request, 'user'):
807 return pyramid_request.user
808
809 # api case
810 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
811 return pyramid_request.rpc_user
807 812
808 813 return None
809 814
810 815
811 816 def action_logger_generic(action, namespace=''):
812 817 """
813 818 A generic logger for actions useful to the system overview, tries to find
814 819 an acting user for the context of the call otherwise reports unknown user
815 820
816 821 :param action: logging message eg 'comment 5 deleted'
817 822 :param type: string
818 823
819 824 :param namespace: namespace of the logging message eg. 'repo.comments'
820 825 :param type: string
821 826
822 827 """
823 828
824 829 logger_name = 'rhodecode.actions'
825 830
826 831 if namespace:
827 832 logger_name += '.' + namespace
828 833
829 834 log = logging.getLogger(logger_name)
830 835
831 836 # get a user if we can
832 837 user = get_current_rhodecode_user()
833 838
834 839 logfunc = log.info
835 840
836 841 if not user:
837 842 user = '<unknown user>'
838 843 logfunc = log.warning
839 844
840 845 logfunc('Logging action by {}: {}'.format(user, action))
841 846
842 847
843 848 def escape_split(text, sep=',', maxsplit=-1):
844 849 r"""
845 850 Allows for escaping of the separator: e.g. arg='foo\, bar'
846 851
847 852 It should be noted that the way bash et. al. do command line parsing, those
848 853 single quotes are required.
849 854 """
850 855 escaped_sep = r'\%s' % sep
851 856
852 857 if escaped_sep not in text:
853 858 return text.split(sep, maxsplit)
854 859
855 860 before, _mid, after = text.partition(escaped_sep)
856 861 startlist = before.split(sep, maxsplit) # a regular split is fine here
857 862 unfinished = startlist[-1]
858 863 startlist = startlist[:-1]
859 864
860 865 # recurse because there may be more escaped separators
861 866 endlist = escape_split(after, sep, maxsplit)
862 867
863 868 # finish building the escaped value. we use endlist[0] becaue the first
864 869 # part of the string sent in recursion is the rest of the escaped value.
865 870 unfinished += sep + endlist[0]
866 871
867 872 return startlist + [unfinished] + endlist[1:] # put together all the parts
868 873
869 874
870 875 class OptionalAttr(object):
871 876 """
872 877 Special Optional Option that defines other attribute. Example::
873 878
874 879 def test(apiuser, userid=Optional(OAttr('apiuser')):
875 880 user = Optional.extract(userid)
876 881 # calls
877 882
878 883 """
879 884
880 885 def __init__(self, attr_name):
881 886 self.attr_name = attr_name
882 887
883 888 def __repr__(self):
884 889 return '<OptionalAttr:%s>' % self.attr_name
885 890
886 891 def __call__(self):
887 892 return self
888 893
889 894
890 895 # alias
891 896 OAttr = OptionalAttr
892 897
893 898
894 899 class Optional(object):
895 900 """
896 901 Defines an optional parameter::
897 902
898 903 param = param.getval() if isinstance(param, Optional) else param
899 904 param = param() if isinstance(param, Optional) else param
900 905
901 906 is equivalent of::
902 907
903 908 param = Optional.extract(param)
904 909
905 910 """
906 911
907 912 def __init__(self, type_):
908 913 self.type_ = type_
909 914
910 915 def __repr__(self):
911 916 return '<Optional:%s>' % self.type_.__repr__()
912 917
913 918 def __call__(self):
914 919 return self.getval()
915 920
916 921 def getval(self):
917 922 """
918 923 returns value from this Optional instance
919 924 """
920 925 if isinstance(self.type_, OAttr):
921 926 # use params name
922 927 return self.type_.attr_name
923 928 return self.type_
924 929
925 930 @classmethod
926 931 def extract(cls, val):
927 932 """
928 933 Extracts value from Optional() instance
929 934
930 935 :param val:
931 936 :return: original value if it's not Optional instance else
932 937 value of instance
933 938 """
934 939 if isinstance(val, cls):
935 940 return val.getval()
936 941 return val
937 942
938 943
939 944 def get_routes_generator_for_server_url(server_url):
940 945 parsed_url = urlobject.URLObject(server_url)
941 946 netloc = safe_str(parsed_url.netloc)
942 947 script_name = safe_str(parsed_url.path)
943 948
944 949 if ':' in netloc:
945 950 server_name, server_port = netloc.split(':')
946 951 else:
947 952 server_name = netloc
948 953 server_port = (parsed_url.scheme == 'https' and '443' or '80')
949 954
950 955 environ = {
951 956 'REQUEST_METHOD': 'GET',
952 957 'PATH_INFO': '/',
953 958 'SERVER_NAME': server_name,
954 959 'SERVER_PORT': server_port,
955 960 'SCRIPT_NAME': script_name,
956 961 }
957 962 if parsed_url.scheme == 'https':
958 963 environ['HTTPS'] = 'on'
959 964 environ['wsgi.url_scheme'] = 'https'
960 965
961 966 return routes.util.URLGenerator(rhodecode.CONFIG['routes.map'], environ)
962 967
963 968
964 969 def glob2re(pat):
965 970 """
966 971 Translate a shell PATTERN to a regular expression.
967 972
968 973 There is no way to quote meta-characters.
969 974 """
970 975
971 976 i, n = 0, len(pat)
972 977 res = ''
973 978 while i < n:
974 979 c = pat[i]
975 980 i = i+1
976 981 if c == '*':
977 982 #res = res + '.*'
978 983 res = res + '[^/]*'
979 984 elif c == '?':
980 985 #res = res + '.'
981 986 res = res + '[^/]'
982 987 elif c == '[':
983 988 j = i
984 989 if j < n and pat[j] == '!':
985 990 j = j+1
986 991 if j < n and pat[j] == ']':
987 992 j = j+1
988 993 while j < n and pat[j] != ']':
989 994 j = j+1
990 995 if j >= n:
991 996 res = res + '\\['
992 997 else:
993 998 stuff = pat[i:j].replace('\\','\\\\')
994 999 i = j+1
995 1000 if stuff[0] == '!':
996 1001 stuff = '^' + stuff[1:]
997 1002 elif stuff[0] == '^':
998 1003 stuff = '\\' + stuff
999 1004 res = '%s[%s]' % (res, stuff)
1000 1005 else:
1001 1006 res = res + re.escape(c)
1002 1007 return res + '\Z(?ms)'
@@ -1,637 +1,635 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 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 logging
22 22 import traceback
23 23
24 24 from rhodecode.lib.utils2 import safe_str, safe_unicode
25 25 from rhodecode.lib.exceptions import (
26 26 UserGroupAssignedException, RepoGroupAssignmentError)
27 27 from rhodecode.lib.utils2 import (
28 28 get_current_rhodecode_user, action_logger_generic)
29 29 from rhodecode.model import BaseModel
30 30 from rhodecode.model.scm import UserGroupList
31 31 from rhodecode.model.db import (
32 32 true, func, User, UserGroupMember, UserGroup,
33 33 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
34 34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
35 35
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class UserGroupModel(BaseModel):
41 41
42 42 cls = UserGroup
43 43
44 44 def _get_user_group(self, user_group):
45 45 return self._get_instance(UserGroup, user_group,
46 46 callback=UserGroup.get_by_group_name)
47 47
48 48 def _create_default_perms(self, user_group):
49 49 # create default permission
50 50 default_perm = 'usergroup.read'
51 51 def_user = User.get_default_user()
52 52 for p in def_user.user_perms:
53 53 if p.permission.permission_name.startswith('usergroup.'):
54 54 default_perm = p.permission.permission_name
55 55 break
56 56
57 57 user_group_to_perm = UserUserGroupToPerm()
58 58 user_group_to_perm.permission = Permission.get_by_key(default_perm)
59 59
60 60 user_group_to_perm.user_group = user_group
61 61 user_group_to_perm.user_id = def_user.user_id
62 62 return user_group_to_perm
63 63
64 64 def update_permissions(
65 65 self, user_group, perm_additions=None, perm_updates=None,
66 66 perm_deletions=None, check_perms=True, cur_user=None):
67 67
68 68 from rhodecode.lib.auth import HasUserGroupPermissionAny
69 69 if not perm_additions:
70 70 perm_additions = []
71 71 if not perm_updates:
72 72 perm_updates = []
73 73 if not perm_deletions:
74 74 perm_deletions = []
75 75
76 76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
77 77
78 78 changes = {
79 79 'added': [],
80 80 'updated': [],
81 81 'deleted': []
82 82 }
83 83 # update permissions
84 84 for member_id, perm, member_type in perm_updates:
85 85 member_id = int(member_id)
86 86 if member_type == 'user':
87 87 member_name = User.get(member_id).username
88 88 # this updates existing one
89 89 self.grant_user_permission(
90 90 user_group=user_group, user=member_id, perm=perm
91 91 )
92 92 else:
93 93 # check if we have permissions to alter this usergroup
94 94 member_name = UserGroup.get(member_id).users_group_name
95 95 if not check_perms or HasUserGroupPermissionAny(
96 96 *req_perms)(member_name, user=cur_user):
97 97 self.grant_user_group_permission(
98 98 target_user_group=user_group, user_group=member_id, perm=perm)
99 99
100 100 changes['updated'].append({'type': member_type, 'id': member_id,
101 101 'name': member_name, 'new_perm': perm})
102 102
103 103 # set new permissions
104 104 for member_id, perm, member_type in perm_additions:
105 105 member_id = int(member_id)
106 106 if member_type == 'user':
107 107 member_name = User.get(member_id).username
108 108 self.grant_user_permission(
109 109 user_group=user_group, user=member_id, perm=perm)
110 110 else:
111 111 # check if we have permissions to alter this usergroup
112 112 member_name = UserGroup.get(member_id).users_group_name
113 113 if not check_perms or HasUserGroupPermissionAny(
114 114 *req_perms)(member_name, user=cur_user):
115 115 self.grant_user_group_permission(
116 116 target_user_group=user_group, user_group=member_id, perm=perm)
117 117
118 118 changes['added'].append({'type': member_type, 'id': member_id,
119 119 'name': member_name, 'new_perm': perm})
120 120
121 121 # delete permissions
122 122 for member_id, perm, member_type in perm_deletions:
123 123 member_id = int(member_id)
124 124 if member_type == 'user':
125 125 member_name = User.get(member_id).username
126 126 self.revoke_user_permission(user_group=user_group, user=member_id)
127 127 else:
128 128 # check if we have permissions to alter this usergroup
129 129 member_name = UserGroup.get(member_id).users_group_name
130 130 if not check_perms or HasUserGroupPermissionAny(
131 131 *req_perms)(member_name, user=cur_user):
132 132 self.revoke_user_group_permission(
133 133 target_user_group=user_group, user_group=member_id)
134 134
135 135 changes['deleted'].append({'type': member_type, 'id': member_id,
136 136 'name': member_name, 'new_perm': perm})
137 137 return changes
138 138
139 139 def get(self, user_group_id, cache=False):
140 140 return UserGroup.get(user_group_id)
141 141
142 142 def get_group(self, user_group):
143 143 return self._get_user_group(user_group)
144 144
145 145 def get_by_name(self, name, cache=False, case_insensitive=False):
146 146 return UserGroup.get_by_group_name(name, cache, case_insensitive)
147 147
148 148 def create(self, name, description, owner, active=True, group_data=None):
149 149 try:
150 150 new_user_group = UserGroup()
151 151 new_user_group.user = self._get_user(owner)
152 152 new_user_group.users_group_name = name
153 153 new_user_group.user_group_description = description
154 154 new_user_group.users_group_active = active
155 155 if group_data:
156 156 new_user_group.group_data = group_data
157 157 self.sa.add(new_user_group)
158 158 perm_obj = self._create_default_perms(new_user_group)
159 159 self.sa.add(perm_obj)
160 160
161 161 self.grant_user_permission(user_group=new_user_group,
162 162 user=owner, perm='usergroup.admin')
163 163
164 164 return new_user_group
165 165 except Exception:
166 166 log.error(traceback.format_exc())
167 167 raise
168 168
169 169 def _get_memberships_for_user_ids(self, user_group, user_id_list):
170 170 members = []
171 171 for user_id in user_id_list:
172 172 member = self._get_membership(user_group.users_group_id, user_id)
173 173 members.append(member)
174 174 return members
175 175
176 176 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
177 177 current_members = user_group.members or []
178 178 current_members_ids = [m.user.user_id for m in current_members]
179 179
180 180 added_members = [
181 181 user_id for user_id in user_id_list
182 182 if user_id not in current_members_ids]
183 183 if user_id_list == []:
184 184 # all members were deleted
185 185 deleted_members = current_members_ids
186 186 else:
187 187 deleted_members = [
188 188 user_id for user_id in current_members_ids
189 189 if user_id not in user_id_list]
190 190
191 191 return added_members, deleted_members
192 192
193 193 def _set_users_as_members(self, user_group, user_ids):
194 194 user_group.members = []
195 195 self.sa.flush()
196 196 members = self._get_memberships_for_user_ids(
197 197 user_group, user_ids)
198 198 user_group.members = members
199 199 self.sa.add(user_group)
200 200
201 201 def _update_members_from_user_ids(self, user_group, user_ids):
202 202 added, removed = self._get_added_and_removed_user_ids(
203 203 user_group, user_ids)
204 204 self._set_users_as_members(user_group, user_ids)
205 205 self._log_user_changes('added to', user_group, added)
206 206 self._log_user_changes('removed from', user_group, removed)
207 207 return added, removed
208 208
209 209 def _clean_members_data(self, members_data):
210 210 if not members_data:
211 211 members_data = []
212 212
213 213 members = []
214 214 for user in members_data:
215 215 uid = int(user['member_user_id'])
216 216 if uid not in members and user['type'] in ['new', 'existing']:
217 217 members.append(uid)
218 218 return members
219 219
220 220 def update(self, user_group, form_data):
221 221 user_group = self._get_user_group(user_group)
222 222 if 'users_group_name' in form_data:
223 223 user_group.users_group_name = form_data['users_group_name']
224 224 if 'users_group_active' in form_data:
225 225 user_group.users_group_active = form_data['users_group_active']
226 226 if 'user_group_description' in form_data:
227 227 user_group.user_group_description = form_data[
228 228 'user_group_description']
229 229
230 230 # handle owner change
231 231 if 'user' in form_data:
232 232 owner = form_data['user']
233 233 if isinstance(owner, basestring):
234 234 owner = User.get_by_username(form_data['user'])
235 235
236 236 if not isinstance(owner, User):
237 237 raise ValueError(
238 238 'invalid owner for user group: %s' % form_data['user'])
239 239
240 240 user_group.user = owner
241 241
242 242 added_user_ids = []
243 243 removed_user_ids = []
244 244 if 'users_group_members' in form_data:
245 245 members_id_list = self._clean_members_data(
246 246 form_data['users_group_members'])
247 247 added_user_ids, removed_user_ids = \
248 248 self._update_members_from_user_ids(user_group, members_id_list)
249 249
250 250 self.sa.add(user_group)
251 251 return user_group, added_user_ids, removed_user_ids
252 252
253 253 def delete(self, user_group, force=False):
254 254 """
255 255 Deletes repository group, unless force flag is used
256 256 raises exception if there are members in that group, else deletes
257 257 group and users
258 258
259 259 :param user_group:
260 260 :param force:
261 261 """
262 262 user_group = self._get_user_group(user_group)
263 263 if not user_group:
264 264 return
265 265
266 266 try:
267 267 # check if this group is not assigned to repo
268 268 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
269 269 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
270 270 # check if this group is not assigned to repo
271 271 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
272 272 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
273 273
274 274 if (assigned_to_repo or assigned_to_repo_group) and not force:
275 275 assigned = ','.join(map(safe_str,
276 276 assigned_to_repo+assigned_to_repo_group))
277 277
278 278 raise UserGroupAssignedException(
279 279 'UserGroup assigned to %s' % (assigned,))
280 280 self.sa.delete(user_group)
281 281 except Exception:
282 282 log.error(traceback.format_exc())
283 283 raise
284 284
285 285 def _log_user_changes(self, action, user_group, user_or_users):
286 286 users = user_or_users
287 287 if not isinstance(users, (list, tuple)):
288 288 users = [users]
289 rhodecode_user = get_current_rhodecode_user()
290 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
289
291 290 group_name = user_group.users_group_name
292 291
293 292 for user_or_user_id in users:
294 293 user = self._get_user(user_or_user_id)
295 294 log_text = 'User {user} {action} {group}'.format(
296 295 action=action, user=user.username, group=group_name)
297 log.info('Logging action: {0} by {1} ip:{2}'.format(
298 log_text, rhodecode_user, ipaddr))
296 action_logger_generic(log_text)
299 297
300 298 def _find_user_in_group(self, user, user_group):
301 299 user_group_member = None
302 300 for m in user_group.members:
303 301 if m.user_id == user.user_id:
304 302 # Found this user's membership row
305 303 user_group_member = m
306 304 break
307 305
308 306 return user_group_member
309 307
310 308 def _get_membership(self, user_group_id, user_id):
311 309 user_group_member = UserGroupMember(user_group_id, user_id)
312 310 return user_group_member
313 311
314 312 def add_user_to_group(self, user_group, user):
315 313 user_group = self._get_user_group(user_group)
316 314 user = self._get_user(user)
317 315 user_member = self._find_user_in_group(user, user_group)
318 316 if user_member:
319 317 # user already in the group, skip
320 318 return True
321 319
322 320 member = self._get_membership(
323 321 user_group.users_group_id, user.user_id)
324 322 user_group.members.append(member)
325 323
326 324 try:
327 325 self.sa.add(member)
328 326 except Exception:
329 327 # what could go wrong here?
330 328 log.error(traceback.format_exc())
331 329 raise
332 330
333 331 self._log_user_changes('added to', user_group, user)
334 332 return member
335 333
336 334 def remove_user_from_group(self, user_group, user):
337 335 user_group = self._get_user_group(user_group)
338 336 user = self._get_user(user)
339 337 user_group_member = self._find_user_in_group(user, user_group)
340 338
341 339 if not user_group_member:
342 340 # User isn't in that group
343 341 return False
344 342
345 343 try:
346 344 self.sa.delete(user_group_member)
347 345 except Exception:
348 346 log.error(traceback.format_exc())
349 347 raise
350 348
351 349 self._log_user_changes('removed from', user_group, user)
352 350 return True
353 351
354 352 def has_perm(self, user_group, perm):
355 353 user_group = self._get_user_group(user_group)
356 354 perm = self._get_perm(perm)
357 355
358 356 return UserGroupToPerm.query()\
359 357 .filter(UserGroupToPerm.users_group == user_group)\
360 358 .filter(UserGroupToPerm.permission == perm).scalar() is not None
361 359
362 360 def grant_perm(self, user_group, perm):
363 361 user_group = self._get_user_group(user_group)
364 362 perm = self._get_perm(perm)
365 363
366 364 # if this permission is already granted skip it
367 365 _perm = UserGroupToPerm.query()\
368 366 .filter(UserGroupToPerm.users_group == user_group)\
369 367 .filter(UserGroupToPerm.permission == perm)\
370 368 .scalar()
371 369 if _perm:
372 370 return
373 371
374 372 new = UserGroupToPerm()
375 373 new.users_group = user_group
376 374 new.permission = perm
377 375 self.sa.add(new)
378 376 return new
379 377
380 378 def revoke_perm(self, user_group, perm):
381 379 user_group = self._get_user_group(user_group)
382 380 perm = self._get_perm(perm)
383 381
384 382 obj = UserGroupToPerm.query()\
385 383 .filter(UserGroupToPerm.users_group == user_group)\
386 384 .filter(UserGroupToPerm.permission == perm).scalar()
387 385 if obj:
388 386 self.sa.delete(obj)
389 387
390 388 def grant_user_permission(self, user_group, user, perm):
391 389 """
392 390 Grant permission for user on given user group, or update
393 391 existing one if found
394 392
395 393 :param user_group: Instance of UserGroup, users_group_id,
396 394 or users_group_name
397 395 :param user: Instance of User, user_id or username
398 396 :param perm: Instance of Permission, or permission_name
399 397 """
400 398
401 399 user_group = self._get_user_group(user_group)
402 400 user = self._get_user(user)
403 401 permission = self._get_perm(perm)
404 402
405 403 # check if we have that permission already
406 404 obj = self.sa.query(UserUserGroupToPerm)\
407 405 .filter(UserUserGroupToPerm.user == user)\
408 406 .filter(UserUserGroupToPerm.user_group == user_group)\
409 407 .scalar()
410 408 if obj is None:
411 409 # create new !
412 410 obj = UserUserGroupToPerm()
413 411 obj.user_group = user_group
414 412 obj.user = user
415 413 obj.permission = permission
416 414 self.sa.add(obj)
417 415 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
418 416 action_logger_generic(
419 417 'granted permission: {} to user: {} on usergroup: {}'.format(
420 418 perm, user, user_group), namespace='security.usergroup')
421 419
422 420 return obj
423 421
424 422 def revoke_user_permission(self, user_group, user):
425 423 """
426 424 Revoke permission for user on given user group
427 425
428 426 :param user_group: Instance of UserGroup, users_group_id,
429 427 or users_group name
430 428 :param user: Instance of User, user_id or username
431 429 """
432 430
433 431 user_group = self._get_user_group(user_group)
434 432 user = self._get_user(user)
435 433
436 434 obj = self.sa.query(UserUserGroupToPerm)\
437 435 .filter(UserUserGroupToPerm.user == user)\
438 436 .filter(UserUserGroupToPerm.user_group == user_group)\
439 437 .scalar()
440 438 if obj:
441 439 self.sa.delete(obj)
442 440 log.debug('Revoked perm on %s on %s', user_group, user)
443 441 action_logger_generic(
444 442 'revoked permission from user: {} on usergroup: {}'.format(
445 443 user, user_group), namespace='security.usergroup')
446 444
447 445 def grant_user_group_permission(self, target_user_group, user_group, perm):
448 446 """
449 447 Grant user group permission for given target_user_group
450 448
451 449 :param target_user_group:
452 450 :param user_group:
453 451 :param perm:
454 452 """
455 453 target_user_group = self._get_user_group(target_user_group)
456 454 user_group = self._get_user_group(user_group)
457 455 permission = self._get_perm(perm)
458 456 # forbid assigning same user group to itself
459 457 if target_user_group == user_group:
460 458 raise RepoGroupAssignmentError('target repo:%s cannot be '
461 459 'assigned to itself' % target_user_group)
462 460
463 461 # check if we have that permission already
464 462 obj = self.sa.query(UserGroupUserGroupToPerm)\
465 463 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
466 464 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
467 465 .scalar()
468 466 if obj is None:
469 467 # create new !
470 468 obj = UserGroupUserGroupToPerm()
471 469 obj.user_group = user_group
472 470 obj.target_user_group = target_user_group
473 471 obj.permission = permission
474 472 self.sa.add(obj)
475 473 log.debug(
476 474 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
477 475 action_logger_generic(
478 476 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
479 477 perm, user_group, target_user_group),
480 478 namespace='security.usergroup')
481 479
482 480 return obj
483 481
484 482 def revoke_user_group_permission(self, target_user_group, user_group):
485 483 """
486 484 Revoke user group permission for given target_user_group
487 485
488 486 :param target_user_group:
489 487 :param user_group:
490 488 """
491 489 target_user_group = self._get_user_group(target_user_group)
492 490 user_group = self._get_user_group(user_group)
493 491
494 492 obj = self.sa.query(UserGroupUserGroupToPerm)\
495 493 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
496 494 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
497 495 .scalar()
498 496 if obj:
499 497 self.sa.delete(obj)
500 498 log.debug(
501 499 'Revoked perm on %s on %s', target_user_group, user_group)
502 500 action_logger_generic(
503 501 'revoked permission from usergroup: {} on usergroup: {}'.format(
504 502 user_group, target_user_group),
505 503 namespace='security.repogroup')
506 504
507 505 def enforce_groups(self, user, groups, extern_type=None):
508 506 user = self._get_user(user)
509 507 log.debug('Enforcing groups %s on user %s', groups, user)
510 508 current_groups = user.group_member
511 509 # find the external created groups
512 510 externals = [x.users_group for x in current_groups
513 511 if 'extern_type' in x.users_group.group_data]
514 512
515 513 # calculate from what groups user should be removed
516 514 # externals that are not in groups
517 515 for gr in externals:
518 516 if gr.users_group_name not in groups:
519 517 log.debug('Removing user %s from user group %s', user, gr)
520 518 self.remove_user_from_group(gr, user)
521 519
522 520 # now we calculate in which groups user should be == groups params
523 521 owner = User.get_first_super_admin().username
524 522 for gr in set(groups):
525 523 existing_group = UserGroup.get_by_group_name(gr)
526 524 if not existing_group:
527 525 desc = 'Automatically created from plugin:%s' % extern_type
528 526 # we use first admin account to set the owner of the group
529 527 existing_group = UserGroupModel().create(
530 528 gr, desc, owner, group_data={'extern_type': extern_type})
531 529
532 530 # we can only add users to special groups created via plugins
533 531 managed = 'extern_type' in existing_group.group_data
534 532 if managed:
535 533 log.debug('Adding user %s to user group %s', user, gr)
536 534 UserGroupModel().add_user_to_group(existing_group, user)
537 535 else:
538 536 log.debug('Skipping addition to group %s since it is '
539 537 'not set to be automatically synchronized' % gr)
540 538
541 539 def change_groups(self, user, groups):
542 540 """
543 541 This method changes user group assignment
544 542 :param user: User
545 543 :param groups: array of UserGroupModel
546 544 """
547 545 user = self._get_user(user)
548 546 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
549 547 current_groups = user.group_member
550 548 current_groups = [x.users_group for x in current_groups]
551 549
552 550 # calculate from what groups user should be removed/add
553 551 groups = set(groups)
554 552 current_groups = set(current_groups)
555 553
556 554 groups_to_remove = current_groups - groups
557 555 groups_to_add = groups - current_groups
558 556
559 557 for gr in groups_to_remove:
560 558 log.debug('Removing user %s from user group %s',
561 559 user.username, gr.users_group_name)
562 560 self.remove_user_from_group(gr.users_group_name, user.username)
563 561 for gr in groups_to_add:
564 562 log.debug('Adding user %s to user group %s',
565 563 user.username, gr.users_group_name)
566 564 UserGroupModel().add_user_to_group(
567 565 gr.users_group_name, user.username)
568 566
569 567 def _serialize_user_group(self, user_group):
570 568 import rhodecode.lib.helpers as h
571 569 return {
572 570 'id': user_group.users_group_id,
573 571 # TODO: marcink figure out a way to generate the url for the
574 572 # icon
575 573 'icon_link': '',
576 574 'value_display': 'Group: %s (%d members)' % (
577 575 user_group.users_group_name, len(user_group.members),),
578 576 'value': user_group.users_group_name,
579 577 'description': user_group.user_group_description,
580 578 'owner': user_group.user.username,
581 579
582 580 'owner_icon': h.gravatar_url(user_group.user.email, 30),
583 581 'value_display_owner': h.person(user_group.user.email),
584 582
585 583 'value_type': 'user_group',
586 584 'active': user_group.users_group_active,
587 585 }
588 586
589 587 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
590 588 expand_groups=False):
591 589 query = self.sa.query(UserGroup)
592 590 if only_active:
593 591 query = query.filter(UserGroup.users_group_active == true())
594 592
595 593 if name_contains:
596 594 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
597 595 query = query.filter(
598 596 UserGroup.users_group_name.ilike(ilike_expression))\
599 597 .order_by(func.length(UserGroup.users_group_name))\
600 598 .order_by(UserGroup.users_group_name)
601 599
602 600 query = query.limit(limit)
603 601 user_groups = query.all()
604 602 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
605 603 user_groups = UserGroupList(user_groups, perm_set=perm_set)
606 604
607 605 # store same serialize method to extract data from User
608 606 from rhodecode.model.user import UserModel
609 607 serialize_user = UserModel()._serialize_user
610 608
611 609 _groups = []
612 610 for group in user_groups:
613 611 entry = self._serialize_user_group(group)
614 612 if expand_groups:
615 613 expanded_members = []
616 614 for member in group.members:
617 615 expanded_members.append(serialize_user(member.user))
618 616 entry['members'] = expanded_members
619 617 _groups.append(entry)
620 618 return _groups
621 619
622 620 @staticmethod
623 621 def get_user_groups_as_dict(user_group):
624 622 import rhodecode.lib.helpers as h
625 623
626 624 data = {
627 625 'users_group_id': user_group.users_group_id,
628 626 'group_name': user_group.users_group_name,
629 627 'group_description': user_group.user_group_description,
630 628 'active': user_group.users_group_active,
631 629 "owner": user_group.user.username,
632 630 'owner_icon': h.gravatar_url(user_group.user.email, 30),
633 631 "owner_data": {
634 632 'owner': user_group.user.username,
635 633 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
636 634 }
637 635 return data
General Comments 0
You need to be logged in to leave comments. Login now