##// END OF EJS Templates
fixed some unicode problems with waitress...
marcink -
r2100:f0649c7c beta
parent child Browse files
Show More
@@ -1,809 +1,814 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 import str2bool, safe_unicode
46 from rhodecode.lib 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,
525 self.user)
525 self.user)
526
526
527 if self.check_permissions():
527 if self.check_permissions():
528 log.debug('Permission granted for %s %s' % (cls, self.user))
528 log.debug('Permission granted for %s %s' % (cls, self.user))
529 return func(*fargs, **fkwargs)
529 return func(*fargs, **fkwargs)
530
530
531 else:
531 else:
532 log.debug('Permission denied for %s %s' % (cls, self.user))
532 log.debug('Permission denied for %s %s' % (cls, self.user))
533 anonymous = self.user.username == 'default'
533 anonymous = self.user.username == 'default'
534
534
535 if anonymous:
535 if anonymous:
536 p = url.current()
536 p = url.current()
537
537
538 import rhodecode.lib.helpers as h
538 import rhodecode.lib.helpers as h
539 h.flash(_('You need to be a signed in to '
539 h.flash(_('You need to be a signed in to '
540 'view this page'),
540 'view this page'),
541 category='warning')
541 category='warning')
542 return redirect(url('login_home', came_from=p))
542 return redirect(url('login_home', came_from=p))
543
543
544 else:
544 else:
545 # redirect with forbidden ret code
545 # redirect with forbidden ret code
546 return abort(403)
546 return abort(403)
547
547
548 def check_permissions(self):
548 def check_permissions(self):
549 """Dummy function for overriding"""
549 """Dummy function for overriding"""
550 raise Exception('You have to write this function in child class')
550 raise Exception('You have to write this function in child class')
551
551
552
552
553 class HasPermissionAllDecorator(PermsDecorator):
553 class HasPermissionAllDecorator(PermsDecorator):
554 """
554 """
555 Checks for access permission for all given predicates. All of them
555 Checks for access permission for all given predicates. All of them
556 have to be meet in order to fulfill the request
556 have to be meet in order to fulfill the request
557 """
557 """
558
558
559 def check_permissions(self):
559 def check_permissions(self):
560 if self.required_perms.issubset(self.user_perms.get('global')):
560 if self.required_perms.issubset(self.user_perms.get('global')):
561 return True
561 return True
562 return False
562 return False
563
563
564
564
565 class HasPermissionAnyDecorator(PermsDecorator):
565 class HasPermissionAnyDecorator(PermsDecorator):
566 """
566 """
567 Checks for access permission for any of given predicates. In order to
567 Checks for access permission for any of given predicates. In order to
568 fulfill the request any of predicates must be meet
568 fulfill the request any of predicates must be meet
569 """
569 """
570
570
571 def check_permissions(self):
571 def check_permissions(self):
572 if self.required_perms.intersection(self.user_perms.get('global')):
572 if self.required_perms.intersection(self.user_perms.get('global')):
573 return True
573 return True
574 return False
574 return False
575
575
576
576
577 class HasRepoPermissionAllDecorator(PermsDecorator):
577 class HasRepoPermissionAllDecorator(PermsDecorator):
578 """
578 """
579 Checks for access permission for all given predicates for specific
579 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
580 repository. All of them have to be meet in order to fulfill the request
581 """
581 """
582
582
583 def check_permissions(self):
583 def check_permissions(self):
584 repo_name = get_repo_slug(request)
584 repo_name = get_repo_slug(request)
585 try:
585 try:
586 user_perms = set([self.user_perms['repositories'][repo_name]])
586 user_perms = set([self.user_perms['repositories'][repo_name]])
587 except KeyError:
587 except KeyError:
588 return False
588 return False
589 if self.required_perms.issubset(user_perms):
589 if self.required_perms.issubset(user_perms):
590 return True
590 return True
591 return False
591 return False
592
592
593
593
594 class HasRepoPermissionAnyDecorator(PermsDecorator):
594 class HasRepoPermissionAnyDecorator(PermsDecorator):
595 """
595 """
596 Checks for access permission for any of given predicates for specific
596 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
597 repository. In order to fulfill the request any of predicates must be meet
598 """
598 """
599
599
600 def check_permissions(self):
600 def check_permissions(self):
601 repo_name = get_repo_slug(request)
601 repo_name = get_repo_slug(request)
602
602
603 try:
603 try:
604 user_perms = set([self.user_perms['repositories'][repo_name]])
604 user_perms = set([self.user_perms['repositories'][repo_name]])
605 except KeyError:
605 except KeyError:
606 return False
606 return False
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 in not defined" % perm)
658 raise Exception("'%s' permission in 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 = ''
661 self.granted_for = ''
662 self.repo_name = None
662 self.repo_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 log.debug('checking %s %s %s', self.__class__.__name__,
667 self.required_perms, user)
667 self.required_perms, user)
668 if not user:
668 if not user:
669 log.debug('Empty request user')
669 log.debug('Empty request user')
670 return False
670 return False
671 self.user_perms = user.permissions
671 self.user_perms = user.permissions
672 self.granted_for = user
672 self.granted_for = user
673
673
674 if self.check_permissions():
674 if self.check_permissions():
675 log.debug('Permission granted %s @ %s', self.granted_for,
675 log.debug('Permission granted %s @ %s', self.granted_for,
676 check_Location or 'unspecified location')
676 check_Location or 'unspecified location')
677 return True
677 return True
678
678
679 else:
679 else:
680 log.debug('Permission denied for %s @ %s', self.granted_for,
680 log.debug('Permission denied for %s @ %s', self.granted_for,
681 check_Location or 'unspecified location')
681 check_Location or 'unspecified location')
682 return False
682 return False
683
683
684 def check_permissions(self):
684 def check_permissions(self):
685 """Dummy function for overriding"""
685 """Dummy function for overriding"""
686 raise Exception('You have to write this function in child class')
686 raise Exception('You have to write this function in child class')
687
687
688
688
689 class HasPermissionAll(PermsFunction):
689 class HasPermissionAll(PermsFunction):
690 def check_permissions(self):
690 def check_permissions(self):
691 if self.required_perms.issubset(self.user_perms.get('global')):
691 if self.required_perms.issubset(self.user_perms.get('global')):
692 return True
692 return True
693 return False
693 return False
694
694
695
695
696 class HasPermissionAny(PermsFunction):
696 class HasPermissionAny(PermsFunction):
697 def check_permissions(self):
697 def check_permissions(self):
698 if self.required_perms.intersection(self.user_perms.get('global')):
698 if self.required_perms.intersection(self.user_perms.get('global')):
699 return True
699 return True
700 return False
700 return False
701
701
702
702
703 class HasRepoPermissionAll(PermsFunction):
703 class HasRepoPermissionAll(PermsFunction):
704
704
705 def __call__(self, repo_name=None, check_Location=''):
705 def __call__(self, repo_name=None, check_Location=''):
706 self.repo_name = repo_name
706 self.repo_name = repo_name
707 return super(HasRepoPermissionAll, self).__call__(check_Location)
707 return super(HasRepoPermissionAll, self).__call__(check_Location)
708
708
709 def check_permissions(self):
709 def check_permissions(self):
710 if not self.repo_name:
710 if not self.repo_name:
711 self.repo_name = get_repo_slug(request)
711 self.repo_name = get_repo_slug(request)
712
712
713 try:
713 try:
714 self.user_perms = set(
714 self.user_perms = set(
715 [self.user_perms['repositories'][self.repo_name]]
715 [self.user_perms['repositories'][self.repo_name]]
716 )
716 )
717 except KeyError:
717 except KeyError:
718 return False
718 return False
719 self.granted_for = self.repo_name
719 self.granted_for = self.repo_name
720 if self.required_perms.issubset(self.user_perms):
720 if self.required_perms.issubset(self.user_perms):
721 return True
721 return True
722 return False
722 return False
723
723
724
724
725 class HasRepoPermissionAny(PermsFunction):
725 class HasRepoPermissionAny(PermsFunction):
726
726
727 def __call__(self, repo_name=None, check_Location=''):
727 def __call__(self, repo_name=None, check_Location=''):
728 self.repo_name = repo_name
728 self.repo_name = repo_name
729 return super(HasRepoPermissionAny, self).__call__(check_Location)
729 return super(HasRepoPermissionAny, self).__call__(check_Location)
730
730
731 def check_permissions(self):
731 def check_permissions(self):
732 if not self.repo_name:
732 if not self.repo_name:
733 self.repo_name = get_repo_slug(request)
733 self.repo_name = get_repo_slug(request)
734
734
735 try:
735 try:
736 self.user_perms = set(
736 self.user_perms = set(
737 [self.user_perms['repositories'][self.repo_name]]
737 [self.user_perms['repositories'][self.repo_name]]
738 )
738 )
739 except KeyError:
739 except KeyError:
740 return False
740 return False
741 self.granted_for = self.repo_name
741 self.granted_for = self.repo_name
742 if self.required_perms.intersection(self.user_perms):
742 if self.required_perms.intersection(self.user_perms):
743 return True
743 return True
744 return False
744 return False
745
745
746
746
747 class HasReposGroupPermissionAny(PermsFunction):
747 class HasReposGroupPermissionAny(PermsFunction):
748 def __call__(self, group_name=None, check_Location=''):
748 def __call__(self, group_name=None, check_Location=''):
749 self.group_name = group_name
749 self.group_name = group_name
750 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
750 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
751
751
752 def check_permissions(self):
752 def check_permissions(self):
753 try:
753 try:
754 self.user_perms = set(
754 self.user_perms = set(
755 [self.user_perms['repositories_groups'][self.group_name]]
755 [self.user_perms['repositories_groups'][self.group_name]]
756 )
756 )
757 except KeyError:
757 except KeyError:
758 return False
758 return False
759 self.granted_for = self.repo_name
759 self.granted_for = self.repo_name
760 if self.required_perms.intersection(self.user_perms):
760 if self.required_perms.intersection(self.user_perms):
761 return True
761 return True
762 return False
762 return False
763
763
764
764
765 class HasReposGroupPermissionAll(PermsFunction):
765 class HasReposGroupPermissionAll(PermsFunction):
766 def __call__(self, group_name=None, check_Location=''):
766 def __call__(self, group_name=None, check_Location=''):
767 self.group_name = group_name
767 self.group_name = group_name
768 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
768 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
769
769
770 def check_permissions(self):
770 def check_permissions(self):
771 try:
771 try:
772 self.user_perms = set(
772 self.user_perms = set(
773 [self.user_perms['repositories_groups'][self.group_name]]
773 [self.user_perms['repositories_groups'][self.group_name]]
774 )
774 )
775 except KeyError:
775 except KeyError:
776 return False
776 return False
777 self.granted_for = self.repo_name
777 self.granted_for = self.repo_name
778 if self.required_perms.issubset(self.user_perms):
778 if self.required_perms.issubset(self.user_perms):
779 return True
779 return True
780 return False
780 return False
781
781
782
782
783 #==============================================================================
783 #==============================================================================
784 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
784 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
785 #==============================================================================
785 #==============================================================================
786 class HasPermissionAnyMiddleware(object):
786 class HasPermissionAnyMiddleware(object):
787 def __init__(self, *perms):
787 def __init__(self, *perms):
788 self.required_perms = set(perms)
788 self.required_perms = set(perms)
789
789
790 def __call__(self, user, repo_name):
790 def __call__(self, user, repo_name):
791 # repo_name MUST be unicode, since we handle keys in permission
792 # dict by unicode
793 repo_name = safe_unicode(repo_name)
791 usr = AuthUser(user.user_id)
794 usr = AuthUser(user.user_id)
792 try:
795 try:
793 self.user_perms = set([usr.permissions['repositories'][repo_name]])
796 self.user_perms = set([usr.permissions['repositories'][repo_name]])
794 except:
797 except Exception:
798 log.error('Exception while accessing permissions %s' %
799 traceback.format_exc())
795 self.user_perms = set()
800 self.user_perms = set()
796 self.granted_for = ''
801 self.granted_for = ''
797 self.username = user.username
802 self.username = user.username
798 self.repo_name = repo_name
803 self.repo_name = repo_name
799 return self.check_permissions()
804 return self.check_permissions()
800
805
801 def check_permissions(self):
806 def check_permissions(self):
802 log.debug('checking mercurial protocol '
807 log.debug('checking mercurial protocol '
803 'permissions %s for user:%s repository:%s', self.user_perms,
808 'permissions %s for user:%s repository:%s', self.user_perms,
804 self.username, self.repo_name)
809 self.username, self.repo_name)
805 if self.required_perms.intersection(self.user_perms):
810 if self.required_perms.intersection(self.user_perms):
806 log.debug('permission granted')
811 log.debug('permission granted')
807 return True
812 return True
808 log.debug('permission denied')
813 log.debug('permission denied')
809 return False
814 return False
@@ -1,250 +1,251 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import re
28 import re
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from dulwich import server as dulserver
32 from dulwich import server as dulserver
33
33
34
34
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36
36
37 def handle(self):
37 def handle(self):
38 write = lambda x: self.proto.write_sideband(1, x)
38 write = lambda x: self.proto.write_sideband(1, x)
39
39
40 graph_walker = dulserver.ProtocolGraphWalker(self,
40 graph_walker = dulserver.ProtocolGraphWalker(self,
41 self.repo.object_store,
41 self.repo.object_store,
42 self.repo.get_peeled)
42 self.repo.get_peeled)
43 objects_iter = self.repo.fetch_objects(
43 objects_iter = self.repo.fetch_objects(
44 graph_walker.determine_wants, graph_walker, self.progress,
44 graph_walker.determine_wants, graph_walker, self.progress,
45 get_tagged=self.get_tagged)
45 get_tagged=self.get_tagged)
46
46
47 # Do they want any objects?
47 # Do they want any objects?
48 if objects_iter is None or len(objects_iter) == 0:
48 if objects_iter is None or len(objects_iter) == 0:
49 return
49 return
50
50
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
53 objects_iter, len(objects_iter))
53 objects_iter, len(objects_iter))
54 messages = []
54 messages = []
55 messages.append('thank you for using rhodecode')
55 messages.append('thank you for using rhodecode')
56
56
57 for msg in messages:
57 for msg in messages:
58 self.progress(msg + "\n")
58 self.progress(msg + "\n")
59 # we are done
59 # we are done
60 self.proto.write("0000")
60 self.proto.write("0000")
61
61
62 dulserver.DEFAULT_HANDLERS = {
62 dulserver.DEFAULT_HANDLERS = {
63 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-upload-pack': SimpleGitUploadPackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
65 }
65 }
66
66
67 from dulwich.repo import Repo
67 from dulwich.repo import Repo
68 from dulwich.web import HTTPGitApplication
68 from dulwich.web import HTTPGitApplication
69
69
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
71
72 from rhodecode.lib import safe_str
72 from rhodecode.lib import safe_str
73 from rhodecode.lib.base import BaseVCSController
73 from rhodecode.lib.base import BaseVCSController
74 from rhodecode.lib.auth import get_container_username
74 from rhodecode.lib.auth import get_container_username
75 from rhodecode.lib.utils import is_valid_repo
75 from rhodecode.lib.utils import is_valid_repo
76 from rhodecode.model.db import User
76 from rhodecode.model.db import User
77
77
78 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
79
79
80 log = logging.getLogger(__name__)
80 log = logging.getLogger(__name__)
81
81
82
82
83 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
83 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
84
84
85
85
86 def is_git(environ):
86 def is_git(environ):
87 path_info = environ['PATH_INFO']
87 path_info = environ['PATH_INFO']
88 isgit_path = GIT_PROTO_PAT.match(path_info)
88 isgit_path = GIT_PROTO_PAT.match(path_info)
89 log.debug('is a git path %s pathinfo : %s' % (isgit_path, path_info))
89 log.debug('pathinfo: %s detected as GIT %s' % (
90 path_info, isgit_path != None)
91 )
90 return isgit_path
92 return isgit_path
91
93
92
94
93 class SimpleGit(BaseVCSController):
95 class SimpleGit(BaseVCSController):
94
96
95 def _handle_request(self, environ, start_response):
97 def _handle_request(self, environ, start_response):
96
98
97 if not is_git(environ):
99 if not is_git(environ):
98 return self.application(environ, start_response)
100 return self.application(environ, start_response)
99
101
100 proxy_key = 'HTTP_X_REAL_IP'
102 proxy_key = 'HTTP_X_REAL_IP'
101 def_key = 'REMOTE_ADDR'
103 def_key = 'REMOTE_ADDR'
102 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
104 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
103 username = None
105 username = None
104 # skip passing error to error controller
106 # skip passing error to error controller
105 environ['pylons.status_code_redirect'] = True
107 environ['pylons.status_code_redirect'] = True
106
108
107 #======================================================================
109 #======================================================================
108 # EXTRACT REPOSITORY NAME FROM ENV
110 # EXTRACT REPOSITORY NAME FROM ENV
109 #======================================================================
111 #======================================================================
110 try:
112 try:
111 repo_name = self.__get_repository(environ)
113 repo_name = self.__get_repository(environ)
112 log.debug('Extracted repo name is %s' % repo_name)
114 log.debug('Extracted repo name is %s' % repo_name)
113 except:
115 except:
114 return HTTPInternalServerError()(environ, start_response)
116 return HTTPInternalServerError()(environ, start_response)
115
117
116 #======================================================================
118 #======================================================================
117 # GET ACTION PULL or PUSH
119 # GET ACTION PULL or PUSH
118 #======================================================================
120 #======================================================================
119 action = self.__get_action(environ)
121 action = self.__get_action(environ)
120
122
121 #======================================================================
123 #======================================================================
122 # CHECK ANONYMOUS PERMISSION
124 # CHECK ANONYMOUS PERMISSION
123 #======================================================================
125 #======================================================================
124
125 if action in ['pull', 'push']:
126 if action in ['pull', 'push']:
126 anonymous_user = self.__get_user('default')
127 anonymous_user = self.__get_user('default')
127 username = anonymous_user.username
128 username = anonymous_user.username
128 anonymous_perm = self._check_permission(action, anonymous_user,
129 anonymous_perm = self._check_permission(action, anonymous_user,
129 repo_name)
130 repo_name)
130
131
131 if anonymous_perm is not True or anonymous_user.active is False:
132 if anonymous_perm is not True or anonymous_user.active is False:
132 if anonymous_perm is not True:
133 if anonymous_perm is not True:
133 log.debug('Not enough credentials to access this '
134 log.debug('Not enough credentials to access this '
134 'repository as anonymous user')
135 'repository as anonymous user')
135 if anonymous_user.active is False:
136 if anonymous_user.active is False:
136 log.debug('Anonymous access is disabled, running '
137 log.debug('Anonymous access is disabled, running '
137 'authentication')
138 'authentication')
138 #==============================================================
139 #==============================================================
139 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
140 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
140 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
141 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
141 #==============================================================
142 #==============================================================
142
143
143 # Attempting to retrieve username from the container
144 # Attempting to retrieve username from the container
144 username = get_container_username(environ, self.config)
145 username = get_container_username(environ, self.config)
145
146
146 # If not authenticated by the container, running basic auth
147 # If not authenticated by the container, running basic auth
147 if not username:
148 if not username:
148 self.authenticate.realm = \
149 self.authenticate.realm = \
149 safe_str(self.config['rhodecode_realm'])
150 safe_str(self.config['rhodecode_realm'])
150 result = self.authenticate(environ)
151 result = self.authenticate(environ)
151 if isinstance(result, str):
152 if isinstance(result, str):
152 AUTH_TYPE.update(environ, 'basic')
153 AUTH_TYPE.update(environ, 'basic')
153 REMOTE_USER.update(environ, result)
154 REMOTE_USER.update(environ, result)
154 username = result
155 username = result
155 else:
156 else:
156 return result.wsgi_application(environ, start_response)
157 return result.wsgi_application(environ, start_response)
157
158
158 #==============================================================
159 #==============================================================
159 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
160 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
160 #==============================================================
161 #==============================================================
161 if action in ['pull', 'push']:
162 if action in ['pull', 'push']:
162 try:
163 try:
163 user = self.__get_user(username)
164 user = self.__get_user(username)
164 if user is None or not user.active:
165 if user is None or not user.active:
165 return HTTPForbidden()(environ, start_response)
166 return HTTPForbidden()(environ, start_response)
166 username = user.username
167 username = user.username
167 except:
168 except:
168 log.error(traceback.format_exc())
169 log.error(traceback.format_exc())
169 return HTTPInternalServerError()(environ,
170 return HTTPInternalServerError()(environ,
170 start_response)
171 start_response)
171
172
172 #check permissions for this repository
173 #check permissions for this repository
173 perm = self._check_permission(action, user, repo_name)
174 perm = self._check_permission(action, user, repo_name)
174 if perm is not True:
175 if perm is not True:
175 return HTTPForbidden()(environ, start_response)
176 return HTTPForbidden()(environ, start_response)
176
177
177 #===================================================================
178 #===================================================================
178 # GIT REQUEST HANDLING
179 # GIT REQUEST HANDLING
179 #===================================================================
180 #===================================================================
180 repo_path = safe_str(os.path.join(self.basepath, repo_name))
181 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
181 log.debug('Repository path is %s' % repo_path)
182 log.debug('Repository path is %s' % repo_path)
182
183
183 # quick check if that dir exists...
184 # quick check if that dir exists...
184 if is_valid_repo(repo_name, self.basepath) is False:
185 if is_valid_repo(repo_name, self.basepath) is False:
185 return HTTPNotFound()(environ, start_response)
186 return HTTPNotFound()(environ, start_response)
186
187
187 try:
188 try:
188 #invalidate cache on push
189 #invalidate cache on push
189 if action == 'push':
190 if action == 'push':
190 self._invalidate_cache(repo_name)
191 self._invalidate_cache(repo_name)
191 log.info('%s action on GIT repo "%s"' % (action, repo_name))
192 log.info('%s action on GIT repo "%s"' % (action, repo_name))
192 app = self.__make_app(repo_name, repo_path)
193 app = self.__make_app(repo_name, repo_path)
193 return app(environ, start_response)
194 return app(environ, start_response)
194 except Exception:
195 except Exception:
195 log.error(traceback.format_exc())
196 log.error(traceback.format_exc())
196 return HTTPInternalServerError()(environ, start_response)
197 return HTTPInternalServerError()(environ, start_response)
197
198
198 def __make_app(self, repo_name, repo_path):
199 def __make_app(self, repo_name, repo_path):
199 """
200 """
200 Make an wsgi application using dulserver
201 Make an wsgi application using dulserver
201
202
202 :param repo_name: name of the repository
203 :param repo_name: name of the repository
203 :param repo_path: full path to the repository
204 :param repo_path: full path to the repository
204 """
205 """
205 _d = {'/' + repo_name: Repo(repo_path)}
206 _d = {'/' + repo_name: Repo(repo_path)}
206 backend = dulserver.DictBackend(_d)
207 backend = dulserver.DictBackend(_d)
207 gitserve = HTTPGitApplication(backend)
208 gitserve = HTTPGitApplication(backend)
208
209
209 return gitserve
210 return gitserve
210
211
211 def __get_repository(self, environ):
212 def __get_repository(self, environ):
212 """
213 """
213 Get's repository name out of PATH_INFO header
214 Get's repository name out of PATH_INFO header
214
215
215 :param environ: environ where PATH_INFO is stored
216 :param environ: environ where PATH_INFO is stored
216 """
217 """
217 try:
218 try:
218 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
219 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
219 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
220 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
220 except:
221 except:
221 log.error(traceback.format_exc())
222 log.error(traceback.format_exc())
222 raise
223 raise
223
224
224 return repo_name
225 return repo_name
225
226
226 def __get_user(self, username):
227 def __get_user(self, username):
227 return User.get_by_username(username)
228 return User.get_by_username(username)
228
229
229 def __get_action(self, environ):
230 def __get_action(self, environ):
230 """
231 """
231 Maps git request commands into a pull or push command.
232 Maps git request commands into a pull or push command.
232
233
233 :param environ:
234 :param environ:
234 """
235 """
235 service = environ['QUERY_STRING'].split('=')
236 service = environ['QUERY_STRING'].split('=')
236
237
237 if len(service) > 1:
238 if len(service) > 1:
238 service_cmd = service[1]
239 service_cmd = service[1]
239 mapping = {
240 mapping = {
240 'git-receive-pack': 'push',
241 'git-receive-pack': 'push',
241 'git-upload-pack': 'pull',
242 'git-upload-pack': 'pull',
242 }
243 }
243 op = mapping[service_cmd]
244 op = mapping[service_cmd]
244 self._git_stored_op = op
245 self._git_stored_op = op
245 return op
246 return op
246 else:
247 else:
247 # try to fallback to stored variable as we don't know if the last
248 # try to fallback to stored variable as we don't know if the last
248 # operation is pull/push
249 # operation is pull/push
249 op = getattr(self, '_git_stored_op', 'pull')
250 op = getattr(self, '_git_stored_op', 'pull')
250 return op
251 return op
@@ -1,247 +1,258 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import urllib
30
31
31 from mercurial.error import RepoError
32 from mercurial.error import RepoError
32 from mercurial.hgweb import hgweb_mod
33 from mercurial.hgweb import hgweb_mod
33
34
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35
36
36 from rhodecode.lib import safe_str
37 from rhodecode.lib import safe_str
37 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
40 from rhodecode.model.db import User
41 from rhodecode.model.db import User
41
42
42 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
43
44
44 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
45
46
46
47
47 def is_mercurial(environ):
48 def is_mercurial(environ):
48 """Returns True if request's target is mercurial server - header
49 """
50 Returns True if request's target is mercurial server - header
49 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
50 """
52 """
51 http_accept = environ.get('HTTP_ACCEPT')
53 http_accept = environ.get('HTTP_ACCEPT')
54 path_info = environ['PATH_INFO']
52 if http_accept and http_accept.startswith('application/mercurial'):
55 if http_accept and http_accept.startswith('application/mercurial'):
53 return True
56 ishg_path = True
54 return False
57 else:
58 ishg_path = False
59
60 log.debug('pathinfo: %s detected as HG %s' % (
61 path_info, ishg_path)
62 )
63 return ishg_path
55
64
56
65
57 class SimpleHg(BaseVCSController):
66 class SimpleHg(BaseVCSController):
58
67
59 def _handle_request(self, environ, start_response):
68 def _handle_request(self, environ, start_response):
60 if not is_mercurial(environ):
69 if not is_mercurial(environ):
61 return self.application(environ, start_response)
70 return self.application(environ, start_response)
62
71
63 proxy_key = 'HTTP_X_REAL_IP'
72 proxy_key = 'HTTP_X_REAL_IP'
64 def_key = 'REMOTE_ADDR'
73 def_key = 'REMOTE_ADDR'
65 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
74 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
66
75
67 # skip passing error to error controller
76 # skip passing error to error controller
68 environ['pylons.status_code_redirect'] = True
77 environ['pylons.status_code_redirect'] = True
69
78
70 #======================================================================
79 #======================================================================
71 # EXTRACT REPOSITORY NAME FROM ENV
80 # EXTRACT REPOSITORY NAME FROM ENV
72 #======================================================================
81 #======================================================================
73 try:
82 try:
74 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
83 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
75 log.debug('Extracted repo name is %s' % repo_name)
84 log.debug('Extracted repo name is %s' % repo_name)
76 except:
85 except:
77 return HTTPInternalServerError()(environ, start_response)
86 return HTTPInternalServerError()(environ, start_response)
78
87
79 #======================================================================
88 #======================================================================
80 # GET ACTION PULL or PUSH
89 # GET ACTION PULL or PUSH
81 #======================================================================
90 #======================================================================
82 action = self.__get_action(environ)
91 action = self.__get_action(environ)
92
83 #======================================================================
93 #======================================================================
84 # CHECK ANONYMOUS PERMISSION
94 # CHECK ANONYMOUS PERMISSION
85 #======================================================================
95 #======================================================================
86 if action in ['pull', 'push']:
96 if action in ['pull', 'push']:
87 anonymous_user = self.__get_user('default')
97 anonymous_user = self.__get_user('default')
88
89 username = anonymous_user.username
98 username = anonymous_user.username
90 anonymous_perm = self._check_permission(action, anonymous_user,
99 anonymous_perm = self._check_permission(action, anonymous_user,
91 repo_name)
100 repo_name)
92
101
93 if anonymous_perm is not True or anonymous_user.active is False:
102 if anonymous_perm is not True or anonymous_user.active is False:
94 if anonymous_perm is not True:
103 if anonymous_perm is not True:
95 log.debug('Not enough credentials to access this '
104 log.debug('Not enough credentials to access this '
96 'repository as anonymous user')
105 'repository as anonymous user')
97 if anonymous_user.active is False:
106 if anonymous_user.active is False:
98 log.debug('Anonymous access is disabled, running '
107 log.debug('Anonymous access is disabled, running '
99 'authentication')
108 'authentication')
100 #==============================================================
109 #==============================================================
101 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
110 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
102 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
111 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
103 #==============================================================
112 #==============================================================
104
113
105 # Attempting to retrieve username from the container
114 # Attempting to retrieve username from the container
106 username = get_container_username(environ, self.config)
115 username = get_container_username(environ, self.config)
107
116
108 # If not authenticated by the container, running basic auth
117 # If not authenticated by the container, running basic auth
109 if not username:
118 if not username:
110 self.authenticate.realm = \
119 self.authenticate.realm = \
111 safe_str(self.config['rhodecode_realm'])
120 safe_str(self.config['rhodecode_realm'])
112 result = self.authenticate(environ)
121 result = self.authenticate(environ)
113 if isinstance(result, str):
122 if isinstance(result, str):
114 AUTH_TYPE.update(environ, 'basic')
123 AUTH_TYPE.update(environ, 'basic')
115 REMOTE_USER.update(environ, result)
124 REMOTE_USER.update(environ, result)
116 username = result
125 username = result
117 else:
126 else:
118 return result.wsgi_application(environ, start_response)
127 return result.wsgi_application(environ, start_response)
119
128
120 #==============================================================
129 #==============================================================
121 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
130 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
122 #==============================================================
131 #==============================================================
123 if action in ['pull', 'push']:
132 if action in ['pull', 'push']:
124 try:
133 try:
125 user = self.__get_user(username)
134 user = self.__get_user(username)
126 if user is None or not user.active:
135 if user is None or not user.active:
127 return HTTPForbidden()(environ, start_response)
136 return HTTPForbidden()(environ, start_response)
128 username = user.username
137 username = user.username
129 except:
138 except:
130 log.error(traceback.format_exc())
139 log.error(traceback.format_exc())
131 return HTTPInternalServerError()(environ,
140 return HTTPInternalServerError()(environ,
132 start_response)
141 start_response)
133
142
134 #check permissions for this repository
143 #check permissions for this repository
135 perm = self._check_permission(action, user,
144 perm = self._check_permission(action, user, repo_name)
136 repo_name)
137 if perm is not True:
145 if perm is not True:
138 return HTTPForbidden()(environ, start_response)
146 return HTTPForbidden()(environ, start_response)
139
147
140 extras = {'ip': ipaddr,
148 # extras are injected into mercurial UI object and later available
141 'username': username,
149 # in hg hooks executed by rhodecode
142 'action': action,
150 extras = {
143 'repository': repo_name}
151 'ip': ipaddr,
152 'username': username,
153 'action': action,
154 'repository': repo_name
155 }
144
156
145 #======================================================================
157 #======================================================================
146 # MERCURIAL REQUEST HANDLING
158 # MERCURIAL REQUEST HANDLING
147 #======================================================================
159 #======================================================================
148
160 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
149 repo_path = safe_str(os.path.join(self.basepath, repo_name))
150 log.debug('Repository path is %s' % repo_path)
161 log.debug('Repository path is %s' % repo_path)
151
162
152 baseui = make_ui('db')
163 baseui = make_ui('db')
153 self.__inject_extras(repo_path, baseui, extras)
164 self.__inject_extras(repo_path, baseui, extras)
154
165
155 # quick check if that dir exists...
166 # quick check if that dir exists...
156 if is_valid_repo(repo_name, self.basepath) is False:
167 if is_valid_repo(repo_name, self.basepath) is False:
157 return HTTPNotFound()(environ, start_response)
168 return HTTPNotFound()(environ, start_response)
158
169
159 try:
170 try:
160 # invalidate cache on push
171 # invalidate cache on push
161 if action == 'push':
172 if action == 'push':
162 self._invalidate_cache(repo_name)
173 self._invalidate_cache(repo_name)
163 log.info('%s action on HG repo "%s"' % (action, repo_name))
174 log.info('%s action on HG repo "%s"' % (action, repo_name))
164 app = self.__make_app(repo_path, baseui, extras)
175 app = self.__make_app(repo_path, baseui, extras)
165 return app(environ, start_response)
176 return app(environ, start_response)
166 except RepoError, e:
177 except RepoError, e:
167 if str(e).find('not found') != -1:
178 if str(e).find('not found') != -1:
168 return HTTPNotFound()(environ, start_response)
179 return HTTPNotFound()(environ, start_response)
169 except Exception:
180 except Exception:
170 log.error(traceback.format_exc())
181 log.error(traceback.format_exc())
171 return HTTPInternalServerError()(environ, start_response)
182 return HTTPInternalServerError()(environ, start_response)
172
183
173 def __make_app(self, repo_name, baseui, extras):
184 def __make_app(self, repo_name, baseui, extras):
174 """
185 """
175 Make an wsgi application using hgweb, and inject generated baseui
186 Make an wsgi application using hgweb, and inject generated baseui
176 instance, additionally inject some extras into ui object
187 instance, additionally inject some extras into ui object
177 """
188 """
178 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
179
190
180 def __get_repository(self, environ):
191 def __get_repository(self, environ):
181 """
192 """
182 Get's repository name out of PATH_INFO header
193 Get's repository name out of PATH_INFO header
183
194
184 :param environ: environ where PATH_INFO is stored
195 :param environ: environ where PATH_INFO is stored
185 """
196 """
186 try:
197 try:
187 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
198 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
188 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
199 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
189 if repo_name.endswith('/'):
200 if repo_name.endswith('/'):
190 repo_name = repo_name.rstrip('/')
201 repo_name = repo_name.rstrip('/')
191 except:
202 except:
192 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
193 raise
204 raise
194
205
195 return repo_name
206 return repo_name
196
207
197 def __get_user(self, username):
208 def __get_user(self, username):
198 return User.get_by_username(username)
209 return User.get_by_username(username)
199
210
200 def __get_action(self, environ):
211 def __get_action(self, environ):
201 """
212 """
202 Maps mercurial request commands into a clone,pull or push command.
213 Maps mercurial request commands into a clone,pull or push command.
203 This should always return a valid command string
214 This should always return a valid command string
204
215
205 :param environ:
216 :param environ:
206 """
217 """
207 mapping = {'changegroup': 'pull',
218 mapping = {'changegroup': 'pull',
208 'changegroupsubset': 'pull',
219 'changegroupsubset': 'pull',
209 'stream_out': 'pull',
220 'stream_out': 'pull',
210 'listkeys': 'pull',
221 'listkeys': 'pull',
211 'unbundle': 'push',
222 'unbundle': 'push',
212 'pushkey': 'push', }
223 'pushkey': 'push', }
213 for qry in environ['QUERY_STRING'].split('&'):
224 for qry in environ['QUERY_STRING'].split('&'):
214 if qry.startswith('cmd'):
225 if qry.startswith('cmd'):
215 cmd = qry.split('=')[-1]
226 cmd = qry.split('=')[-1]
216 if cmd in mapping:
227 if cmd in mapping:
217 return mapping[cmd]
228 return mapping[cmd]
218 else:
229 else:
219 return 'pull'
230 return 'pull'
220
231
221 def __inject_extras(self, repo_path, baseui, extras={}):
232 def __inject_extras(self, repo_path, baseui, extras={}):
222 """
233 """
223 Injects some extra params into baseui instance
234 Injects some extra params into baseui instance
224
235
225 also overwrites global settings with those takes from local hgrc file
236 also overwrites global settings with those takes from local hgrc file
226
237
227 :param baseui: baseui instance
238 :param baseui: baseui instance
228 :param extras: dict with extra params to put into baseui
239 :param extras: dict with extra params to put into baseui
229 """
240 """
230
241
231 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
242 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
232
243
233 # make our hgweb quiet so it doesn't print output
244 # make our hgweb quiet so it doesn't print output
234 baseui.setconfig('ui', 'quiet', 'true')
245 baseui.setconfig('ui', 'quiet', 'true')
235
246
236 #inject some additional parameters that will be available in ui
247 #inject some additional parameters that will be available in ui
237 #for hooks
248 #for hooks
238 for k, v in extras.items():
249 for k, v in extras.items():
239 baseui.setconfig('rhodecode_extras', k, v)
250 baseui.setconfig('rhodecode_extras', k, v)
240
251
241 repoui = make_ui('file', hgrc, False)
252 repoui = make_ui('file', hgrc, False)
242
253
243 if repoui:
254 if repoui:
244 #overwrite our ui instance with the section from hgrc file
255 #overwrite our ui instance with the section from hgrc file
245 for section in ui_sections:
256 for section in ui_sections:
246 for k, v in repoui.configitems(section):
257 for k, v in repoui.configitems(section):
247 baseui.setconfig(section, k, v)
258 baseui.setconfig(section, k, v)
@@ -1,629 +1,634 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 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 os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 from os.path import abspath
35 from os.path import abspath
36 from os.path import dirname as dn, join as jn
36 from os.path import dirname as dn, join as jn
37
37
38 from paste.script.command import Command, BadCommand
38 from paste.script.command import Command, BadCommand
39
39
40 from mercurial import ui, config
40 from mercurial import ui, config
41
41
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43
43
44 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs import get_backend
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.utils.helpers import get_scm
47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.exceptions import VCSError
49
49
50 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
51
51
52 from rhodecode.model import meta
52 from rhodecode.model import meta
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
55 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
56 from rhodecode.model.repos_group import ReposGroupModel
56 from rhodecode.model.repos_group import ReposGroupModel
57 from rhodecode.lib import safe_str, safe_unicode
57
58
58 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
59
60
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61
62
62
63
63 def recursive_replace(str_, replace=' '):
64 def recursive_replace(str_, replace=' '):
64 """Recursive replace of given sign to just one instance
65 """Recursive replace of given sign to just one instance
65
66
66 :param str_: given string
67 :param str_: given string
67 :param replace: char to find and replace multiple instances
68 :param replace: char to find and replace multiple instances
68
69
69 Examples::
70 Examples::
70 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
71 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
71 'Mighty-Mighty-Bo-sstones'
72 'Mighty-Mighty-Bo-sstones'
72 """
73 """
73
74
74 if str_.find(replace * 2) == -1:
75 if str_.find(replace * 2) == -1:
75 return str_
76 return str_
76 else:
77 else:
77 str_ = str_.replace(replace * 2, replace)
78 str_ = str_.replace(replace * 2, replace)
78 return recursive_replace(str_, replace)
79 return recursive_replace(str_, replace)
79
80
80
81
81 def repo_name_slug(value):
82 def repo_name_slug(value):
82 """Return slug of name of repository
83 """Return slug of name of repository
83 This function is called on each creation/modification
84 This function is called on each creation/modification
84 of repository to prevent bad names in repo
85 of repository to prevent bad names in repo
85 """
86 """
86
87
87 slug = remove_formatting(value)
88 slug = remove_formatting(value)
88 slug = strip_tags(slug)
89 slug = strip_tags(slug)
89
90
90 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
91 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
91 slug = slug.replace(c, '-')
92 slug = slug.replace(c, '-')
92 slug = recursive_replace(slug, '-')
93 slug = recursive_replace(slug, '-')
93 slug = collapse(slug, '-')
94 slug = collapse(slug, '-')
94 return slug
95 return slug
95
96
96
97
97 def get_repo_slug(request):
98 def get_repo_slug(request):
98 _repo = request.environ['pylons.routes_dict'].get('repo_name')
99 _repo = request.environ['pylons.routes_dict'].get('repo_name')
99 if _repo:
100 if _repo:
100 _repo = _repo.rstrip('/')
101 _repo = _repo.rstrip('/')
101 return _repo
102 return _repo
102
103
103
104
104 def get_repos_group_slug(request):
105 def get_repos_group_slug(request):
105 _group = request.environ['pylons.routes_dict'].get('group_name')
106 _group = request.environ['pylons.routes_dict'].get('group_name')
106 if _group:
107 if _group:
107 _group = _group.rstrip('/')
108 _group = _group.rstrip('/')
108 return _group
109 return _group
109
110
110
111
111 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
112 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
112 """
113 """
113 Action logger for various actions made by users
114 Action logger for various actions made by users
114
115
115 :param user: user that made this action, can be a unique username string or
116 :param user: user that made this action, can be a unique username string or
116 object containing user_id attribute
117 object containing user_id attribute
117 :param action: action to log, should be on of predefined unique actions for
118 :param action: action to log, should be on of predefined unique actions for
118 easy translations
119 easy translations
119 :param repo: string name of repository or object containing repo_id,
120 :param repo: string name of repository or object containing repo_id,
120 that action was made on
121 that action was made on
121 :param ipaddr: optional ip address from what the action was made
122 :param ipaddr: optional ip address from what the action was made
122 :param sa: optional sqlalchemy session
123 :param sa: optional sqlalchemy session
123
124
124 """
125 """
125
126
126 if not sa:
127 if not sa:
127 sa = meta.Session
128 sa = meta.Session
128
129
129 try:
130 try:
130 if hasattr(user, 'user_id'):
131 if hasattr(user, 'user_id'):
131 user_obj = user
132 user_obj = user
132 elif isinstance(user, basestring):
133 elif isinstance(user, basestring):
133 user_obj = User.get_by_username(user)
134 user_obj = User.get_by_username(user)
134 else:
135 else:
135 raise Exception('You have to provide user object or username')
136 raise Exception('You have to provide user object or username')
136
137
137 if hasattr(repo, 'repo_id'):
138 if hasattr(repo, 'repo_id'):
138 repo_obj = Repository.get(repo.repo_id)
139 repo_obj = Repository.get(repo.repo_id)
139 repo_name = repo_obj.repo_name
140 repo_name = repo_obj.repo_name
140 elif isinstance(repo, basestring):
141 elif isinstance(repo, basestring):
141 repo_name = repo.lstrip('/')
142 repo_name = repo.lstrip('/')
142 repo_obj = Repository.get_by_repo_name(repo_name)
143 repo_obj = Repository.get_by_repo_name(repo_name)
143 else:
144 else:
144 raise Exception('You have to provide repository to action logger')
145 raise Exception('You have to provide repository to action logger')
145
146
146 user_log = UserLog()
147 user_log = UserLog()
147 user_log.user_id = user_obj.user_id
148 user_log.user_id = user_obj.user_id
148 user_log.action = action
149 user_log.action = action
149
150
150 user_log.repository_id = repo_obj.repo_id
151 user_log.repository_id = repo_obj.repo_id
151 user_log.repository_name = repo_name
152 user_log.repository_name = repo_name
152
153
153 user_log.action_date = datetime.datetime.now()
154 user_log.action_date = datetime.datetime.now()
154 user_log.user_ip = ipaddr
155 user_log.user_ip = ipaddr
155 sa.add(user_log)
156 sa.add(user_log)
156
157
157 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
158 log.info(
159 'Adding user %s, action %s on %s' % (user_obj, action,
160 safe_unicode(repo))
161 )
158 if commit:
162 if commit:
159 sa.commit()
163 sa.commit()
160 except:
164 except:
161 log.error(traceback.format_exc())
165 log.error(traceback.format_exc())
162 raise
166 raise
163
167
164
168
165 def get_repos(path, recursive=False):
169 def get_repos(path, recursive=False):
166 """
170 """
167 Scans given path for repos and return (name,(type,path)) tuple
171 Scans given path for repos and return (name,(type,path)) tuple
168
172
169 :param path: path to scan for repositories
173 :param path: path to scan for repositories
170 :param recursive: recursive search and return names with subdirs in front
174 :param recursive: recursive search and return names with subdirs in front
171 """
175 """
172
176
173 # remove ending slash for better results
177 # remove ending slash for better results
174 path = path.rstrip(os.sep)
178 path = path.rstrip(os.sep)
175
179
176 def _get_repos(p):
180 def _get_repos(p):
177 if not os.access(p, os.W_OK):
181 if not os.access(p, os.W_OK):
178 return
182 return
179 for dirpath in os.listdir(p):
183 for dirpath in os.listdir(p):
180 if os.path.isfile(os.path.join(p, dirpath)):
184 if os.path.isfile(os.path.join(p, dirpath)):
181 continue
185 continue
182 cur_path = os.path.join(p, dirpath)
186 cur_path = os.path.join(p, dirpath)
183 try:
187 try:
184 scm_info = get_scm(cur_path)
188 scm_info = get_scm(cur_path)
185 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
189 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
186 except VCSError:
190 except VCSError:
187 if not recursive:
191 if not recursive:
188 continue
192 continue
189 #check if this dir containts other repos for recursive scan
193 #check if this dir containts other repos for recursive scan
190 rec_path = os.path.join(p, dirpath)
194 rec_path = os.path.join(p, dirpath)
191 if os.path.isdir(rec_path):
195 if os.path.isdir(rec_path):
192 for inner_scm in _get_repos(rec_path):
196 for inner_scm in _get_repos(rec_path):
193 yield inner_scm
197 yield inner_scm
194
198
195 return _get_repos(path)
199 return _get_repos(path)
196
200
197
201
198 def is_valid_repo(repo_name, base_path):
202 def is_valid_repo(repo_name, base_path):
199 """
203 """
200 Returns True if given path is a valid repository False otherwise
204 Returns True if given path is a valid repository False otherwise
205
201 :param repo_name:
206 :param repo_name:
202 :param base_path:
207 :param base_path:
203
208
204 :return True: if given path is a valid repository
209 :return True: if given path is a valid repository
205 """
210 """
206 full_path = os.path.join(base_path, repo_name)
211 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
207
212
208 try:
213 try:
209 get_scm(full_path)
214 get_scm(full_path)
210 return True
215 return True
211 except VCSError:
216 except VCSError:
212 return False
217 return False
213
218
214
219
215 def is_valid_repos_group(repos_group_name, base_path):
220 def is_valid_repos_group(repos_group_name, base_path):
216 """
221 """
217 Returns True if given path is a repos group False otherwise
222 Returns True if given path is a repos group False otherwise
218
223
219 :param repo_name:
224 :param repo_name:
220 :param base_path:
225 :param base_path:
221 """
226 """
222 full_path = os.path.join(base_path, repos_group_name)
227 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
223
228
224 # check if it's not a repo
229 # check if it's not a repo
225 if is_valid_repo(repos_group_name, base_path):
230 if is_valid_repo(repos_group_name, base_path):
226 return False
231 return False
227
232
228 # check if it's a valid path
233 # check if it's a valid path
229 if os.path.isdir(full_path):
234 if os.path.isdir(full_path):
230 return True
235 return True
231
236
232 return False
237 return False
233
238
234
239
235 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
240 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
236 while True:
241 while True:
237 ok = raw_input(prompt)
242 ok = raw_input(prompt)
238 if ok in ('y', 'ye', 'yes'):
243 if ok in ('y', 'ye', 'yes'):
239 return True
244 return True
240 if ok in ('n', 'no', 'nop', 'nope'):
245 if ok in ('n', 'no', 'nop', 'nope'):
241 return False
246 return False
242 retries = retries - 1
247 retries = retries - 1
243 if retries < 0:
248 if retries < 0:
244 raise IOError
249 raise IOError
245 print complaint
250 print complaint
246
251
247 #propagated from mercurial documentation
252 #propagated from mercurial documentation
248 ui_sections = ['alias', 'auth',
253 ui_sections = ['alias', 'auth',
249 'decode/encode', 'defaults',
254 'decode/encode', 'defaults',
250 'diff', 'email',
255 'diff', 'email',
251 'extensions', 'format',
256 'extensions', 'format',
252 'merge-patterns', 'merge-tools',
257 'merge-patterns', 'merge-tools',
253 'hooks', 'http_proxy',
258 'hooks', 'http_proxy',
254 'smtp', 'patch',
259 'smtp', 'patch',
255 'paths', 'profiling',
260 'paths', 'profiling',
256 'server', 'trusted',
261 'server', 'trusted',
257 'ui', 'web', ]
262 'ui', 'web', ]
258
263
259
264
260 def make_ui(read_from='file', path=None, checkpaths=True):
265 def make_ui(read_from='file', path=None, checkpaths=True):
261 """A function that will read python rc files or database
266 """A function that will read python rc files or database
262 and make an mercurial ui object from read options
267 and make an mercurial ui object from read options
263
268
264 :param path: path to mercurial config file
269 :param path: path to mercurial config file
265 :param checkpaths: check the path
270 :param checkpaths: check the path
266 :param read_from: read from 'file' or 'db'
271 :param read_from: read from 'file' or 'db'
267 """
272 """
268
273
269 baseui = ui.ui()
274 baseui = ui.ui()
270
275
271 # clean the baseui object
276 # clean the baseui object
272 baseui._ocfg = config.config()
277 baseui._ocfg = config.config()
273 baseui._ucfg = config.config()
278 baseui._ucfg = config.config()
274 baseui._tcfg = config.config()
279 baseui._tcfg = config.config()
275
280
276 if read_from == 'file':
281 if read_from == 'file':
277 if not os.path.isfile(path):
282 if not os.path.isfile(path):
278 log.debug('hgrc file is not present at %s skipping...' % path)
283 log.debug('hgrc file is not present at %s skipping...' % path)
279 return False
284 return False
280 log.debug('reading hgrc from %s' % path)
285 log.debug('reading hgrc from %s' % path)
281 cfg = config.config()
286 cfg = config.config()
282 cfg.read(path)
287 cfg.read(path)
283 for section in ui_sections:
288 for section in ui_sections:
284 for k, v in cfg.items(section):
289 for k, v in cfg.items(section):
285 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
290 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
286 baseui.setconfig(section, k, v)
291 baseui.setconfig(section, k, v)
287
292
288 elif read_from == 'db':
293 elif read_from == 'db':
289 sa = meta.Session
294 sa = meta.Session
290 ret = sa.query(RhodeCodeUi)\
295 ret = sa.query(RhodeCodeUi)\
291 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
296 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
292 .all()
297 .all()
293
298
294 hg_ui = ret
299 hg_ui = ret
295 for ui_ in hg_ui:
300 for ui_ in hg_ui:
296 if ui_.ui_active:
301 if ui_.ui_active:
297 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
302 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
298 ui_.ui_key, ui_.ui_value)
303 ui_.ui_key, ui_.ui_value)
299 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
304 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
300
305
301 meta.Session.remove()
306 meta.Session.remove()
302 return baseui
307 return baseui
303
308
304
309
305 def set_rhodecode_config(config):
310 def set_rhodecode_config(config):
306 """
311 """
307 Updates pylons config with new settings from database
312 Updates pylons config with new settings from database
308
313
309 :param config:
314 :param config:
310 """
315 """
311 hgsettings = RhodeCodeSetting.get_app_settings()
316 hgsettings = RhodeCodeSetting.get_app_settings()
312
317
313 for k, v in hgsettings.items():
318 for k, v in hgsettings.items():
314 config[k] = v
319 config[k] = v
315
320
316
321
317 def invalidate_cache(cache_key, *args):
322 def invalidate_cache(cache_key, *args):
318 """
323 """
319 Puts cache invalidation task into db for
324 Puts cache invalidation task into db for
320 further global cache invalidation
325 further global cache invalidation
321 """
326 """
322
327
323 from rhodecode.model.scm import ScmModel
328 from rhodecode.model.scm import ScmModel
324
329
325 if cache_key.startswith('get_repo_cached_'):
330 if cache_key.startswith('get_repo_cached_'):
326 name = cache_key.split('get_repo_cached_')[-1]
331 name = cache_key.split('get_repo_cached_')[-1]
327 ScmModel().mark_for_invalidation(name)
332 ScmModel().mark_for_invalidation(name)
328
333
329
334
330 class EmptyChangeset(BaseChangeset):
335 class EmptyChangeset(BaseChangeset):
331 """
336 """
332 An dummy empty changeset. It's possible to pass hash when creating
337 An dummy empty changeset. It's possible to pass hash when creating
333 an EmptyChangeset
338 an EmptyChangeset
334 """
339 """
335
340
336 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
341 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
337 alias=None):
342 alias=None):
338 self._empty_cs = cs
343 self._empty_cs = cs
339 self.revision = -1
344 self.revision = -1
340 self.message = ''
345 self.message = ''
341 self.author = ''
346 self.author = ''
342 self.date = ''
347 self.date = ''
343 self.repository = repo
348 self.repository = repo
344 self.requested_revision = requested_revision
349 self.requested_revision = requested_revision
345 self.alias = alias
350 self.alias = alias
346
351
347 @LazyProperty
352 @LazyProperty
348 def raw_id(self):
353 def raw_id(self):
349 """
354 """
350 Returns raw string identifying this changeset, useful for web
355 Returns raw string identifying this changeset, useful for web
351 representation.
356 representation.
352 """
357 """
353
358
354 return self._empty_cs
359 return self._empty_cs
355
360
356 @LazyProperty
361 @LazyProperty
357 def branch(self):
362 def branch(self):
358 return get_backend(self.alias).DEFAULT_BRANCH_NAME
363 return get_backend(self.alias).DEFAULT_BRANCH_NAME
359
364
360 @LazyProperty
365 @LazyProperty
361 def short_id(self):
366 def short_id(self):
362 return self.raw_id[:12]
367 return self.raw_id[:12]
363
368
364 def get_file_changeset(self, path):
369 def get_file_changeset(self, path):
365 return self
370 return self
366
371
367 def get_file_content(self, path):
372 def get_file_content(self, path):
368 return u''
373 return u''
369
374
370 def get_file_size(self, path):
375 def get_file_size(self, path):
371 return 0
376 return 0
372
377
373
378
374 def map_groups(groups):
379 def map_groups(groups):
375 """
380 """
376 Checks for groups existence, and creates groups structures.
381 Checks for groups existence, and creates groups structures.
377 It returns last group in structure
382 It returns last group in structure
378
383
379 :param groups: list of groups structure
384 :param groups: list of groups structure
380 """
385 """
381 sa = meta.Session
386 sa = meta.Session
382
387
383 parent = None
388 parent = None
384 group = None
389 group = None
385
390
386 # last element is repo in nested groups structure
391 # last element is repo in nested groups structure
387 groups = groups[:-1]
392 groups = groups[:-1]
388 rgm = ReposGroupModel(sa)
393 rgm = ReposGroupModel(sa)
389 for lvl, group_name in enumerate(groups):
394 for lvl, group_name in enumerate(groups):
390 group_name = '/'.join(groups[:lvl] + [group_name])
395 group_name = '/'.join(groups[:lvl] + [group_name])
391 group = RepoGroup.get_by_group_name(group_name)
396 group = RepoGroup.get_by_group_name(group_name)
392 desc = '%s group' % group_name
397 desc = '%s group' % group_name
393
398
394 # # WTF that doesn't work !?
399 # # WTF that doesn't work !?
395 # if group is None:
400 # if group is None:
396 # group = rgm.create(group_name, desc, parent, just_db=True)
401 # group = rgm.create(group_name, desc, parent, just_db=True)
397 # sa.commit()
402 # sa.commit()
398
403
399 # skip folders that are now removed repos
404 # skip folders that are now removed repos
400 if REMOVED_REPO_PAT.match(group_name):
405 if REMOVED_REPO_PAT.match(group_name):
401 break
406 break
402
407
403 if group is None:
408 if group is None:
404 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
409 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
405 group = RepoGroup(group_name, parent)
410 group = RepoGroup(group_name, parent)
406 group.group_description = desc
411 group.group_description = desc
407 sa.add(group)
412 sa.add(group)
408 rgm._create_default_perms(group)
413 rgm._create_default_perms(group)
409 sa.commit()
414 sa.commit()
410 parent = group
415 parent = group
411 return group
416 return group
412
417
413
418
414 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
419 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
415 """
420 """
416 maps all repos given in initial_repo_list, non existing repositories
421 maps all repos given in initial_repo_list, non existing repositories
417 are created, if remove_obsolete is True it also check for db entries
422 are created, if remove_obsolete is True it also check for db entries
418 that are not in initial_repo_list and removes them.
423 that are not in initial_repo_list and removes them.
419
424
420 :param initial_repo_list: list of repositories found by scanning methods
425 :param initial_repo_list: list of repositories found by scanning methods
421 :param remove_obsolete: check for obsolete entries in database
426 :param remove_obsolete: check for obsolete entries in database
422 """
427 """
423 from rhodecode.model.repo import RepoModel
428 from rhodecode.model.repo import RepoModel
424 sa = meta.Session
429 sa = meta.Session
425 rm = RepoModel()
430 rm = RepoModel()
426 user = sa.query(User).filter(User.admin == True).first()
431 user = sa.query(User).filter(User.admin == True).first()
427 if user is None:
432 if user is None:
428 raise Exception('Missing administrative account !')
433 raise Exception('Missing administrative account !')
429 added = []
434 added = []
430
435
431 for name, repo in initial_repo_list.items():
436 for name, repo in initial_repo_list.items():
432 group = map_groups(name.split(Repository.url_sep()))
437 group = map_groups(name.split(Repository.url_sep()))
433 if not rm.get_by_repo_name(name, cache=False):
438 if not rm.get_by_repo_name(name, cache=False):
434 log.info('repository %s not found creating default' % name)
439 log.info('repository %s not found creating default' % name)
435 added.append(name)
440 added.append(name)
436 form_data = {
441 form_data = {
437 'repo_name': name,
442 'repo_name': name,
438 'repo_name_full': name,
443 'repo_name_full': name,
439 'repo_type': repo.alias,
444 'repo_type': repo.alias,
440 'description': repo.description \
445 'description': repo.description \
441 if repo.description != 'unknown' else '%s repository' % name,
446 if repo.description != 'unknown' else '%s repository' % name,
442 'private': False,
447 'private': False,
443 'group_id': getattr(group, 'group_id', None)
448 'group_id': getattr(group, 'group_id', None)
444 }
449 }
445 rm.create(form_data, user, just_db=True)
450 rm.create(form_data, user, just_db=True)
446 sa.commit()
451 sa.commit()
447 removed = []
452 removed = []
448 if remove_obsolete:
453 if remove_obsolete:
449 #remove from database those repositories that are not in the filesystem
454 #remove from database those repositories that are not in the filesystem
450 for repo in sa.query(Repository).all():
455 for repo in sa.query(Repository).all():
451 if repo.repo_name not in initial_repo_list.keys():
456 if repo.repo_name not in initial_repo_list.keys():
452 removed.append(repo.repo_name)
457 removed.append(repo.repo_name)
453 sa.delete(repo)
458 sa.delete(repo)
454 sa.commit()
459 sa.commit()
455
460
456 return added, removed
461 return added, removed
457
462
458
463
459 # set cache regions for beaker so celery can utilise it
464 # set cache regions for beaker so celery can utilise it
460 def add_cache(settings):
465 def add_cache(settings):
461 cache_settings = {'regions': None}
466 cache_settings = {'regions': None}
462 for key in settings.keys():
467 for key in settings.keys():
463 for prefix in ['beaker.cache.', 'cache.']:
468 for prefix in ['beaker.cache.', 'cache.']:
464 if key.startswith(prefix):
469 if key.startswith(prefix):
465 name = key.split(prefix)[1].strip()
470 name = key.split(prefix)[1].strip()
466 cache_settings[name] = settings[key].strip()
471 cache_settings[name] = settings[key].strip()
467 if cache_settings['regions']:
472 if cache_settings['regions']:
468 for region in cache_settings['regions'].split(','):
473 for region in cache_settings['regions'].split(','):
469 region = region.strip()
474 region = region.strip()
470 region_settings = {}
475 region_settings = {}
471 for key, value in cache_settings.items():
476 for key, value in cache_settings.items():
472 if key.startswith(region):
477 if key.startswith(region):
473 region_settings[key.split('.')[1]] = value
478 region_settings[key.split('.')[1]] = value
474 region_settings['expire'] = int(region_settings.get('expire',
479 region_settings['expire'] = int(region_settings.get('expire',
475 60))
480 60))
476 region_settings.setdefault('lock_dir',
481 region_settings.setdefault('lock_dir',
477 cache_settings.get('lock_dir'))
482 cache_settings.get('lock_dir'))
478 region_settings.setdefault('data_dir',
483 region_settings.setdefault('data_dir',
479 cache_settings.get('data_dir'))
484 cache_settings.get('data_dir'))
480
485
481 if 'type' not in region_settings:
486 if 'type' not in region_settings:
482 region_settings['type'] = cache_settings.get('type',
487 region_settings['type'] = cache_settings.get('type',
483 'memory')
488 'memory')
484 beaker.cache.cache_regions[region] = region_settings
489 beaker.cache.cache_regions[region] = region_settings
485
490
486
491
487 #==============================================================================
492 #==============================================================================
488 # TEST FUNCTIONS AND CREATORS
493 # TEST FUNCTIONS AND CREATORS
489 #==============================================================================
494 #==============================================================================
490 def create_test_index(repo_location, config, full_index):
495 def create_test_index(repo_location, config, full_index):
491 """
496 """
492 Makes default test index
497 Makes default test index
493
498
494 :param config: test config
499 :param config: test config
495 :param full_index:
500 :param full_index:
496 """
501 """
497
502
498 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
503 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
499 from rhodecode.lib.pidlock import DaemonLock, LockHeld
504 from rhodecode.lib.pidlock import DaemonLock, LockHeld
500
505
501 repo_location = repo_location
506 repo_location = repo_location
502
507
503 index_location = os.path.join(config['app_conf']['index_dir'])
508 index_location = os.path.join(config['app_conf']['index_dir'])
504 if not os.path.exists(index_location):
509 if not os.path.exists(index_location):
505 os.makedirs(index_location)
510 os.makedirs(index_location)
506
511
507 try:
512 try:
508 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
513 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
509 WhooshIndexingDaemon(index_location=index_location,
514 WhooshIndexingDaemon(index_location=index_location,
510 repo_location=repo_location)\
515 repo_location=repo_location)\
511 .run(full_index=full_index)
516 .run(full_index=full_index)
512 l.release()
517 l.release()
513 except LockHeld:
518 except LockHeld:
514 pass
519 pass
515
520
516
521
517 def create_test_env(repos_test_path, config):
522 def create_test_env(repos_test_path, config):
518 """
523 """
519 Makes a fresh database and
524 Makes a fresh database and
520 install test repository into tmp dir
525 install test repository into tmp dir
521 """
526 """
522 from rhodecode.lib.db_manage import DbManage
527 from rhodecode.lib.db_manage import DbManage
523 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
528 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
524
529
525 # PART ONE create db
530 # PART ONE create db
526 dbconf = config['sqlalchemy.db1.url']
531 dbconf = config['sqlalchemy.db1.url']
527 log.debug('making test db %s' % dbconf)
532 log.debug('making test db %s' % dbconf)
528
533
529 # create test dir if it doesn't exist
534 # create test dir if it doesn't exist
530 if not os.path.isdir(repos_test_path):
535 if not os.path.isdir(repos_test_path):
531 log.debug('Creating testdir %s' % repos_test_path)
536 log.debug('Creating testdir %s' % repos_test_path)
532 os.makedirs(repos_test_path)
537 os.makedirs(repos_test_path)
533
538
534 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
539 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
535 tests=True)
540 tests=True)
536 dbmanage.create_tables(override=True)
541 dbmanage.create_tables(override=True)
537 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
542 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
538 dbmanage.create_default_user()
543 dbmanage.create_default_user()
539 dbmanage.admin_prompt()
544 dbmanage.admin_prompt()
540 dbmanage.create_permissions()
545 dbmanage.create_permissions()
541 dbmanage.populate_default_permissions()
546 dbmanage.populate_default_permissions()
542 Session.commit()
547 Session.commit()
543 # PART TWO make test repo
548 # PART TWO make test repo
544 log.debug('making test vcs repositories')
549 log.debug('making test vcs repositories')
545
550
546 idx_path = config['app_conf']['index_dir']
551 idx_path = config['app_conf']['index_dir']
547 data_path = config['app_conf']['cache_dir']
552 data_path = config['app_conf']['cache_dir']
548
553
549 #clean index and data
554 #clean index and data
550 if idx_path and os.path.exists(idx_path):
555 if idx_path and os.path.exists(idx_path):
551 log.debug('remove %s' % idx_path)
556 log.debug('remove %s' % idx_path)
552 shutil.rmtree(idx_path)
557 shutil.rmtree(idx_path)
553
558
554 if data_path and os.path.exists(data_path):
559 if data_path and os.path.exists(data_path):
555 log.debug('remove %s' % data_path)
560 log.debug('remove %s' % data_path)
556 shutil.rmtree(data_path)
561 shutil.rmtree(data_path)
557
562
558 #CREATE DEFAULT HG REPOSITORY
563 #CREATE DEFAULT HG REPOSITORY
559 cur_dir = dn(dn(abspath(__file__)))
564 cur_dir = dn(dn(abspath(__file__)))
560 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
565 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
561 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
566 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
562 tar.close()
567 tar.close()
563
568
564
569
565 #==============================================================================
570 #==============================================================================
566 # PASTER COMMANDS
571 # PASTER COMMANDS
567 #==============================================================================
572 #==============================================================================
568 class BasePasterCommand(Command):
573 class BasePasterCommand(Command):
569 """
574 """
570 Abstract Base Class for paster commands.
575 Abstract Base Class for paster commands.
571
576
572 The celery commands are somewhat aggressive about loading
577 The celery commands are somewhat aggressive about loading
573 celery.conf, and since our module sets the `CELERY_LOADER`
578 celery.conf, and since our module sets the `CELERY_LOADER`
574 environment variable to our loader, we have to bootstrap a bit and
579 environment variable to our loader, we have to bootstrap a bit and
575 make sure we've had a chance to load the pylons config off of the
580 make sure we've had a chance to load the pylons config off of the
576 command line, otherwise everything fails.
581 command line, otherwise everything fails.
577 """
582 """
578 min_args = 1
583 min_args = 1
579 min_args_error = "Please provide a paster config file as an argument."
584 min_args_error = "Please provide a paster config file as an argument."
580 takes_config_file = 1
585 takes_config_file = 1
581 requires_config_file = True
586 requires_config_file = True
582
587
583 def notify_msg(self, msg, log=False):
588 def notify_msg(self, msg, log=False):
584 """Make a notification to user, additionally if logger is passed
589 """Make a notification to user, additionally if logger is passed
585 it logs this action using given logger
590 it logs this action using given logger
586
591
587 :param msg: message that will be printed to user
592 :param msg: message that will be printed to user
588 :param log: logging instance, to use to additionally log this message
593 :param log: logging instance, to use to additionally log this message
589
594
590 """
595 """
591 if log and isinstance(log, logging):
596 if log and isinstance(log, logging):
592 log(msg)
597 log(msg)
593
598
594 def run(self, args):
599 def run(self, args):
595 """
600 """
596 Overrides Command.run
601 Overrides Command.run
597
602
598 Checks for a config file argument and loads it.
603 Checks for a config file argument and loads it.
599 """
604 """
600 if len(args) < self.min_args:
605 if len(args) < self.min_args:
601 raise BadCommand(
606 raise BadCommand(
602 self.min_args_error % {'min_args': self.min_args,
607 self.min_args_error % {'min_args': self.min_args,
603 'actual_args': len(args)})
608 'actual_args': len(args)})
604
609
605 # Decrement because we're going to lob off the first argument.
610 # Decrement because we're going to lob off the first argument.
606 # @@ This is hacky
611 # @@ This is hacky
607 self.min_args -= 1
612 self.min_args -= 1
608 self.bootstrap_config(args[0])
613 self.bootstrap_config(args[0])
609 self.update_parser()
614 self.update_parser()
610 return super(BasePasterCommand, self).run(args[1:])
615 return super(BasePasterCommand, self).run(args[1:])
611
616
612 def update_parser(self):
617 def update_parser(self):
613 """
618 """
614 Abstract method. Allows for the class's parser to be updated
619 Abstract method. Allows for the class's parser to be updated
615 before the superclass's `run` method is called. Necessary to
620 before the superclass's `run` method is called. Necessary to
616 allow options/arguments to be passed through to the underlying
621 allow options/arguments to be passed through to the underlying
617 celery command.
622 celery command.
618 """
623 """
619 raise NotImplementedError("Abstract Method.")
624 raise NotImplementedError("Abstract Method.")
620
625
621 def bootstrap_config(self, conf):
626 def bootstrap_config(self, conf):
622 """
627 """
623 Loads the pylons configuration.
628 Loads the pylons configuration.
624 """
629 """
625 from pylons import config as pylonsconfig
630 from pylons import config as pylonsconfig
626
631
627 path_to_ini_file = os.path.realpath(conf)
632 path_to_ini_file = os.path.realpath(conf)
628 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
633 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
629 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
634 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
General Comments 0
You need to be logged in to leave comments. Login now