##// END OF EJS Templates
more detailed logging on auth system...
marcink -
r2125:097327aa beta
parent child Browse files
Show More
@@ -1,25 +1,30 b''
1 .. _debugging:
1 .. _debugging:
2
2
3 ===================
3 ===================
4 DEBUGGING RHODECODE
4 Debugging RhodeCode
5 ===================
5 ===================
6
6
7 If you encountered problems with RhodeCode here are some instructions how to
7 If you encountered problems with RhodeCode here are some instructions how to
8 possibly debug them.
8 possibly debug them.
9
9
10 ** First make sure you're using the latest version available.**
10 ** First make sure you're using the latest version available.**
11
11
12 enable detailed debug
12 enable detailed debug
13 ---------------------
13 ---------------------
14
14
15 RhodeCode uses standard python logging modules to log it's output.
15 RhodeCode uses standard python logging modules to log it's output.
16 By default only loggers with INFO level are displayed. To enable full output
16 By default only loggers with INFO level are displayed. To enable full output
17 change `level = DEBUG` for all logging handlers in currently used .ini file.
17 change `level = DEBUG` for all logging handlers in currently used .ini file.
18 After this you can check much more detailed output of actions happening on
18 This change will allow to see much more detailed output in the logfile or
19 RhodeCode system.
19 console. This generally helps a lot to track issues.
20
20
21
21
22 enable interactive debug mode
22 enable interactive debug mode
23 -----------------------------
23 -----------------------------
24
24
25 To enable interactive debug mode simply
25 To enable interactive debug mode simply comment out `set debug = false` in
26 .ini file, this will trigger and interactive debugger each time there an
27 error in browser, or send a http link if error occured in the backend. This
28 is a great tool for fast debugging as you get a handy python console right
29 in the web view. ** NEVER ENABLE THIS ON PRODUCTION ** the interactive console
30 can be a serious security threat to you system.
@@ -1,814 +1,821 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import random
26 import random
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import hashlib
29 import hashlib
30
30
31 from tempfile import _RandomNameSequence
31 from tempfile import _RandomNameSequence
32 from decorator import decorator
32 from decorator import decorator
33
33
34 from pylons import config, url, request
34 from pylons import config, url, request
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40
40
41 if __platform__ in PLATFORM_WIN:
41 if __platform__ in PLATFORM_WIN:
42 from hashlib import sha256
42 from hashlib import sha256
43 if __platform__ in PLATFORM_OTHERS:
43 if __platform__ in PLATFORM_OTHERS:
44 import bcrypt
44 import bcrypt
45
45
46 from rhodecode.lib.utils2 import str2bool, safe_unicode
46 from rhodecode.lib.utils2 import str2bool, safe_unicode
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
49 from rhodecode.lib.auth_ldap import AuthLdap
49 from rhodecode.lib.auth_ldap import AuthLdap
50
50
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.user import UserModel
52 from rhodecode.model.user import UserModel
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class PasswordGenerator(object):
58 class PasswordGenerator(object):
59 """
59 """
60 This is a simple class for generating password from different sets of
60 This is a simple class for generating password from different sets of
61 characters
61 characters
62 usage::
62 usage::
63
63
64 passwd_gen = PasswordGenerator()
64 passwd_gen = PasswordGenerator()
65 #print 8-letter password containing only big and small letters
65 #print 8-letter password containing only big and small letters
66 of alphabet
66 of alphabet
67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
68 """
68 """
69 ALPHABETS_NUM = r'''1234567890'''
69 ALPHABETS_NUM = r'''1234567890'''
70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
79
79
80 def __init__(self, passwd=''):
80 def __init__(self, passwd=''):
81 self.passwd = passwd
81 self.passwd = passwd
82
82
83 def gen_password(self, length, type_=None):
83 def gen_password(self, length, type_=None):
84 if type_ is None:
84 if type_ is None:
85 type_ = self.ALPHABETS_FULL
85 type_ = self.ALPHABETS_FULL
86 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
86 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
87 return self.passwd
87 return self.passwd
88
88
89
89
90 class RhodeCodeCrypto(object):
90 class RhodeCodeCrypto(object):
91
91
92 @classmethod
92 @classmethod
93 def hash_string(cls, str_):
93 def hash_string(cls, str_):
94 """
94 """
95 Cryptographic function used for password hashing based on pybcrypt
95 Cryptographic function used for password hashing based on pybcrypt
96 or pycrypto in windows
96 or pycrypto in windows
97
97
98 :param password: password to hash
98 :param password: password to hash
99 """
99 """
100 if __platform__ in PLATFORM_WIN:
100 if __platform__ in PLATFORM_WIN:
101 return sha256(str_).hexdigest()
101 return sha256(str_).hexdigest()
102 elif __platform__ in PLATFORM_OTHERS:
102 elif __platform__ in PLATFORM_OTHERS:
103 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
104 else:
104 else:
105 raise Exception('Unknown or unsupported platform %s' \
105 raise Exception('Unknown or unsupported platform %s' \
106 % __platform__)
106 % __platform__)
107
107
108 @classmethod
108 @classmethod
109 def hash_check(cls, password, hashed):
109 def hash_check(cls, password, hashed):
110 """
110 """
111 Checks matching password with it's hashed value, runs different
111 Checks matching password with it's hashed value, runs different
112 implementation based on platform it runs on
112 implementation based on platform it runs on
113
113
114 :param password: password
114 :param password: password
115 :param hashed: password in hashed form
115 :param hashed: password in hashed form
116 """
116 """
117
117
118 if __platform__ in PLATFORM_WIN:
118 if __platform__ in PLATFORM_WIN:
119 return sha256(password).hexdigest() == hashed
119 return sha256(password).hexdigest() == hashed
120 elif __platform__ in PLATFORM_OTHERS:
120 elif __platform__ in PLATFORM_OTHERS:
121 return bcrypt.hashpw(password, hashed) == hashed
121 return bcrypt.hashpw(password, hashed) == hashed
122 else:
122 else:
123 raise Exception('Unknown or unsupported platform %s' \
123 raise Exception('Unknown or unsupported platform %s' \
124 % __platform__)
124 % __platform__)
125
125
126
126
127 def get_crypt_password(password):
127 def get_crypt_password(password):
128 return RhodeCodeCrypto.hash_string(password)
128 return RhodeCodeCrypto.hash_string(password)
129
129
130
130
131 def check_password(password, hashed):
131 def check_password(password, hashed):
132 return RhodeCodeCrypto.hash_check(password, hashed)
132 return RhodeCodeCrypto.hash_check(password, hashed)
133
133
134
134
135 def generate_api_key(str_, salt=None):
135 def generate_api_key(str_, salt=None):
136 """
136 """
137 Generates API KEY from given string
137 Generates API KEY from given string
138
138
139 :param str_:
139 :param str_:
140 :param salt:
140 :param salt:
141 """
141 """
142
142
143 if salt is None:
143 if salt is None:
144 salt = _RandomNameSequence().next()
144 salt = _RandomNameSequence().next()
145
145
146 return hashlib.sha1(str_ + salt).hexdigest()
146 return hashlib.sha1(str_ + salt).hexdigest()
147
147
148
148
149 def authfunc(environ, username, password):
149 def authfunc(environ, username, password):
150 """
150 """
151 Dummy authentication wrapper function used in Mercurial and Git for
151 Dummy authentication wrapper function used in Mercurial and Git for
152 access control.
152 access control.
153
153
154 :param environ: needed only for using in Basic auth
154 :param environ: needed only for using in Basic auth
155 """
155 """
156 return authenticate(username, password)
156 return authenticate(username, password)
157
157
158
158
159 def authenticate(username, password):
159 def authenticate(username, password):
160 """
160 """
161 Authentication function used for access control,
161 Authentication function used for access control,
162 firstly checks for db authentication then if ldap is enabled for ldap
162 firstly checks for db authentication then if ldap is enabled for ldap
163 authentication, also creates ldap user if not in database
163 authentication, also creates ldap user if not in database
164
164
165 :param username: username
165 :param username: username
166 :param password: password
166 :param password: password
167 """
167 """
168
168
169 user_model = UserModel()
169 user_model = UserModel()
170 user = User.get_by_username(username)
170 user = User.get_by_username(username)
171
171
172 log.debug('Authenticating user using RhodeCode account')
172 log.debug('Authenticating user using RhodeCode account')
173 if user is not None and not user.ldap_dn:
173 if user is not None and not user.ldap_dn:
174 if user.active:
174 if user.active:
175 if user.username == 'default' and user.active:
175 if user.username == 'default' and user.active:
176 log.info('user %s authenticated correctly as anonymous user' %
176 log.info('user %s authenticated correctly as anonymous user' %
177 username)
177 username)
178 return True
178 return True
179
179
180 elif user.username == username and check_password(password,
180 elif user.username == username and check_password(password,
181 user.password):
181 user.password):
182 log.info('user %s authenticated correctly' % username)
182 log.info('user %s authenticated correctly' % username)
183 return True
183 return True
184 else:
184 else:
185 log.warning('user %s tried auth but is disabled' % username)
185 log.warning('user %s tried auth but is disabled' % username)
186
186
187 else:
187 else:
188 log.debug('Regular authentication failed')
188 log.debug('Regular authentication failed')
189 user_obj = User.get_by_username(username, case_insensitive=True)
189 user_obj = User.get_by_username(username, case_insensitive=True)
190
190
191 if user_obj is not None and not user_obj.ldap_dn:
191 if user_obj is not None and not user_obj.ldap_dn:
192 log.debug('this user already exists as non ldap')
192 log.debug('this user already exists as non ldap')
193 return False
193 return False
194
194
195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
196 #======================================================================
196 #======================================================================
197 # FALLBACK TO LDAP AUTH IF ENABLE
197 # FALLBACK TO LDAP AUTH IF ENABLE
198 #======================================================================
198 #======================================================================
199 if str2bool(ldap_settings.get('ldap_active')):
199 if str2bool(ldap_settings.get('ldap_active')):
200 log.debug("Authenticating user using ldap")
200 log.debug("Authenticating user using ldap")
201 kwargs = {
201 kwargs = {
202 'server': ldap_settings.get('ldap_host', ''),
202 'server': ldap_settings.get('ldap_host', ''),
203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
204 'port': ldap_settings.get('ldap_port'),
204 'port': ldap_settings.get('ldap_port'),
205 'bind_dn': ldap_settings.get('ldap_dn_user'),
205 'bind_dn': ldap_settings.get('ldap_dn_user'),
206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
209 'ldap_filter': ldap_settings.get('ldap_filter'),
209 'ldap_filter': ldap_settings.get('ldap_filter'),
210 'search_scope': ldap_settings.get('ldap_search_scope'),
210 'search_scope': ldap_settings.get('ldap_search_scope'),
211 'attr_login': ldap_settings.get('ldap_attr_login'),
211 'attr_login': ldap_settings.get('ldap_attr_login'),
212 'ldap_version': 3,
212 'ldap_version': 3,
213 }
213 }
214 log.debug('Checking for ldap authentication')
214 log.debug('Checking for ldap authentication')
215 try:
215 try:
216 aldap = AuthLdap(**kwargs)
216 aldap = AuthLdap(**kwargs)
217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
218 password)
218 password)
219 log.debug('Got ldap DN response %s' % user_dn)
219 log.debug('Got ldap DN response %s' % user_dn)
220
220
221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
222 .get(k), [''])[0]
222 .get(k), [''])[0]
223
223
224 user_attrs = {
224 user_attrs = {
225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
227 'email': get_ldap_attr('ldap_attr_email'),
227 'email': get_ldap_attr('ldap_attr_email'),
228 }
228 }
229
229
230 # don't store LDAP password since we don't need it. Override
230 # don't store LDAP password since we don't need it. Override
231 # with some random generated password
231 # with some random generated password
232 _password = PasswordGenerator().gen_password(length=8)
232 _password = PasswordGenerator().gen_password(length=8)
233 # create this user on the fly if it doesn't exist in rhodecode
233 # create this user on the fly if it doesn't exist in rhodecode
234 # database
234 # database
235 if user_model.create_ldap(username, _password, user_dn,
235 if user_model.create_ldap(username, _password, user_dn,
236 user_attrs):
236 user_attrs):
237 log.info('created new ldap user %s' % username)
237 log.info('created new ldap user %s' % username)
238
238
239 Session.commit()
239 Session.commit()
240 return True
240 return True
241 except (LdapUsernameError, LdapPasswordError,):
241 except (LdapUsernameError, LdapPasswordError,):
242 pass
242 pass
243 except (Exception,):
243 except (Exception,):
244 log.error(traceback.format_exc())
244 log.error(traceback.format_exc())
245 pass
245 pass
246 return False
246 return False
247
247
248
248
249 def login_container_auth(username):
249 def login_container_auth(username):
250 user = User.get_by_username(username)
250 user = User.get_by_username(username)
251 if user is None:
251 if user is None:
252 user_attrs = {
252 user_attrs = {
253 'name': username,
253 'name': username,
254 'lastname': None,
254 'lastname': None,
255 'email': None,
255 'email': None,
256 }
256 }
257 user = UserModel().create_for_container_auth(username, user_attrs)
257 user = UserModel().create_for_container_auth(username, user_attrs)
258 if not user:
258 if not user:
259 return None
259 return None
260 log.info('User %s was created by container authentication' % username)
260 log.info('User %s was created by container authentication' % username)
261
261
262 if not user.active:
262 if not user.active:
263 return None
263 return None
264
264
265 user.update_lastlogin()
265 user.update_lastlogin()
266 Session.commit()
266 Session.commit()
267
267
268 log.debug('User %s is now logged in by container authentication',
268 log.debug('User %s is now logged in by container authentication',
269 user.username)
269 user.username)
270 return user
270 return user
271
271
272
272
273 def get_container_username(environ, config):
273 def get_container_username(environ, config):
274 username = None
274 username = None
275
275
276 if str2bool(config.get('container_auth_enabled', False)):
276 if str2bool(config.get('container_auth_enabled', False)):
277 from paste.httpheaders import REMOTE_USER
277 from paste.httpheaders import REMOTE_USER
278 username = REMOTE_USER(environ)
278 username = REMOTE_USER(environ)
279
279
280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
281 username = environ.get('HTTP_X_FORWARDED_USER')
281 username = environ.get('HTTP_X_FORWARDED_USER')
282
282
283 if username:
283 if username:
284 # Removing realm and domain from username
284 # Removing realm and domain from username
285 username = username.partition('@')[0]
285 username = username.partition('@')[0]
286 username = username.rpartition('\\')[2]
286 username = username.rpartition('\\')[2]
287 log.debug('Received username %s from container' % username)
287 log.debug('Received username %s from container' % username)
288
288
289 return username
289 return username
290
290
291
291
292 class CookieStoreWrapper(object):
292 class CookieStoreWrapper(object):
293
293
294 def __init__(self, cookie_store):
294 def __init__(self, cookie_store):
295 self.cookie_store = cookie_store
295 self.cookie_store = cookie_store
296
296
297 def __repr__(self):
297 def __repr__(self):
298 return 'CookieStore<%s>' % (self.cookie_store)
298 return 'CookieStore<%s>' % (self.cookie_store)
299
299
300 def get(self, key, other=None):
300 def get(self, key, other=None):
301 if isinstance(self.cookie_store, dict):
301 if isinstance(self.cookie_store, dict):
302 return self.cookie_store.get(key, other)
302 return self.cookie_store.get(key, other)
303 elif isinstance(self.cookie_store, AuthUser):
303 elif isinstance(self.cookie_store, AuthUser):
304 return self.cookie_store.__dict__.get(key, other)
304 return self.cookie_store.__dict__.get(key, other)
305
305
306
306
307 class AuthUser(object):
307 class AuthUser(object):
308 """
308 """
309 A simple object that handles all attributes of user in RhodeCode
309 A simple object that handles all attributes of user in RhodeCode
310
310
311 It does lookup based on API key,given user, or user present in session
311 It does lookup based on API key,given user, or user present in session
312 Then it fills all required information for such user. It also checks if
312 Then it fills all required information for such user. It also checks if
313 anonymous access is enabled and if so, it returns default user as logged
313 anonymous access is enabled and if so, it returns default user as logged
314 in
314 in
315 """
315 """
316
316
317 def __init__(self, user_id=None, api_key=None, username=None):
317 def __init__(self, user_id=None, api_key=None, username=None):
318
318
319 self.user_id = user_id
319 self.user_id = user_id
320 self.api_key = None
320 self.api_key = None
321 self.username = username
321 self.username = username
322
322
323 self.name = ''
323 self.name = ''
324 self.lastname = ''
324 self.lastname = ''
325 self.email = ''
325 self.email = ''
326 self.is_authenticated = False
326 self.is_authenticated = False
327 self.admin = False
327 self.admin = False
328 self.permissions = {}
328 self.permissions = {}
329 self._api_key = api_key
329 self._api_key = api_key
330 self.propagate_data()
330 self.propagate_data()
331 self._instance = None
331 self._instance = None
332
332
333 def propagate_data(self):
333 def propagate_data(self):
334 user_model = UserModel()
334 user_model = UserModel()
335 self.anonymous_user = User.get_by_username('default', cache=True)
335 self.anonymous_user = User.get_by_username('default', cache=True)
336 is_user_loaded = False
336 is_user_loaded = False
337
337
338 # try go get user by api key
338 # try go get user by api key
339 if self._api_key and self._api_key != self.anonymous_user.api_key:
339 if self._api_key and self._api_key != self.anonymous_user.api_key:
340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
342 # lookup by userid
342 # lookup by userid
343 elif (self.user_id is not None and
343 elif (self.user_id is not None and
344 self.user_id != self.anonymous_user.user_id):
344 self.user_id != self.anonymous_user.user_id):
345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
347 # lookup by username
347 # lookup by username
348 elif self.username and \
348 elif self.username and \
349 str2bool(config.get('container_auth_enabled', False)):
349 str2bool(config.get('container_auth_enabled', False)):
350
350
351 log.debug('Auth User lookup by USER NAME %s' % self.username)
351 log.debug('Auth User lookup by USER NAME %s' % self.username)
352 dbuser = login_container_auth(self.username)
352 dbuser = login_container_auth(self.username)
353 if dbuser is not None:
353 if dbuser is not None:
354 for k, v in dbuser.get_dict().items():
354 for k, v in dbuser.get_dict().items():
355 setattr(self, k, v)
355 setattr(self, k, v)
356 self.set_authenticated()
356 self.set_authenticated()
357 is_user_loaded = True
357 is_user_loaded = True
358 else:
358 else:
359 log.debug('No data in %s that could been used to log in' % self)
359 log.debug('No data in %s that could been used to log in' % self)
360
360
361 if not is_user_loaded:
361 if not is_user_loaded:
362 # if we cannot authenticate user try anonymous
362 # if we cannot authenticate user try anonymous
363 if self.anonymous_user.active is True:
363 if self.anonymous_user.active is True:
364 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
364 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
365 # then we set this user is logged in
365 # then we set this user is logged in
366 self.is_authenticated = True
366 self.is_authenticated = True
367 else:
367 else:
368 self.user_id = None
368 self.user_id = None
369 self.username = None
369 self.username = None
370 self.is_authenticated = False
370 self.is_authenticated = False
371
371
372 if not self.username:
372 if not self.username:
373 self.username = 'None'
373 self.username = 'None'
374
374
375 log.debug('Auth User is now %s' % self)
375 log.debug('Auth User is now %s' % self)
376 user_model.fill_perms(self)
376 user_model.fill_perms(self)
377
377
378 @property
378 @property
379 def is_admin(self):
379 def is_admin(self):
380 return self.admin
380 return self.admin
381
381
382 def __repr__(self):
382 def __repr__(self):
383 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
383 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
384 self.is_authenticated)
384 self.is_authenticated)
385
385
386 def set_authenticated(self, authenticated=True):
386 def set_authenticated(self, authenticated=True):
387 if self.user_id != self.anonymous_user.user_id:
387 if self.user_id != self.anonymous_user.user_id:
388 self.is_authenticated = authenticated
388 self.is_authenticated = authenticated
389
389
390 def get_cookie_store(self):
390 def get_cookie_store(self):
391 return {'username': self.username,
391 return {'username': self.username,
392 'user_id': self.user_id,
392 'user_id': self.user_id,
393 'is_authenticated': self.is_authenticated}
393 'is_authenticated': self.is_authenticated}
394
394
395 @classmethod
395 @classmethod
396 def from_cookie_store(cls, cookie_store):
396 def from_cookie_store(cls, cookie_store):
397 """
397 """
398 Creates AuthUser from a cookie store
398 Creates AuthUser from a cookie store
399
399
400 :param cls:
400 :param cls:
401 :param cookie_store:
401 :param cookie_store:
402 """
402 """
403 user_id = cookie_store.get('user_id')
403 user_id = cookie_store.get('user_id')
404 username = cookie_store.get('username')
404 username = cookie_store.get('username')
405 api_key = cookie_store.get('api_key')
405 api_key = cookie_store.get('api_key')
406 return AuthUser(user_id, api_key, username)
406 return AuthUser(user_id, api_key, username)
407
407
408
408
409 def set_available_permissions(config):
409 def set_available_permissions(config):
410 """
410 """
411 This function will propagate pylons globals with all available defined
411 This function will propagate pylons globals with all available defined
412 permission given in db. We don't want to check each time from db for new
412 permission given in db. We don't want to check each time from db for new
413 permissions since adding a new permission also requires application restart
413 permissions since adding a new permission also requires application restart
414 ie. to decorate new views with the newly created permission
414 ie. to decorate new views with the newly created permission
415
415
416 :param config: current pylons config instance
416 :param config: current pylons config instance
417
417
418 """
418 """
419 log.info('getting information about all available permissions')
419 log.info('getting information about all available permissions')
420 try:
420 try:
421 sa = meta.Session
421 sa = meta.Session
422 all_perms = sa.query(Permission).all()
422 all_perms = sa.query(Permission).all()
423 except Exception:
423 except Exception:
424 pass
424 pass
425 finally:
425 finally:
426 meta.Session.remove()
426 meta.Session.remove()
427
427
428 config['available_permissions'] = [x.permission_name for x in all_perms]
428 config['available_permissions'] = [x.permission_name for x in all_perms]
429
429
430
430
431 #==============================================================================
431 #==============================================================================
432 # CHECK DECORATORS
432 # CHECK DECORATORS
433 #==============================================================================
433 #==============================================================================
434 class LoginRequired(object):
434 class LoginRequired(object):
435 """
435 """
436 Must be logged in to execute this function else
436 Must be logged in to execute this function else
437 redirect to login page
437 redirect to login page
438
438
439 :param api_access: if enabled this checks only for valid auth token
439 :param api_access: if enabled this checks only for valid auth token
440 and grants access based on valid token
440 and grants access based on valid token
441 """
441 """
442
442
443 def __init__(self, api_access=False):
443 def __init__(self, api_access=False):
444 self.api_access = api_access
444 self.api_access = api_access
445
445
446 def __call__(self, func):
446 def __call__(self, func):
447 return decorator(self.__wrapper, func)
447 return decorator(self.__wrapper, func)
448
448
449 def __wrapper(self, func, *fargs, **fkwargs):
449 def __wrapper(self, func, *fargs, **fkwargs):
450 cls = fargs[0]
450 cls = fargs[0]
451 user = cls.rhodecode_user
451 user = cls.rhodecode_user
452
452
453 api_access_ok = False
453 api_access_ok = False
454 if self.api_access:
454 if self.api_access:
455 log.debug('Checking API KEY access for %s' % cls)
455 log.debug('Checking API KEY access for %s' % cls)
456 if user.api_key == request.GET.get('api_key'):
456 if user.api_key == request.GET.get('api_key'):
457 api_access_ok = True
457 api_access_ok = True
458 else:
458 else:
459 log.debug("API KEY token not valid")
459 log.debug("API KEY token not valid")
460 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
460 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
461 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
461 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
462 if user.is_authenticated or api_access_ok:
462 if user.is_authenticated or api_access_ok:
463 log.info('user %s is authenticated and granted access to %s' % (
463 log.info('user %s is authenticated and granted access to %s' % (
464 user.username, loc)
464 user.username, loc)
465 )
465 )
466 return func(*fargs, **fkwargs)
466 return func(*fargs, **fkwargs)
467 else:
467 else:
468 log.warn('user %s NOT authenticated on func: %s' % (
468 log.warn('user %s NOT authenticated on func: %s' % (
469 user, loc)
469 user, loc)
470 )
470 )
471 p = url.current()
471 p = url.current()
472
472
473 log.debug('redirecting to login page with %s' % p)
473 log.debug('redirecting to login page with %s' % p)
474 return redirect(url('login_home', came_from=p))
474 return redirect(url('login_home', came_from=p))
475
475
476
476
477 class NotAnonymous(object):
477 class NotAnonymous(object):
478 """
478 """
479 Must be logged in to execute this function else
479 Must be logged in to execute this function else
480 redirect to login page"""
480 redirect to login page"""
481
481
482 def __call__(self, func):
482 def __call__(self, func):
483 return decorator(self.__wrapper, func)
483 return decorator(self.__wrapper, func)
484
484
485 def __wrapper(self, func, *fargs, **fkwargs):
485 def __wrapper(self, func, *fargs, **fkwargs):
486 cls = fargs[0]
486 cls = fargs[0]
487 self.user = cls.rhodecode_user
487 self.user = cls.rhodecode_user
488
488
489 log.debug('Checking if user is not anonymous @%s' % cls)
489 log.debug('Checking if user is not anonymous @%s' % cls)
490
490
491 anonymous = self.user.username == 'default'
491 anonymous = self.user.username == 'default'
492
492
493 if anonymous:
493 if anonymous:
494 p = url.current()
494 p = url.current()
495
495
496 import rhodecode.lib.helpers as h
496 import rhodecode.lib.helpers as h
497 h.flash(_('You need to be a registered user to '
497 h.flash(_('You need to be a registered user to '
498 'perform this action'),
498 'perform this action'),
499 category='warning')
499 category='warning')
500 return redirect(url('login_home', came_from=p))
500 return redirect(url('login_home', came_from=p))
501 else:
501 else:
502 return func(*fargs, **fkwargs)
502 return func(*fargs, **fkwargs)
503
503
504
504
505 class PermsDecorator(object):
505 class PermsDecorator(object):
506 """Base class for controller decorators"""
506 """Base class for controller decorators"""
507
507
508 def __init__(self, *required_perms):
508 def __init__(self, *required_perms):
509 available_perms = config['available_permissions']
509 available_perms = config['available_permissions']
510 for perm in required_perms:
510 for perm in required_perms:
511 if perm not in available_perms:
511 if perm not in available_perms:
512 raise Exception("'%s' permission is not defined" % perm)
512 raise Exception("'%s' permission is not defined" % perm)
513 self.required_perms = set(required_perms)
513 self.required_perms = set(required_perms)
514 self.user_perms = None
514 self.user_perms = None
515
515
516 def __call__(self, func):
516 def __call__(self, func):
517 return decorator(self.__wrapper, func)
517 return decorator(self.__wrapper, func)
518
518
519 def __wrapper(self, func, *fargs, **fkwargs):
519 def __wrapper(self, func, *fargs, **fkwargs):
520 cls = fargs[0]
520 cls = fargs[0]
521 self.user = cls.rhodecode_user
521 self.user = cls.rhodecode_user
522 self.user_perms = self.user.permissions
522 self.user_perms = self.user.permissions
523 log.debug('checking %s permissions %s for %s %s',
523 log.debug('checking %s permissions %s for %s %s',
524 self.__class__.__name__, self.required_perms, cls,
524 self.__class__.__name__, self.required_perms, cls, self.user)
525 self.user)
526
525
527 if self.check_permissions():
526 if self.check_permissions():
528 log.debug('Permission granted for %s %s' % (cls, self.user))
527 log.debug('Permission granted for %s %s' % (cls, self.user))
529 return func(*fargs, **fkwargs)
528 return func(*fargs, **fkwargs)
530
529
531 else:
530 else:
532 log.debug('Permission denied for %s %s' % (cls, self.user))
531 log.debug('Permission denied for %s %s' % (cls, self.user))
533 anonymous = self.user.username == 'default'
532 anonymous = self.user.username == 'default'
534
533
535 if anonymous:
534 if anonymous:
536 p = url.current()
535 p = url.current()
537
536
538 import rhodecode.lib.helpers as h
537 import rhodecode.lib.helpers as h
539 h.flash(_('You need to be a signed in to '
538 h.flash(_('You need to be a signed in to '
540 'view this page'),
539 'view this page'),
541 category='warning')
540 category='warning')
542 return redirect(url('login_home', came_from=p))
541 return redirect(url('login_home', came_from=p))
543
542
544 else:
543 else:
545 # redirect with forbidden ret code
544 # redirect with forbidden ret code
546 return abort(403)
545 return abort(403)
547
546
548 def check_permissions(self):
547 def check_permissions(self):
549 """Dummy function for overriding"""
548 """Dummy function for overriding"""
550 raise Exception('You have to write this function in child class')
549 raise Exception('You have to write this function in child class')
551
550
552
551
553 class HasPermissionAllDecorator(PermsDecorator):
552 class HasPermissionAllDecorator(PermsDecorator):
554 """
553 """
555 Checks for access permission for all given predicates. All of them
554 Checks for access permission for all given predicates. All of them
556 have to be meet in order to fulfill the request
555 have to be meet in order to fulfill the request
557 """
556 """
558
557
559 def check_permissions(self):
558 def check_permissions(self):
560 if self.required_perms.issubset(self.user_perms.get('global')):
559 if self.required_perms.issubset(self.user_perms.get('global')):
561 return True
560 return True
562 return False
561 return False
563
562
564
563
565 class HasPermissionAnyDecorator(PermsDecorator):
564 class HasPermissionAnyDecorator(PermsDecorator):
566 """
565 """
567 Checks for access permission for any of given predicates. In order to
566 Checks for access permission for any of given predicates. In order to
568 fulfill the request any of predicates must be meet
567 fulfill the request any of predicates must be meet
569 """
568 """
570
569
571 def check_permissions(self):
570 def check_permissions(self):
572 if self.required_perms.intersection(self.user_perms.get('global')):
571 if self.required_perms.intersection(self.user_perms.get('global')):
573 return True
572 return True
574 return False
573 return False
575
574
576
575
577 class HasRepoPermissionAllDecorator(PermsDecorator):
576 class HasRepoPermissionAllDecorator(PermsDecorator):
578 """
577 """
579 Checks for access permission for all given predicates for specific
578 Checks for access permission for all given predicates for specific
580 repository. All of them have to be meet in order to fulfill the request
579 repository. All of them have to be meet in order to fulfill the request
581 """
580 """
582
581
583 def check_permissions(self):
582 def check_permissions(self):
584 repo_name = get_repo_slug(request)
583 repo_name = get_repo_slug(request)
585 try:
584 try:
586 user_perms = set([self.user_perms['repositories'][repo_name]])
585 user_perms = set([self.user_perms['repositories'][repo_name]])
587 except KeyError:
586 except KeyError:
588 return False
587 return False
589 if self.required_perms.issubset(user_perms):
588 if self.required_perms.issubset(user_perms):
590 return True
589 return True
591 return False
590 return False
592
591
593
592
594 class HasRepoPermissionAnyDecorator(PermsDecorator):
593 class HasRepoPermissionAnyDecorator(PermsDecorator):
595 """
594 """
596 Checks for access permission for any of given predicates for specific
595 Checks for access permission for any of given predicates for specific
597 repository. In order to fulfill the request any of predicates must be meet
596 repository. In order to fulfill the request any of predicates must be meet
598 """
597 """
599
598
600 def check_permissions(self):
599 def check_permissions(self):
601 repo_name = get_repo_slug(request)
600 repo_name = get_repo_slug(request)
602
601
603 try:
602 try:
604 user_perms = set([self.user_perms['repositories'][repo_name]])
603 user_perms = set([self.user_perms['repositories'][repo_name]])
605 except KeyError:
604 except KeyError:
606 return False
605 return False
606
607 if self.required_perms.intersection(user_perms):
607 if self.required_perms.intersection(user_perms):
608 return True
608 return True
609 return False
609 return False
610
610
611
611
612 class HasReposGroupPermissionAllDecorator(PermsDecorator):
612 class HasReposGroupPermissionAllDecorator(PermsDecorator):
613 """
613 """
614 Checks for access permission for all given predicates for specific
614 Checks for access permission for all given predicates for specific
615 repository. All of them have to be meet in order to fulfill the request
615 repository. All of them have to be meet in order to fulfill the request
616 """
616 """
617
617
618 def check_permissions(self):
618 def check_permissions(self):
619 group_name = get_repos_group_slug(request)
619 group_name = get_repos_group_slug(request)
620 try:
620 try:
621 user_perms = set([self.user_perms['repositories_groups'][group_name]])
621 user_perms = set([self.user_perms['repositories_groups'][group_name]])
622 except KeyError:
622 except KeyError:
623 return False
623 return False
624 if self.required_perms.issubset(user_perms):
624 if self.required_perms.issubset(user_perms):
625 return True
625 return True
626 return False
626 return False
627
627
628
628
629 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
629 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
630 """
630 """
631 Checks for access permission for any of given predicates for specific
631 Checks for access permission for any of given predicates for specific
632 repository. In order to fulfill the request any of predicates must be meet
632 repository. In order to fulfill the request any of predicates must be meet
633 """
633 """
634
634
635 def check_permissions(self):
635 def check_permissions(self):
636 group_name = get_repos_group_slug(request)
636 group_name = get_repos_group_slug(request)
637
637
638 try:
638 try:
639 user_perms = set([self.user_perms['repositories_groups'][group_name]])
639 user_perms = set([self.user_perms['repositories_groups'][group_name]])
640 except KeyError:
640 except KeyError:
641 return False
641 return False
642 if self.required_perms.intersection(user_perms):
642 if self.required_perms.intersection(user_perms):
643 return True
643 return True
644 return False
644 return False
645
645
646
646
647 #==============================================================================
647 #==============================================================================
648 # CHECK FUNCTIONS
648 # CHECK FUNCTIONS
649 #==============================================================================
649 #==============================================================================
650 class PermsFunction(object):
650 class PermsFunction(object):
651 """Base function for other check functions"""
651 """Base function for other check functions"""
652
652
653 def __init__(self, *perms):
653 def __init__(self, *perms):
654 available_perms = config['available_permissions']
654 available_perms = config['available_permissions']
655
655
656 for perm in perms:
656 for perm in perms:
657 if perm not in available_perms:
657 if perm not in available_perms:
658 raise Exception("'%s' permission is not defined" % perm)
658 raise Exception("'%s' permission is not defined" % perm)
659 self.required_perms = set(perms)
659 self.required_perms = set(perms)
660 self.user_perms = None
660 self.user_perms = None
661 self.granted_for = ''
662 self.repo_name = None
661 self.repo_name = None
662 self.group_name = None
663
663
664 def __call__(self, check_Location=''):
664 def __call__(self, check_Location=''):
665 user = request.user
665 user = request.user
666 log.debug('checking %s %s %s', self.__class__.__name__,
666 cls_name = self.__class__.__name__
667 self.required_perms, user)
667 check_scope = {
668 'HasPermissionAll': '',
669 'HasPermissionAny': '',
670 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
671 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
672 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
673 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
674 }.get(cls_name, '?')
675 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
676 self.required_perms, user, check_scope,
677 check_Location or 'unspecified location')
668 if not user:
678 if not user:
669 log.debug('Empty request user')
679 log.debug('Empty request user')
670 return False
680 return False
671 self.user_perms = user.permissions
681 self.user_perms = user.permissions
672 self.granted_for = user
673
674 if self.check_permissions():
682 if self.check_permissions():
675 log.debug('Permission granted %s @ %s', self.granted_for,
683 log.debug('Permission granted for user: %s @ %s', user,
676 check_Location or 'unspecified location')
684 check_Location or 'unspecified location')
677 return True
685 return True
678
686
679 else:
687 else:
680 log.debug('Permission denied for %s @ %s', self.granted_for,
688 log.debug('Permission denied for user: %s @ %s', user,
681 check_Location or 'unspecified location')
689 check_Location or 'unspecified location')
682 return False
690 return False
683
691
684 def check_permissions(self):
692 def check_permissions(self):
685 """Dummy function for overriding"""
693 """Dummy function for overriding"""
686 raise Exception('You have to write this function in child class')
694 raise Exception('You have to write this function in child class')
687
695
688
696
689 class HasPermissionAll(PermsFunction):
697 class HasPermissionAll(PermsFunction):
690 def check_permissions(self):
698 def check_permissions(self):
691 if self.required_perms.issubset(self.user_perms.get('global')):
699 if self.required_perms.issubset(self.user_perms.get('global')):
692 return True
700 return True
693 return False
701 return False
694
702
695
703
696 class HasPermissionAny(PermsFunction):
704 class HasPermissionAny(PermsFunction):
697 def check_permissions(self):
705 def check_permissions(self):
698 if self.required_perms.intersection(self.user_perms.get('global')):
706 if self.required_perms.intersection(self.user_perms.get('global')):
699 return True
707 return True
700 return False
708 return False
701
709
702
710
703 class HasRepoPermissionAll(PermsFunction):
711 class HasRepoPermissionAll(PermsFunction):
704
705 def __call__(self, repo_name=None, check_Location=''):
712 def __call__(self, repo_name=None, check_Location=''):
706 self.repo_name = repo_name
713 self.repo_name = repo_name
707 return super(HasRepoPermissionAll, self).__call__(check_Location)
714 return super(HasRepoPermissionAll, self).__call__(check_Location)
708
715
709 def check_permissions(self):
716 def check_permissions(self):
710 if not self.repo_name:
717 if not self.repo_name:
711 self.repo_name = get_repo_slug(request)
718 self.repo_name = get_repo_slug(request)
712
719
713 try:
720 try:
714 self.user_perms = set(
721 self._user_perms = set(
715 [self.user_perms['repositories'][self.repo_name]]
722 [self.user_perms['repositories'][self.repo_name]]
716 )
723 )
717 except KeyError:
724 except KeyError:
718 return False
725 return False
719 self.granted_for = self.repo_name
726 if self.required_perms.issubset(self._user_perms):
720 if self.required_perms.issubset(self.user_perms):
721 return True
727 return True
722 return False
728 return False
723
729
724
730
725 class HasRepoPermissionAny(PermsFunction):
731 class HasRepoPermissionAny(PermsFunction):
726
727 def __call__(self, repo_name=None, check_Location=''):
732 def __call__(self, repo_name=None, check_Location=''):
728 self.repo_name = repo_name
733 self.repo_name = repo_name
729 return super(HasRepoPermissionAny, self).__call__(check_Location)
734 return super(HasRepoPermissionAny, self).__call__(check_Location)
730
735
731 def check_permissions(self):
736 def check_permissions(self):
732 if not self.repo_name:
737 if not self.repo_name:
733 self.repo_name = get_repo_slug(request)
738 self.repo_name = get_repo_slug(request)
734
739
735 try:
740 try:
736 self.user_perms = set(
741 self._user_perms = set(
737 [self.user_perms['repositories'][self.repo_name]]
742 [self.user_perms['repositories'][self.repo_name]]
738 )
743 )
739 except KeyError:
744 except KeyError:
740 return False
745 return False
741 self.granted_for = self.repo_name
746 if self.required_perms.intersection(self._user_perms):
742 if self.required_perms.intersection(self.user_perms):
743 return True
747 return True
744 return False
748 return False
745
749
746
750
747 class HasReposGroupPermissionAny(PermsFunction):
751 class HasReposGroupPermissionAny(PermsFunction):
748 def __call__(self, group_name=None, check_Location=''):
752 def __call__(self, group_name=None, check_Location=''):
749 self.group_name = group_name
753 self.group_name = group_name
750 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
754 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
751
755
752 def check_permissions(self):
756 def check_permissions(self):
753 try:
757 try:
754 self.user_perms = set(
758 self._user_perms = set(
755 [self.user_perms['repositories_groups'][self.group_name]]
759 [self.user_perms['repositories_groups'][self.group_name]]
756 )
760 )
757 except KeyError:
761 except KeyError:
758 return False
762 return False
759 self.granted_for = self.repo_name
763 if self.required_perms.intersection(self._user_perms):
760 if self.required_perms.intersection(self.user_perms):
761 return True
764 return True
762 return False
765 return False
763
766
764
767
765 class HasReposGroupPermissionAll(PermsFunction):
768 class HasReposGroupPermissionAll(PermsFunction):
766 def __call__(self, group_name=None, check_Location=''):
769 def __call__(self, group_name=None, check_Location=''):
767 self.group_name = group_name
770 self.group_name = group_name
768 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
771 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
769
772
770 def check_permissions(self):
773 def check_permissions(self):
771 try:
774 try:
772 self.user_perms = set(
775 self._user_perms = set(
773 [self.user_perms['repositories_groups'][self.group_name]]
776 [self.user_perms['repositories_groups'][self.group_name]]
774 )
777 )
775 except KeyError:
778 except KeyError:
776 return False
779 return False
777 self.granted_for = self.repo_name
780 if self.required_perms.issubset(self._user_perms):
778 if self.required_perms.issubset(self.user_perms):
779 return True
781 return True
780 return False
782 return False
781
783
782
784
783 #==============================================================================
785 #==============================================================================
784 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
786 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
785 #==============================================================================
787 #==============================================================================
786 class HasPermissionAnyMiddleware(object):
788 class HasPermissionAnyMiddleware(object):
787 def __init__(self, *perms):
789 def __init__(self, *perms):
788 self.required_perms = set(perms)
790 self.required_perms = set(perms)
789
791
790 def __call__(self, user, repo_name):
792 def __call__(self, user, repo_name):
791 # repo_name MUST be unicode, since we handle keys in permission
793 # repo_name MUST be unicode, since we handle keys in permission
792 # dict by unicode
794 # dict by unicode
793 repo_name = safe_unicode(repo_name)
795 repo_name = safe_unicode(repo_name)
794 usr = AuthUser(user.user_id)
796 usr = AuthUser(user.user_id)
795 try:
797 try:
796 self.user_perms = set([usr.permissions['repositories'][repo_name]])
798 self.user_perms = set([usr.permissions['repositories'][repo_name]])
797 except Exception:
799 except Exception:
798 log.error('Exception while accessing permissions %s' %
800 log.error('Exception while accessing permissions %s' %
799 traceback.format_exc())
801 traceback.format_exc())
800 self.user_perms = set()
802 self.user_perms = set()
801 self.granted_for = ''
802 self.username = user.username
803 self.username = user.username
803 self.repo_name = repo_name
804 self.repo_name = repo_name
804 return self.check_permissions()
805 return self.check_permissions()
805
806
806 def check_permissions(self):
807 def check_permissions(self):
807 log.debug('checking mercurial protocol '
808 log.debug('checking mercurial protocol '
808 'permissions %s for user:%s repository:%s', self.user_perms,
809 'permissions %s for user:%s repository:%s', self.user_perms,
809 self.username, self.repo_name)
810 self.username, self.repo_name)
810 if self.required_perms.intersection(self.user_perms):
811 if self.required_perms.intersection(self.user_perms):
811 log.debug('permission granted')
812 log.debug('permission granted for user:%s on repo:%s' % (
813 self.username, self.repo_name
814 )
815 )
812 return True
816 return True
813 log.debug('permission denied')
817 log.debug('permission denied for user:%s on repo:%s' % (
818 self.username, self.repo_name
819 )
820 )
814 return False
821 return False
@@ -1,926 +1,926 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12
12
13 from datetime import datetime
13 from datetime import datetime
14 from pygments.formatters.html import HtmlFormatter
14 from pygments.formatters.html import HtmlFormatter
15 from pygments import highlight as code_highlight
15 from pygments import highlight as code_highlight
16 from pylons import url, request, config
16 from pylons import url, request, config
17 from pylons.i18n.translation import _, ungettext
17 from pylons.i18n.translation import _, ungettext
18 from hashlib import md5
18 from hashlib import md5
19
19
20 from webhelpers.html import literal, HTML, escape
20 from webhelpers.html import literal, HTML, escape
21 from webhelpers.html.tools import *
21 from webhelpers.html.tools import *
22 from webhelpers.html.builder import make_tag
22 from webhelpers.html.builder import make_tag
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 submit, text, password, textarea, title, ul, xml_declaration, radio
26 submit, text, password, textarea, title, ul, xml_declaration, radio
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 from webhelpers.number import format_byte_size, format_bit_size
29 from webhelpers.number import format_byte_size, format_bit_size
30 from webhelpers.pylonslib import Flash as _Flash
30 from webhelpers.pylonslib import Flash as _Flash
31 from webhelpers.pylonslib.secure_form import secure_form
31 from webhelpers.pylonslib.secure_form import secure_form
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 replace_whitespace, urlify, truncate, wrap_paragraphs
34 replace_whitespace, urlify, truncate, wrap_paragraphs
35 from webhelpers.date import time_ago_in_words
35 from webhelpers.date import time_ago_in_words
36 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
39
39
40 from rhodecode.lib.annotate import annotate_highlight
40 from rhodecode.lib.annotate import annotate_highlight
41 from rhodecode.lib.utils import repo_name_slug
41 from rhodecode.lib.utils import repo_name_slug
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
43 get_changeset_safe
43 get_changeset_safe
44 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.markup_renderer import MarkupRenderer
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
49 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
50 """
50 """
51 Reset button
51 Reset button
52 """
52 """
53 _set_input_attrs(attrs, type, name, value)
53 _set_input_attrs(attrs, type, name, value)
54 _set_id_attr(attrs, id, name)
54 _set_id_attr(attrs, id, name)
55 convert_boolean_attrs(attrs, ["disabled"])
55 convert_boolean_attrs(attrs, ["disabled"])
56 return HTML.input(**attrs)
56 return HTML.input(**attrs)
57
57
58 reset = _reset
58 reset = _reset
59 safeid = _make_safe_id_component
59 safeid = _make_safe_id_component
60
60
61
61
62 def FID(raw_id, path):
62 def FID(raw_id, path):
63 """
63 """
64 Creates a uniqe ID for filenode based on it's hash of path and revision
64 Creates a uniqe ID for filenode based on it's hash of path and revision
65 it's safe to use in urls
65 it's safe to use in urls
66
66
67 :param raw_id:
67 :param raw_id:
68 :param path:
68 :param path:
69 """
69 """
70
70
71 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
71 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
72
72
73
73
74 def get_token():
74 def get_token():
75 """Return the current authentication token, creating one if one doesn't
75 """Return the current authentication token, creating one if one doesn't
76 already exist.
76 already exist.
77 """
77 """
78 token_key = "_authentication_token"
78 token_key = "_authentication_token"
79 from pylons import session
79 from pylons import session
80 if not token_key in session:
80 if not token_key in session:
81 try:
81 try:
82 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
82 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
83 except AttributeError: # Python < 2.4
83 except AttributeError: # Python < 2.4
84 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
84 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
85 session[token_key] = token
85 session[token_key] = token
86 if hasattr(session, 'save'):
86 if hasattr(session, 'save'):
87 session.save()
87 session.save()
88 return session[token_key]
88 return session[token_key]
89
89
90 class _GetError(object):
90 class _GetError(object):
91 """Get error from form_errors, and represent it as span wrapped error
91 """Get error from form_errors, and represent it as span wrapped error
92 message
92 message
93
93
94 :param field_name: field to fetch errors for
94 :param field_name: field to fetch errors for
95 :param form_errors: form errors dict
95 :param form_errors: form errors dict
96 """
96 """
97
97
98 def __call__(self, field_name, form_errors):
98 def __call__(self, field_name, form_errors):
99 tmpl = """<span class="error_msg">%s</span>"""
99 tmpl = """<span class="error_msg">%s</span>"""
100 if form_errors and form_errors.has_key(field_name):
100 if form_errors and form_errors.has_key(field_name):
101 return literal(tmpl % form_errors.get(field_name))
101 return literal(tmpl % form_errors.get(field_name))
102
102
103 get_error = _GetError()
103 get_error = _GetError()
104
104
105 class _ToolTip(object):
105 class _ToolTip(object):
106
106
107 def __call__(self, tooltip_title, trim_at=50):
107 def __call__(self, tooltip_title, trim_at=50):
108 """Special function just to wrap our text into nice formatted
108 """Special function just to wrap our text into nice formatted
109 autowrapped text
109 autowrapped text
110
110
111 :param tooltip_title:
111 :param tooltip_title:
112 """
112 """
113 return escape(tooltip_title)
113 return escape(tooltip_title)
114 tooltip = _ToolTip()
114 tooltip = _ToolTip()
115
115
116 class _FilesBreadCrumbs(object):
116 class _FilesBreadCrumbs(object):
117
117
118 def __call__(self, repo_name, rev, paths):
118 def __call__(self, repo_name, rev, paths):
119 if isinstance(paths, str):
119 if isinstance(paths, str):
120 paths = safe_unicode(paths)
120 paths = safe_unicode(paths)
121 url_l = [link_to(repo_name, url('files_home',
121 url_l = [link_to(repo_name, url('files_home',
122 repo_name=repo_name,
122 repo_name=repo_name,
123 revision=rev, f_path=''))]
123 revision=rev, f_path=''))]
124 paths_l = paths.split('/')
124 paths_l = paths.split('/')
125 for cnt, p in enumerate(paths_l):
125 for cnt, p in enumerate(paths_l):
126 if p != '':
126 if p != '':
127 url_l.append(link_to(p,
127 url_l.append(link_to(p,
128 url('files_home',
128 url('files_home',
129 repo_name=repo_name,
129 repo_name=repo_name,
130 revision=rev,
130 revision=rev,
131 f_path='/'.join(paths_l[:cnt + 1])
131 f_path='/'.join(paths_l[:cnt + 1])
132 )
132 )
133 )
133 )
134 )
134 )
135
135
136 return literal('/'.join(url_l))
136 return literal('/'.join(url_l))
137
137
138 files_breadcrumbs = _FilesBreadCrumbs()
138 files_breadcrumbs = _FilesBreadCrumbs()
139
139
140 class CodeHtmlFormatter(HtmlFormatter):
140 class CodeHtmlFormatter(HtmlFormatter):
141 """My code Html Formatter for source codes
141 """My code Html Formatter for source codes
142 """
142 """
143
143
144 def wrap(self, source, outfile):
144 def wrap(self, source, outfile):
145 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
145 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
146
146
147 def _wrap_code(self, source):
147 def _wrap_code(self, source):
148 for cnt, it in enumerate(source):
148 for cnt, it in enumerate(source):
149 i, t = it
149 i, t = it
150 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
150 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
151 yield i, t
151 yield i, t
152
152
153 def _wrap_tablelinenos(self, inner):
153 def _wrap_tablelinenos(self, inner):
154 dummyoutfile = StringIO.StringIO()
154 dummyoutfile = StringIO.StringIO()
155 lncount = 0
155 lncount = 0
156 for t, line in inner:
156 for t, line in inner:
157 if t:
157 if t:
158 lncount += 1
158 lncount += 1
159 dummyoutfile.write(line)
159 dummyoutfile.write(line)
160
160
161 fl = self.linenostart
161 fl = self.linenostart
162 mw = len(str(lncount + fl - 1))
162 mw = len(str(lncount + fl - 1))
163 sp = self.linenospecial
163 sp = self.linenospecial
164 st = self.linenostep
164 st = self.linenostep
165 la = self.lineanchors
165 la = self.lineanchors
166 aln = self.anchorlinenos
166 aln = self.anchorlinenos
167 nocls = self.noclasses
167 nocls = self.noclasses
168 if sp:
168 if sp:
169 lines = []
169 lines = []
170
170
171 for i in range(fl, fl + lncount):
171 for i in range(fl, fl + lncount):
172 if i % st == 0:
172 if i % st == 0:
173 if i % sp == 0:
173 if i % sp == 0:
174 if aln:
174 if aln:
175 lines.append('<a href="#%s%d" class="special">%*d</a>' %
175 lines.append('<a href="#%s%d" class="special">%*d</a>' %
176 (la, i, mw, i))
176 (la, i, mw, i))
177 else:
177 else:
178 lines.append('<span class="special">%*d</span>' % (mw, i))
178 lines.append('<span class="special">%*d</span>' % (mw, i))
179 else:
179 else:
180 if aln:
180 if aln:
181 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
181 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
182 else:
182 else:
183 lines.append('%*d' % (mw, i))
183 lines.append('%*d' % (mw, i))
184 else:
184 else:
185 lines.append('')
185 lines.append('')
186 ls = '\n'.join(lines)
186 ls = '\n'.join(lines)
187 else:
187 else:
188 lines = []
188 lines = []
189 for i in range(fl, fl + lncount):
189 for i in range(fl, fl + lncount):
190 if i % st == 0:
190 if i % st == 0:
191 if aln:
191 if aln:
192 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
192 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
193 else:
193 else:
194 lines.append('%*d' % (mw, i))
194 lines.append('%*d' % (mw, i))
195 else:
195 else:
196 lines.append('')
196 lines.append('')
197 ls = '\n'.join(lines)
197 ls = '\n'.join(lines)
198
198
199 # in case you wonder about the seemingly redundant <div> here: since the
199 # in case you wonder about the seemingly redundant <div> here: since the
200 # content in the other cell also is wrapped in a div, some browsers in
200 # content in the other cell also is wrapped in a div, some browsers in
201 # some configurations seem to mess up the formatting...
201 # some configurations seem to mess up the formatting...
202 if nocls:
202 if nocls:
203 yield 0, ('<table class="%stable">' % self.cssclass +
203 yield 0, ('<table class="%stable">' % self.cssclass +
204 '<tr><td><div class="linenodiv" '
204 '<tr><td><div class="linenodiv" '
205 'style="background-color: #f0f0f0; padding-right: 10px">'
205 'style="background-color: #f0f0f0; padding-right: 10px">'
206 '<pre style="line-height: 125%">' +
206 '<pre style="line-height: 125%">' +
207 ls + '</pre></div></td><td id="hlcode" class="code">')
207 ls + '</pre></div></td><td id="hlcode" class="code">')
208 else:
208 else:
209 yield 0, ('<table class="%stable">' % self.cssclass +
209 yield 0, ('<table class="%stable">' % self.cssclass +
210 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
210 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
211 ls + '</pre></div></td><td id="hlcode" class="code">')
211 ls + '</pre></div></td><td id="hlcode" class="code">')
212 yield 0, dummyoutfile.getvalue()
212 yield 0, dummyoutfile.getvalue()
213 yield 0, '</td></tr></table>'
213 yield 0, '</td></tr></table>'
214
214
215
215
216 def pygmentize(filenode, **kwargs):
216 def pygmentize(filenode, **kwargs):
217 """pygmentize function using pygments
217 """pygmentize function using pygments
218
218
219 :param filenode:
219 :param filenode:
220 """
220 """
221
221
222 return literal(code_highlight(filenode.content,
222 return literal(code_highlight(filenode.content,
223 filenode.lexer, CodeHtmlFormatter(**kwargs)))
223 filenode.lexer, CodeHtmlFormatter(**kwargs)))
224
224
225
225
226 def pygmentize_annotation(repo_name, filenode, **kwargs):
226 def pygmentize_annotation(repo_name, filenode, **kwargs):
227 """
227 """
228 pygmentize function for annotation
228 pygmentize function for annotation
229
229
230 :param filenode:
230 :param filenode:
231 """
231 """
232
232
233 color_dict = {}
233 color_dict = {}
234
234
235 def gen_color(n=10000):
235 def gen_color(n=10000):
236 """generator for getting n of evenly distributed colors using
236 """generator for getting n of evenly distributed colors using
237 hsv color and golden ratio. It always return same order of colors
237 hsv color and golden ratio. It always return same order of colors
238
238
239 :returns: RGB tuple
239 :returns: RGB tuple
240 """
240 """
241
241
242 def hsv_to_rgb(h, s, v):
242 def hsv_to_rgb(h, s, v):
243 if s == 0.0:
243 if s == 0.0:
244 return v, v, v
244 return v, v, v
245 i = int(h * 6.0) # XXX assume int() truncates!
245 i = int(h * 6.0) # XXX assume int() truncates!
246 f = (h * 6.0) - i
246 f = (h * 6.0) - i
247 p = v * (1.0 - s)
247 p = v * (1.0 - s)
248 q = v * (1.0 - s * f)
248 q = v * (1.0 - s * f)
249 t = v * (1.0 - s * (1.0 - f))
249 t = v * (1.0 - s * (1.0 - f))
250 i = i % 6
250 i = i % 6
251 if i == 0:
251 if i == 0:
252 return v, t, p
252 return v, t, p
253 if i == 1:
253 if i == 1:
254 return q, v, p
254 return q, v, p
255 if i == 2:
255 if i == 2:
256 return p, v, t
256 return p, v, t
257 if i == 3:
257 if i == 3:
258 return p, q, v
258 return p, q, v
259 if i == 4:
259 if i == 4:
260 return t, p, v
260 return t, p, v
261 if i == 5:
261 if i == 5:
262 return v, p, q
262 return v, p, q
263
263
264 golden_ratio = 0.618033988749895
264 golden_ratio = 0.618033988749895
265 h = 0.22717784590367374
265 h = 0.22717784590367374
266
266
267 for _ in xrange(n):
267 for _ in xrange(n):
268 h += golden_ratio
268 h += golden_ratio
269 h %= 1
269 h %= 1
270 HSV_tuple = [h, 0.95, 0.95]
270 HSV_tuple = [h, 0.95, 0.95]
271 RGB_tuple = hsv_to_rgb(*HSV_tuple)
271 RGB_tuple = hsv_to_rgb(*HSV_tuple)
272 yield map(lambda x: str(int(x * 256)), RGB_tuple)
272 yield map(lambda x: str(int(x * 256)), RGB_tuple)
273
273
274 cgenerator = gen_color()
274 cgenerator = gen_color()
275
275
276 def get_color_string(cs):
276 def get_color_string(cs):
277 if cs in color_dict:
277 if cs in color_dict:
278 col = color_dict[cs]
278 col = color_dict[cs]
279 else:
279 else:
280 col = color_dict[cs] = cgenerator.next()
280 col = color_dict[cs] = cgenerator.next()
281 return "color: rgb(%s)! important;" % (', '.join(col))
281 return "color: rgb(%s)! important;" % (', '.join(col))
282
282
283 def url_func(repo_name):
283 def url_func(repo_name):
284
284
285 def _url_func(changeset):
285 def _url_func(changeset):
286 author = changeset.author
286 author = changeset.author
287 date = changeset.date
287 date = changeset.date
288 message = tooltip(changeset.message)
288 message = tooltip(changeset.message)
289
289
290 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
290 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
291 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
291 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
292 "</b> %s<br/></div>")
292 "</b> %s<br/></div>")
293
293
294 tooltip_html = tooltip_html % (author, date, message)
294 tooltip_html = tooltip_html % (author, date, message)
295 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
295 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
296 short_id(changeset.raw_id))
296 short_id(changeset.raw_id))
297 uri = link_to(
297 uri = link_to(
298 lnk_format,
298 lnk_format,
299 url('changeset_home', repo_name=repo_name,
299 url('changeset_home', repo_name=repo_name,
300 revision=changeset.raw_id),
300 revision=changeset.raw_id),
301 style=get_color_string(changeset.raw_id),
301 style=get_color_string(changeset.raw_id),
302 class_='tooltip',
302 class_='tooltip',
303 title=tooltip_html
303 title=tooltip_html
304 )
304 )
305
305
306 uri += '\n'
306 uri += '\n'
307 return uri
307 return uri
308 return _url_func
308 return _url_func
309
309
310 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
310 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
311
311
312
312
313 def is_following_repo(repo_name, user_id):
313 def is_following_repo(repo_name, user_id):
314 from rhodecode.model.scm import ScmModel
314 from rhodecode.model.scm import ScmModel
315 return ScmModel().is_following_repo(repo_name, user_id)
315 return ScmModel().is_following_repo(repo_name, user_id)
316
316
317 flash = _Flash()
317 flash = _Flash()
318
318
319 #==============================================================================
319 #==============================================================================
320 # SCM FILTERS available via h.
320 # SCM FILTERS available via h.
321 #==============================================================================
321 #==============================================================================
322 from rhodecode.lib.vcs.utils import author_name, author_email
322 from rhodecode.lib.vcs.utils import author_name, author_email
323 from rhodecode.lib.utils2 import credentials_filter, age as _age
323 from rhodecode.lib.utils2 import credentials_filter, age as _age
324 from rhodecode.model.db import User
324 from rhodecode.model.db import User
325
325
326 age = lambda x: _age(x)
326 age = lambda x: _age(x)
327 capitalize = lambda x: x.capitalize()
327 capitalize = lambda x: x.capitalize()
328 email = author_email
328 email = author_email
329 short_id = lambda x: x[:12]
329 short_id = lambda x: x[:12]
330 hide_credentials = lambda x: ''.join(credentials_filter(x))
330 hide_credentials = lambda x: ''.join(credentials_filter(x))
331
331
332
332
333 def is_git(repository):
333 def is_git(repository):
334 if hasattr(repository, 'alias'):
334 if hasattr(repository, 'alias'):
335 _type = repository.alias
335 _type = repository.alias
336 elif hasattr(repository, 'repo_type'):
336 elif hasattr(repository, 'repo_type'):
337 _type = repository.repo_type
337 _type = repository.repo_type
338 else:
338 else:
339 _type = repository
339 _type = repository
340 return _type == 'git'
340 return _type == 'git'
341
341
342
342
343 def is_hg(repository):
343 def is_hg(repository):
344 if hasattr(repository, 'alias'):
344 if hasattr(repository, 'alias'):
345 _type = repository.alias
345 _type = repository.alias
346 elif hasattr(repository, 'repo_type'):
346 elif hasattr(repository, 'repo_type'):
347 _type = repository.repo_type
347 _type = repository.repo_type
348 else:
348 else:
349 _type = repository
349 _type = repository
350 return _type == 'hg'
350 return _type == 'hg'
351
351
352
352
353 def email_or_none(author):
353 def email_or_none(author):
354 _email = email(author)
354 _email = email(author)
355 if _email != '':
355 if _email != '':
356 return _email
356 return _email
357
357
358 # See if it contains a username we can get an email from
358 # See if it contains a username we can get an email from
359 user = User.get_by_username(author_name(author), case_insensitive=True,
359 user = User.get_by_username(author_name(author), case_insensitive=True,
360 cache=True)
360 cache=True)
361 if user is not None:
361 if user is not None:
362 return user.email
362 return user.email
363
363
364 # No valid email, not a valid user in the system, none!
364 # No valid email, not a valid user in the system, none!
365 return None
365 return None
366
366
367
367
368 def person(author):
368 def person(author):
369 # attr to return from fetched user
369 # attr to return from fetched user
370 person_getter = lambda usr: usr.username
370 person_getter = lambda usr: usr.username
371
371
372 # Valid email in the attribute passed, see if they're in the system
372 # Valid email in the attribute passed, see if they're in the system
373 _email = email(author)
373 _email = email(author)
374 if _email != '':
374 if _email != '':
375 user = User.get_by_email(_email, case_insensitive=True, cache=True)
375 user = User.get_by_email(_email, case_insensitive=True, cache=True)
376 if user is not None:
376 if user is not None:
377 return person_getter(user)
377 return person_getter(user)
378 return _email
378 return _email
379
379
380 # Maybe it's a username?
380 # Maybe it's a username?
381 _author = author_name(author)
381 _author = author_name(author)
382 user = User.get_by_username(_author, case_insensitive=True,
382 user = User.get_by_username(_author, case_insensitive=True,
383 cache=True)
383 cache=True)
384 if user is not None:
384 if user is not None:
385 return person_getter(user)
385 return person_getter(user)
386
386
387 # Still nothing? Just pass back the author name then
387 # Still nothing? Just pass back the author name then
388 return _author
388 return _author
389
389
390
390
391 def bool2icon(value):
391 def bool2icon(value):
392 """Returns True/False values represented as small html image of true/false
392 """Returns True/False values represented as small html image of true/false
393 icons
393 icons
394
394
395 :param value: bool value
395 :param value: bool value
396 """
396 """
397
397
398 if value is True:
398 if value is True:
399 return HTML.tag('img', src=url("/images/icons/accept.png"),
399 return HTML.tag('img', src=url("/images/icons/accept.png"),
400 alt=_('True'))
400 alt=_('True'))
401
401
402 if value is False:
402 if value is False:
403 return HTML.tag('img', src=url("/images/icons/cancel.png"),
403 return HTML.tag('img', src=url("/images/icons/cancel.png"),
404 alt=_('False'))
404 alt=_('False'))
405
405
406 return value
406 return value
407
407
408
408
409 def action_parser(user_log, feed=False):
409 def action_parser(user_log, feed=False):
410 """
410 """
411 This helper will action_map the specified string action into translated
411 This helper will action_map the specified string action into translated
412 fancy names with icons and links
412 fancy names with icons and links
413
413
414 :param user_log: user log instance
414 :param user_log: user log instance
415 :param feed: use output for feeds (no html and fancy icons)
415 :param feed: use output for feeds (no html and fancy icons)
416 """
416 """
417
417
418 action = user_log.action
418 action = user_log.action
419 action_params = ' '
419 action_params = ' '
420
420
421 x = action.split(':')
421 x = action.split(':')
422
422
423 if len(x) > 1:
423 if len(x) > 1:
424 action, action_params = x
424 action, action_params = x
425
425
426 def get_cs_links():
426 def get_cs_links():
427 revs_limit = 3 # display this amount always
427 revs_limit = 3 # display this amount always
428 revs_top_limit = 50 # show upto this amount of changesets hidden
428 revs_top_limit = 50 # show upto this amount of changesets hidden
429 revs_ids = action_params.split(',')
429 revs_ids = action_params.split(',')
430 deleted = user_log.repository is None
430 deleted = user_log.repository is None
431 if deleted:
431 if deleted:
432 return ','.join(revs_ids)
432 return ','.join(revs_ids)
433
433
434 repo_name = user_log.repository.repo_name
434 repo_name = user_log.repository.repo_name
435
435
436 repo = user_log.repository.scm_instance
436 repo = user_log.repository.scm_instance
437
437
438 message = lambda rev: rev.message
438 message = lambda rev: rev.message
439 lnk = lambda rev, repo_name: (
439 lnk = lambda rev, repo_name: (
440 link_to('r%s:%s' % (rev.revision, rev.short_id),
440 link_to('r%s:%s' % (rev.revision, rev.short_id),
441 url('changeset_home', repo_name=repo_name,
441 url('changeset_home', repo_name=repo_name,
442 revision=rev.raw_id),
442 revision=rev.raw_id),
443 title=tooltip(message(rev)), class_='tooltip')
443 title=tooltip(message(rev)), class_='tooltip')
444 )
444 )
445 # get only max revs_top_limit of changeset for performance/ui reasons
445 # get only max revs_top_limit of changeset for performance/ui reasons
446 revs = [
446 revs = [
447 x for x in repo.get_changesets(revs_ids[0],
447 x for x in repo.get_changesets(revs_ids[0],
448 revs_ids[:revs_top_limit][-1])
448 revs_ids[:revs_top_limit][-1])
449 ]
449 ]
450
450
451 cs_links = []
451 cs_links = []
452 cs_links.append(" " + ', '.join(
452 cs_links.append(" " + ', '.join(
453 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
453 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
454 )
454 )
455 )
455 )
456
456
457 compare_view = (
457 compare_view = (
458 ' <div class="compare_view tooltip" title="%s">'
458 ' <div class="compare_view tooltip" title="%s">'
459 '<a href="%s">%s</a> </div>' % (
459 '<a href="%s">%s</a> </div>' % (
460 _('Show all combined changesets %s->%s') % (
460 _('Show all combined changesets %s->%s') % (
461 revs_ids[0], revs_ids[-1]
461 revs_ids[0], revs_ids[-1]
462 ),
462 ),
463 url('changeset_home', repo_name=repo_name,
463 url('changeset_home', repo_name=repo_name,
464 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
464 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
465 ),
465 ),
466 _('compare view')
466 _('compare view')
467 )
467 )
468 )
468 )
469
469
470 # if we have exactly one more than normally displayed
470 # if we have exactly one more than normally displayed
471 # just display it, takes less space than displaying
471 # just display it, takes less space than displaying
472 # "and 1 more revisions"
472 # "and 1 more revisions"
473 if len(revs_ids) == revs_limit + 1:
473 if len(revs_ids) == revs_limit + 1:
474 rev = revs[revs_limit]
474 rev = revs[revs_limit]
475 cs_links.append(", " + lnk(rev, repo_name))
475 cs_links.append(", " + lnk(rev, repo_name))
476
476
477 # hidden-by-default ones
477 # hidden-by-default ones
478 if len(revs_ids) > revs_limit + 1:
478 if len(revs_ids) > revs_limit + 1:
479 uniq_id = revs_ids[0]
479 uniq_id = revs_ids[0]
480 html_tmpl = (
480 html_tmpl = (
481 '<span> %s <a class="show_more" id="_%s" '
481 '<span> %s <a class="show_more" id="_%s" '
482 'href="#more">%s</a> %s</span>'
482 'href="#more">%s</a> %s</span>'
483 )
483 )
484 if not feed:
484 if not feed:
485 cs_links.append(html_tmpl % (
485 cs_links.append(html_tmpl % (
486 _('and'),
486 _('and'),
487 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
487 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
488 _('revisions')
488 _('revisions')
489 )
489 )
490 )
490 )
491
491
492 if not feed:
492 if not feed:
493 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
493 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
494 else:
494 else:
495 html_tmpl = '<span id="%s"> %s </span>'
495 html_tmpl = '<span id="%s"> %s </span>'
496
496
497 morelinks = ', '.join(
497 morelinks = ', '.join(
498 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
498 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
499 )
499 )
500
500
501 if len(revs_ids) > revs_top_limit:
501 if len(revs_ids) > revs_top_limit:
502 morelinks += ', ...'
502 morelinks += ', ...'
503
503
504 cs_links.append(html_tmpl % (uniq_id, morelinks))
504 cs_links.append(html_tmpl % (uniq_id, morelinks))
505 if len(revs) > 1:
505 if len(revs) > 1:
506 cs_links.append(compare_view)
506 cs_links.append(compare_view)
507 return ''.join(cs_links)
507 return ''.join(cs_links)
508
508
509 def get_fork_name():
509 def get_fork_name():
510 repo_name = action_params
510 repo_name = action_params
511 return _('fork name ') + str(link_to(action_params, url('summary_home',
511 return _('fork name ') + str(link_to(action_params, url('summary_home',
512 repo_name=repo_name,)))
512 repo_name=repo_name,)))
513
513
514 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
514 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
515 'user_created_repo': (_('[created] repository'), None),
515 'user_created_repo': (_('[created] repository'), None),
516 'user_created_fork': (_('[created] repository as fork'), None),
516 'user_created_fork': (_('[created] repository as fork'), None),
517 'user_forked_repo': (_('[forked] repository'), get_fork_name),
517 'user_forked_repo': (_('[forked] repository'), get_fork_name),
518 'user_updated_repo': (_('[updated] repository'), None),
518 'user_updated_repo': (_('[updated] repository'), None),
519 'admin_deleted_repo': (_('[delete] repository'), None),
519 'admin_deleted_repo': (_('[delete] repository'), None),
520 'admin_created_repo': (_('[created] repository'), None),
520 'admin_created_repo': (_('[created] repository'), None),
521 'admin_forked_repo': (_('[forked] repository'), None),
521 'admin_forked_repo': (_('[forked] repository'), None),
522 'admin_updated_repo': (_('[updated] repository'), None),
522 'admin_updated_repo': (_('[updated] repository'), None),
523 'push': (_('[pushed] into'), get_cs_links),
523 'push': (_('[pushed] into'), get_cs_links),
524 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
524 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
525 'push_remote': (_('[pulled from remote] into'), get_cs_links),
525 'push_remote': (_('[pulled from remote] into'), get_cs_links),
526 'pull': (_('[pulled] from'), None),
526 'pull': (_('[pulled] from'), None),
527 'started_following_repo': (_('[started following] repository'), None),
527 'started_following_repo': (_('[started following] repository'), None),
528 'stopped_following_repo': (_('[stopped following] repository'), None),
528 'stopped_following_repo': (_('[stopped following] repository'), None),
529 }
529 }
530
530
531 action_str = action_map.get(action, action)
531 action_str = action_map.get(action, action)
532 if feed:
532 if feed:
533 action = action_str[0].replace('[', '').replace(']', '')
533 action = action_str[0].replace('[', '').replace(']', '')
534 else:
534 else:
535 action = action_str[0]\
535 action = action_str[0]\
536 .replace('[', '<span class="journal_highlight">')\
536 .replace('[', '<span class="journal_highlight">')\
537 .replace(']', '</span>')
537 .replace(']', '</span>')
538
538
539 action_params_func = lambda: ""
539 action_params_func = lambda: ""
540
540
541 if callable(action_str[1]):
541 if callable(action_str[1]):
542 action_params_func = action_str[1]
542 action_params_func = action_str[1]
543
543
544 return [literal(action), action_params_func]
544 return [literal(action), action_params_func]
545
545
546
546
547 def action_parser_icon(user_log):
547 def action_parser_icon(user_log):
548 action = user_log.action
548 action = user_log.action
549 action_params = None
549 action_params = None
550 x = action.split(':')
550 x = action.split(':')
551
551
552 if len(x) > 1:
552 if len(x) > 1:
553 action, action_params = x
553 action, action_params = x
554
554
555 tmpl = """<img src="%s%s" alt="%s"/>"""
555 tmpl = """<img src="%s%s" alt="%s"/>"""
556 map = {'user_deleted_repo':'database_delete.png',
556 map = {'user_deleted_repo':'database_delete.png',
557 'user_created_repo':'database_add.png',
557 'user_created_repo':'database_add.png',
558 'user_created_fork':'arrow_divide.png',
558 'user_created_fork':'arrow_divide.png',
559 'user_forked_repo':'arrow_divide.png',
559 'user_forked_repo':'arrow_divide.png',
560 'user_updated_repo':'database_edit.png',
560 'user_updated_repo':'database_edit.png',
561 'admin_deleted_repo':'database_delete.png',
561 'admin_deleted_repo':'database_delete.png',
562 'admin_created_repo':'database_add.png',
562 'admin_created_repo':'database_add.png',
563 'admin_forked_repo':'arrow_divide.png',
563 'admin_forked_repo':'arrow_divide.png',
564 'admin_updated_repo':'database_edit.png',
564 'admin_updated_repo':'database_edit.png',
565 'push':'script_add.png',
565 'push':'script_add.png',
566 'push_local':'script_edit.png',
566 'push_local':'script_edit.png',
567 'push_remote':'connect.png',
567 'push_remote':'connect.png',
568 'pull':'down_16.png',
568 'pull':'down_16.png',
569 'started_following_repo':'heart_add.png',
569 'started_following_repo':'heart_add.png',
570 'stopped_following_repo':'heart_delete.png',
570 'stopped_following_repo':'heart_delete.png',
571 }
571 }
572 return literal(tmpl % ((url('/images/icons/')),
572 return literal(tmpl % ((url('/images/icons/')),
573 map.get(action, action), action))
573 map.get(action, action), action))
574
574
575
575
576 #==============================================================================
576 #==============================================================================
577 # PERMS
577 # PERMS
578 #==============================================================================
578 #==============================================================================
579 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
579 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
580 HasRepoPermissionAny, HasRepoPermissionAll
580 HasRepoPermissionAny, HasRepoPermissionAll
581
581
582
582
583 #==============================================================================
583 #==============================================================================
584 # GRAVATAR URL
584 # GRAVATAR URL
585 #==============================================================================
585 #==============================================================================
586
586
587 def gravatar_url(email_address, size=30):
587 def gravatar_url(email_address, size=30):
588 if (not str2bool(config['app_conf'].get('use_gravatar')) or
588 if (not str2bool(config['app_conf'].get('use_gravatar')) or
589 not email_address or email_address == 'anonymous@rhodecode.org'):
589 not email_address or email_address == 'anonymous@rhodecode.org'):
590 f = lambda a, l: min(l, key=lambda x: abs(x - a))
590 f = lambda a, l: min(l, key=lambda x: abs(x - a))
591 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
591 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
592
592
593 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
593 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
594 default = 'identicon'
594 default = 'identicon'
595 baseurl_nossl = "http://www.gravatar.com/avatar/"
595 baseurl_nossl = "http://www.gravatar.com/avatar/"
596 baseurl_ssl = "https://secure.gravatar.com/avatar/"
596 baseurl_ssl = "https://secure.gravatar.com/avatar/"
597 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
597 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
598
598
599 if isinstance(email_address, unicode):
599 if isinstance(email_address, unicode):
600 #hashlib crashes on unicode items
600 #hashlib crashes on unicode items
601 email_address = safe_str(email_address)
601 email_address = safe_str(email_address)
602 # construct the url
602 # construct the url
603 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
603 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
604 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
604 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
605
605
606 return gravatar_url
606 return gravatar_url
607
607
608
608
609 #==============================================================================
609 #==============================================================================
610 # REPO PAGER, PAGER FOR REPOSITORY
610 # REPO PAGER, PAGER FOR REPOSITORY
611 #==============================================================================
611 #==============================================================================
612 class RepoPage(Page):
612 class RepoPage(Page):
613
613
614 def __init__(self, collection, page=1, items_per_page=20,
614 def __init__(self, collection, page=1, items_per_page=20,
615 item_count=None, url=None, **kwargs):
615 item_count=None, url=None, **kwargs):
616
616
617 """Create a "RepoPage" instance. special pager for paging
617 """Create a "RepoPage" instance. special pager for paging
618 repository
618 repository
619 """
619 """
620 self._url_generator = url
620 self._url_generator = url
621
621
622 # Safe the kwargs class-wide so they can be used in the pager() method
622 # Safe the kwargs class-wide so they can be used in the pager() method
623 self.kwargs = kwargs
623 self.kwargs = kwargs
624
624
625 # Save a reference to the collection
625 # Save a reference to the collection
626 self.original_collection = collection
626 self.original_collection = collection
627
627
628 self.collection = collection
628 self.collection = collection
629
629
630 # The self.page is the number of the current page.
630 # The self.page is the number of the current page.
631 # The first page has the number 1!
631 # The first page has the number 1!
632 try:
632 try:
633 self.page = int(page) # make it int() if we get it as a string
633 self.page = int(page) # make it int() if we get it as a string
634 except (ValueError, TypeError):
634 except (ValueError, TypeError):
635 self.page = 1
635 self.page = 1
636
636
637 self.items_per_page = items_per_page
637 self.items_per_page = items_per_page
638
638
639 # Unless the user tells us how many items the collections has
639 # Unless the user tells us how many items the collections has
640 # we calculate that ourselves.
640 # we calculate that ourselves.
641 if item_count is not None:
641 if item_count is not None:
642 self.item_count = item_count
642 self.item_count = item_count
643 else:
643 else:
644 self.item_count = len(self.collection)
644 self.item_count = len(self.collection)
645
645
646 # Compute the number of the first and last available page
646 # Compute the number of the first and last available page
647 if self.item_count > 0:
647 if self.item_count > 0:
648 self.first_page = 1
648 self.first_page = 1
649 self.page_count = int(math.ceil(float(self.item_count) /
649 self.page_count = int(math.ceil(float(self.item_count) /
650 self.items_per_page))
650 self.items_per_page))
651 self.last_page = self.first_page + self.page_count - 1
651 self.last_page = self.first_page + self.page_count - 1
652
652
653 # Make sure that the requested page number is the range of
653 # Make sure that the requested page number is the range of
654 # valid pages
654 # valid pages
655 if self.page > self.last_page:
655 if self.page > self.last_page:
656 self.page = self.last_page
656 self.page = self.last_page
657 elif self.page < self.first_page:
657 elif self.page < self.first_page:
658 self.page = self.first_page
658 self.page = self.first_page
659
659
660 # Note: the number of items on this page can be less than
660 # Note: the number of items on this page can be less than
661 # items_per_page if the last page is not full
661 # items_per_page if the last page is not full
662 self.first_item = max(0, (self.item_count) - (self.page *
662 self.first_item = max(0, (self.item_count) - (self.page *
663 items_per_page))
663 items_per_page))
664 self.last_item = ((self.item_count - 1) - items_per_page *
664 self.last_item = ((self.item_count - 1) - items_per_page *
665 (self.page - 1))
665 (self.page - 1))
666
666
667 self.items = list(self.collection[self.first_item:self.last_item + 1])
667 self.items = list(self.collection[self.first_item:self.last_item + 1])
668
668
669 # Links to previous and next page
669 # Links to previous and next page
670 if self.page > self.first_page:
670 if self.page > self.first_page:
671 self.previous_page = self.page - 1
671 self.previous_page = self.page - 1
672 else:
672 else:
673 self.previous_page = None
673 self.previous_page = None
674
674
675 if self.page < self.last_page:
675 if self.page < self.last_page:
676 self.next_page = self.page + 1
676 self.next_page = self.page + 1
677 else:
677 else:
678 self.next_page = None
678 self.next_page = None
679
679
680 # No items available
680 # No items available
681 else:
681 else:
682 self.first_page = None
682 self.first_page = None
683 self.page_count = 0
683 self.page_count = 0
684 self.last_page = None
684 self.last_page = None
685 self.first_item = None
685 self.first_item = None
686 self.last_item = None
686 self.last_item = None
687 self.previous_page = None
687 self.previous_page = None
688 self.next_page = None
688 self.next_page = None
689 self.items = []
689 self.items = []
690
690
691 # This is a subclass of the 'list' type. Initialise the list now.
691 # This is a subclass of the 'list' type. Initialise the list now.
692 list.__init__(self, reversed(self.items))
692 list.__init__(self, reversed(self.items))
693
693
694
694
695 def changed_tooltip(nodes):
695 def changed_tooltip(nodes):
696 """
696 """
697 Generates a html string for changed nodes in changeset page.
697 Generates a html string for changed nodes in changeset page.
698 It limits the output to 30 entries
698 It limits the output to 30 entries
699
699
700 :param nodes: LazyNodesGenerator
700 :param nodes: LazyNodesGenerator
701 """
701 """
702 if nodes:
702 if nodes:
703 pref = ': <br/> '
703 pref = ': <br/> '
704 suf = ''
704 suf = ''
705 if len(nodes) > 30:
705 if len(nodes) > 30:
706 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
706 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
707 return literal(pref + '<br/> '.join([safe_unicode(x.path)
707 return literal(pref + '<br/> '.join([safe_unicode(x.path)
708 for x in nodes[:30]]) + suf)
708 for x in nodes[:30]]) + suf)
709 else:
709 else:
710 return ': ' + _('No Files')
710 return ': ' + _('No Files')
711
711
712
712
713 def repo_link(groups_and_repos):
713 def repo_link(groups_and_repos):
714 """
714 """
715 Makes a breadcrumbs link to repo within a group
715 Makes a breadcrumbs link to repo within a group
716 joins &raquo; on each group to create a fancy link
716 joins &raquo; on each group to create a fancy link
717
717
718 ex::
718 ex::
719 group >> subgroup >> repo
719 group >> subgroup >> repo
720
720
721 :param groups_and_repos:
721 :param groups_and_repos:
722 """
722 """
723 groups, repo_name = groups_and_repos
723 groups, repo_name = groups_and_repos
724
724
725 if not groups:
725 if not groups:
726 return repo_name
726 return repo_name
727 else:
727 else:
728 def make_link(group):
728 def make_link(group):
729 return link_to(group.name, url('repos_group_home',
729 return link_to(group.name, url('repos_group_home',
730 group_name=group.group_name))
730 group_name=group.group_name))
731 return literal(' &raquo; '.join(map(make_link, groups)) + \
731 return literal(' &raquo; '.join(map(make_link, groups)) + \
732 " &raquo; " + repo_name)
732 " &raquo; " + repo_name)
733
733
734
734
735 def fancy_file_stats(stats):
735 def fancy_file_stats(stats):
736 """
736 """
737 Displays a fancy two colored bar for number of added/deleted
737 Displays a fancy two colored bar for number of added/deleted
738 lines of code on file
738 lines of code on file
739
739
740 :param stats: two element list of added/deleted lines of code
740 :param stats: two element list of added/deleted lines of code
741 """
741 """
742
742
743 a, d, t = stats[0], stats[1], stats[0] + stats[1]
743 a, d, t = stats[0], stats[1], stats[0] + stats[1]
744 width = 100
744 width = 100
745 unit = float(width) / (t or 1)
745 unit = float(width) / (t or 1)
746
746
747 # needs > 9% of width to be visible or 0 to be hidden
747 # needs > 9% of width to be visible or 0 to be hidden
748 a_p = max(9, unit * a) if a > 0 else 0
748 a_p = max(9, unit * a) if a > 0 else 0
749 d_p = max(9, unit * d) if d > 0 else 0
749 d_p = max(9, unit * d) if d > 0 else 0
750 p_sum = a_p + d_p
750 p_sum = a_p + d_p
751
751
752 if p_sum > width:
752 if p_sum > width:
753 #adjust the percentage to be == 100% since we adjusted to 9
753 #adjust the percentage to be == 100% since we adjusted to 9
754 if a_p > d_p:
754 if a_p > d_p:
755 a_p = a_p - (p_sum - width)
755 a_p = a_p - (p_sum - width)
756 else:
756 else:
757 d_p = d_p - (p_sum - width)
757 d_p = d_p - (p_sum - width)
758
758
759 a_v = a if a > 0 else ''
759 a_v = a if a > 0 else ''
760 d_v = d if d > 0 else ''
760 d_v = d if d > 0 else ''
761
761
762 def cgen(l_type):
762 def cgen(l_type):
763 mapping = {'tr': 'top-right-rounded-corner-mid',
763 mapping = {'tr': 'top-right-rounded-corner-mid',
764 'tl': 'top-left-rounded-corner-mid',
764 'tl': 'top-left-rounded-corner-mid',
765 'br': 'bottom-right-rounded-corner-mid',
765 'br': 'bottom-right-rounded-corner-mid',
766 'bl': 'bottom-left-rounded-corner-mid'}
766 'bl': 'bottom-left-rounded-corner-mid'}
767 map_getter = lambda x: mapping[x]
767 map_getter = lambda x: mapping[x]
768
768
769 if l_type == 'a' and d_v:
769 if l_type == 'a' and d_v:
770 #case when added and deleted are present
770 #case when added and deleted are present
771 return ' '.join(map(map_getter, ['tl', 'bl']))
771 return ' '.join(map(map_getter, ['tl', 'bl']))
772
772
773 if l_type == 'a' and not d_v:
773 if l_type == 'a' and not d_v:
774 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
774 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
775
775
776 if l_type == 'd' and a_v:
776 if l_type == 'd' and a_v:
777 return ' '.join(map(map_getter, ['tr', 'br']))
777 return ' '.join(map(map_getter, ['tr', 'br']))
778
778
779 if l_type == 'd' and not a_v:
779 if l_type == 'd' and not a_v:
780 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
780 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
781
781
782 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
782 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
783 cgen('a'), a_p, a_v
783 cgen('a'), a_p, a_v
784 )
784 )
785 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
785 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
786 cgen('d'), d_p, d_v
786 cgen('d'), d_p, d_v
787 )
787 )
788 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
788 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
789
789
790
790
791 def urlify_text(text_):
791 def urlify_text(text_):
792 import re
792 import re
793
793
794 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
794 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
795 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
795 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
796
796
797 def url_func(match_obj):
797 def url_func(match_obj):
798 url_full = match_obj.groups()[0]
798 url_full = match_obj.groups()[0]
799 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
799 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
800
800
801 return literal(url_pat.sub(url_func, text_))
801 return literal(url_pat.sub(url_func, text_))
802
802
803
803
804 def urlify_changesets(text_, repository):
804 def urlify_changesets(text_, repository):
805 """
805 """
806 Extract revision ids from changeset and make link from them
806 Extract revision ids from changeset and make link from them
807
807
808 :param text_:
808 :param text_:
809 :param repository:
809 :param repository:
810 """
810 """
811 import re
811 import re
812 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
812 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
813
813
814 def url_func(match_obj):
814 def url_func(match_obj):
815 rev = match_obj.groups()[0]
815 rev = match_obj.groups()[0]
816 pref = ''
816 pref = ''
817 if match_obj.group().startswith(' '):
817 if match_obj.group().startswith(' '):
818 pref = ' '
818 pref = ' '
819 tmpl = (
819 tmpl = (
820 '%(pref)s<a class="%(cls)s" href="%(url)s">'
820 '%(pref)s<a class="%(cls)s" href="%(url)s">'
821 '%(rev)s'
821 '%(rev)s'
822 '</a>'
822 '</a>'
823 )
823 )
824 return tmpl % {
824 return tmpl % {
825 'pref': pref,
825 'pref': pref,
826 'cls': 'revision-link',
826 'cls': 'revision-link',
827 'url': url('changeset_home', repo_name=repository, revision=rev),
827 'url': url('changeset_home', repo_name=repository, revision=rev),
828 'rev': rev,
828 'rev': rev,
829 }
829 }
830
830
831 newtext = URL_PAT.sub(url_func, text_)
831 newtext = URL_PAT.sub(url_func, text_)
832
832
833 return newtext
833 return newtext
834
834
835
835
836 def urlify_commit(text_, repository=None, link_=None):
836 def urlify_commit(text_, repository=None, link_=None):
837 """
837 """
838 Parses given text message and makes proper links.
838 Parses given text message and makes proper links.
839 issues are linked to given issue-server, and rest is a changeset link
839 issues are linked to given issue-server, and rest is a changeset link
840 if link_ is given, in other case it's a plain text
840 if link_ is given, in other case it's a plain text
841
841
842 :param text_:
842 :param text_:
843 :param repository:
843 :param repository:
844 :param link_: changeset link
844 :param link_: changeset link
845 """
845 """
846 import re
846 import re
847 import traceback
847 import traceback
848
848
849 def escaper(string):
849 def escaper(string):
850 return string.replace('<', '&lt;').replace('>', '&gt;')
850 return string.replace('<', '&lt;').replace('>', '&gt;')
851
851
852 def linkify_others(t, l):
852 def linkify_others(t, l):
853 urls = re.compile(r'(\<a.*?\<\/a\>)',)
853 urls = re.compile(r'(\<a.*?\<\/a\>)',)
854 links = []
854 links = []
855 for e in urls.split(t):
855 for e in urls.split(t):
856 if not urls.match(e):
856 if not urls.match(e):
857 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
857 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
858 else:
858 else:
859 links.append(e)
859 links.append(e)
860
860
861 return ''.join(links)
861 return ''.join(links)
862
862
863
863
864 # urlify changesets - extrac revisions and make link out of them
864 # urlify changesets - extrac revisions and make link out of them
865 text_ = urlify_changesets(escaper(text_), repository)
865 text_ = urlify_changesets(escaper(text_), repository)
866
866
867 try:
867 try:
868 conf = config['app_conf']
868 conf = config['app_conf']
869
869
870 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
870 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
871
871
872 if URL_PAT:
872 if URL_PAT:
873 ISSUE_SERVER_LNK = conf.get('issue_server_link')
873 ISSUE_SERVER_LNK = conf.get('issue_server_link')
874 ISSUE_PREFIX = conf.get('issue_prefix')
874 ISSUE_PREFIX = conf.get('issue_prefix')
875
875
876 def url_func(match_obj):
876 def url_func(match_obj):
877 pref = ''
877 pref = ''
878 if match_obj.group().startswith(' '):
878 if match_obj.group().startswith(' '):
879 pref = ' '
879 pref = ' '
880
880
881 issue_id = ''.join(match_obj.groups())
881 issue_id = ''.join(match_obj.groups())
882 tmpl = (
882 tmpl = (
883 '%(pref)s<a class="%(cls)s" href="%(url)s">'
883 '%(pref)s<a class="%(cls)s" href="%(url)s">'
884 '%(issue-prefix)s%(id-repr)s'
884 '%(issue-prefix)s%(id-repr)s'
885 '</a>'
885 '</a>'
886 )
886 )
887 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
887 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
888 if repository:
888 if repository:
889 url = url.replace('{repo}', repository)
889 url = url.replace('{repo}', repository)
890
890
891 return tmpl % {
891 return tmpl % {
892 'pref': pref,
892 'pref': pref,
893 'cls': 'issue-tracker-link',
893 'cls': 'issue-tracker-link',
894 'url': url,
894 'url': url,
895 'id-repr': issue_id,
895 'id-repr': issue_id,
896 'issue-prefix': ISSUE_PREFIX,
896 'issue-prefix': ISSUE_PREFIX,
897 'serv': ISSUE_SERVER_LNK,
897 'serv': ISSUE_SERVER_LNK,
898 }
898 }
899
899
900 newtext = URL_PAT.sub(url_func, text_)
900 newtext = URL_PAT.sub(url_func, text_)
901
901
902 if link_:
902 if link_:
903 # wrap not links into final link => link_
903 # wrap not links into final link => link_
904 newtext = linkify_others(newtext, link_)
904 newtext = linkify_others(newtext, link_)
905
905
906 return literal(newtext)
906 return literal(newtext)
907 except:
907 except:
908 log.error(traceback.format_exc())
908 log.error(traceback.format_exc())
909 pass
909 pass
910
910
911 return text_
911 return text_
912
912
913
913
914 def rst(source):
914 def rst(source):
915 return literal('<div class="rst-block">%s</div>' %
915 return literal('<div class="rst-block">%s</div>' %
916 MarkupRenderer.rst(source))
916 MarkupRenderer.rst(source))
917
917
918
918
919 def rst_w_mentions(source):
919 def rst_w_mentions(source):
920 """
920 """
921 Wrapped rst renderer with @mention highlighting
921 Wrapped rst renderer with @mention highlighting
922
922
923 :param source:
923 :param source:
924 """
924 """
925 return literal('<div class="rst-block">%s</div>' %
925 return literal('<div class="rst-block">%s</div>' %
926 MarkupRenderer.rst_with_mentions(source))
926 MarkupRenderer.rst_with_mentions(source))
General Comments 0
You need to be logged in to leave comments. Login now