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