##// END OF EJS Templates
fixes #762, LDAP and container created users are now activated based on...
marcink -
r3370:fdb0f59b beta
parent child Browse files
Show More
@@ -1,195 +1,191 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.login
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Login controller for rhodeocode
7 7
8 8 :created_on: Apr 22, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import formencode
28 28 import datetime
29 29 import urlparse
30 30
31 31 from formencode import htmlfill
32 32 from webob.exc import HTTPFound
33 33 from pylons.i18n.translation import _
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons import request, response, session, tmpl_context as c, url
36 36
37 37 import rhodecode.lib.helpers as h
38 38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.model.db import User
41 41 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
42 42 from rhodecode.model.user import UserModel
43 43 from rhodecode.model.meta import Session
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class LoginController(BaseController):
50 50
51 51 def __before__(self):
52 52 super(LoginController, self).__before__()
53 53
54 54 def index(self):
55 55 # redirect if already logged in
56 56 c.came_from = request.GET.get('came_from')
57 57 not_default = self.rhodecode_user.username != 'default'
58 58 ip_allowed = self.rhodecode_user.ip_allowed
59 59 if self.rhodecode_user.is_authenticated and not_default and ip_allowed:
60 60 return redirect(url('home'))
61 61
62 62 if request.POST:
63 63 # import Login Form validator class
64 64 login_form = LoginForm()
65 65 try:
66 66 session.invalidate()
67 67 c.form_result = login_form.to_python(dict(request.POST))
68 68 # form checks for username/password, now we're authenticated
69 69 username = c.form_result['username']
70 70 user = User.get_by_username(username, case_insensitive=True)
71 71 auth_user = AuthUser(user.user_id)
72 72 auth_user.set_authenticated()
73 73 cs = auth_user.get_cookie_store()
74 74 session['rhodecode_user'] = cs
75 75 user.update_lastlogin()
76 76 Session().commit()
77 77
78 78 # If they want to be remembered, update the cookie
79 79 if c.form_result['remember'] is not False:
80 80 _year = (datetime.datetime.now() +
81 81 datetime.timedelta(seconds=60 * 60 * 24 * 365))
82 82 session._set_cookie_expires(_year)
83 83
84 84 session.save()
85 85
86 86 log.info('user %s is now authenticated and stored in '
87 87 'session, session attrs %s' % (username, cs))
88 88
89 89 # dumps session attrs back to cookie
90 90 session._update_cookie_out()
91 91
92 92 # we set new cookie
93 93 headers = None
94 94 if session.request['set_cookie']:
95 95 # send set-cookie headers back to response to update cookie
96 96 headers = [('Set-Cookie', session.request['cookie_out'])]
97 97
98 98 allowed_schemes = ['http', 'https']
99 99 if c.came_from:
100 100 parsed = urlparse.urlparse(c.came_from)
101 101 server_parsed = urlparse.urlparse(url.current())
102 102 if parsed.scheme and parsed.scheme not in allowed_schemes:
103 103 log.error(
104 104 'Suspicious URL scheme detected %s for url %s' %
105 105 (parsed.scheme, parsed))
106 106 c.came_from = url('home')
107 107 elif server_parsed.netloc != parsed.netloc:
108 108 log.error('Suspicious NETLOC detected %s for url %s'
109 109 'server url is: %s' %
110 110 (parsed.netloc, parsed, server_parsed))
111 111 c.came_from = url('home')
112 112 raise HTTPFound(location=c.came_from, headers=headers)
113 113 else:
114 114 raise HTTPFound(location=url('home'), headers=headers)
115 115
116 116 except formencode.Invalid, errors:
117 117 return htmlfill.render(
118 118 render('/login.html'),
119 119 defaults=errors.value,
120 120 errors=errors.error_dict or {},
121 121 prefix_error=False,
122 122 encoding="UTF-8")
123 123
124 124 return render('/login.html')
125 125
126 126 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
127 127 'hg.register.manual_activate')
128 128 def register(self):
129 c.auto_active = False
130 for perm in User.get_by_username('default').user_perms:
131 if perm.permission.permission_name == 'hg.register.auto_activate':
132 c.auto_active = True
133 break
129 c.auto_active = 'hg.register.auto_activate' in User.get_by_username('default')\
130 .AuthUser.permissions['global']
134 131
135 132 if request.POST:
136
137 133 register_form = RegisterForm()()
138 134 try:
139 135 form_result = register_form.to_python(dict(request.POST))
140 136 form_result['active'] = c.auto_active
141 137 UserModel().create_registration(form_result)
142 h.flash(_('You have successfully registered into rhodecode'),
138 h.flash(_('You have successfully registered into RhodeCode'),
143 139 category='success')
144 140 Session().commit()
145 141 return redirect(url('login_home'))
146 142
147 143 except formencode.Invalid, errors:
148 144 return htmlfill.render(
149 145 render('/register.html'),
150 146 defaults=errors.value,
151 147 errors=errors.error_dict or {},
152 148 prefix_error=False,
153 149 encoding="UTF-8")
154 150
155 151 return render('/register.html')
156 152
157 153 def password_reset(self):
158 154 if request.POST:
159 155 password_reset_form = PasswordResetForm()()
160 156 try:
161 157 form_result = password_reset_form.to_python(dict(request.POST))
162 158 UserModel().reset_password_link(form_result)
163 159 h.flash(_('Your password reset link was sent'),
164 160 category='success')
165 161 return redirect(url('login_home'))
166 162
167 163 except formencode.Invalid, errors:
168 164 return htmlfill.render(
169 165 render('/password_reset.html'),
170 166 defaults=errors.value,
171 167 errors=errors.error_dict or {},
172 168 prefix_error=False,
173 169 encoding="UTF-8")
174 170
175 171 return render('/password_reset.html')
176 172
177 173 def password_reset_confirmation(self):
178 174 if request.GET and request.GET.get('key'):
179 175 try:
180 176 user = User.get_by_api_key(request.GET.get('key'))
181 177 data = dict(email=user.email)
182 178 UserModel().reset_password(data)
183 179 h.flash(_('Your password reset was successful, '
184 180 'new password has been sent to your email'),
185 181 category='success')
186 182 except Exception, e:
187 183 log.error(e)
188 184 return redirect(url('reset_password'))
189 185
190 186 return redirect(url('login_home'))
191 187
192 188 def logout(self):
193 189 session.delete()
194 190 log.info('Logging out and deleting session for user')
195 191 redirect(url('home'))
@@ -1,1009 +1,1013 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import random
27 27 import logging
28 28 import traceback
29 29 import hashlib
30 30
31 31 from tempfile import _RandomNameSequence
32 32 from decorator import decorator
33 33
34 34 from pylons import config, url, request
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37 from sqlalchemy.orm.exc import ObjectDeletedError
38 38
39 39 from rhodecode import __platform__, is_windows, is_unix
40 40 from rhodecode.model.meta import Session
41 41
42 42 from rhodecode.lib.utils2 import str2bool, safe_unicode
43 43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
44 44 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
45 45 from rhodecode.lib.auth_ldap import AuthLdap
46 46
47 47 from rhodecode.model import meta
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class PasswordGenerator(object):
56 56 """
57 57 This is a simple class for generating password from different sets of
58 58 characters
59 59 usage::
60 60
61 61 passwd_gen = PasswordGenerator()
62 62 #print 8-letter password containing only big and small letters
63 63 of alphabet
64 64 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 65 """
66 66 ALPHABETS_NUM = r'''1234567890'''
67 67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
68 68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
69 69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
70 70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
71 71 + ALPHABETS_NUM + ALPHABETS_SPECIAL
72 72 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
73 73 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
74 74 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
75 75 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
76 76
77 77 def __init__(self, passwd=''):
78 78 self.passwd = passwd
79 79
80 80 def gen_password(self, length, type_=None):
81 81 if type_ is None:
82 82 type_ = self.ALPHABETS_FULL
83 83 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
84 84 return self.passwd
85 85
86 86
87 87 class RhodeCodeCrypto(object):
88 88
89 89 @classmethod
90 90 def hash_string(cls, str_):
91 91 """
92 92 Cryptographic function used for password hashing based on pybcrypt
93 93 or pycrypto in windows
94 94
95 95 :param password: password to hash
96 96 """
97 97 if is_windows:
98 98 from hashlib import sha256
99 99 return sha256(str_).hexdigest()
100 100 elif is_unix:
101 101 import bcrypt
102 102 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 103 else:
104 104 raise Exception('Unknown or unsupported platform %s' \
105 105 % __platform__)
106 106
107 107 @classmethod
108 108 def hash_check(cls, password, hashed):
109 109 """
110 110 Checks matching password with it's hashed value, runs different
111 111 implementation based on platform it runs on
112 112
113 113 :param password: password
114 114 :param hashed: password in hashed form
115 115 """
116 116
117 117 if is_windows:
118 118 from hashlib import sha256
119 119 return sha256(password).hexdigest() == hashed
120 120 elif is_unix:
121 121 import bcrypt
122 122 return bcrypt.hashpw(password, hashed) == hashed
123 123 else:
124 124 raise Exception('Unknown or unsupported platform %s' \
125 125 % __platform__)
126 126
127 127
128 128 def get_crypt_password(password):
129 129 return RhodeCodeCrypto.hash_string(password)
130 130
131 131
132 132 def check_password(password, hashed):
133 133 return RhodeCodeCrypto.hash_check(password, hashed)
134 134
135 135
136 136 def generate_api_key(str_, salt=None):
137 137 """
138 138 Generates API KEY from given string
139 139
140 140 :param str_:
141 141 :param salt:
142 142 """
143 143
144 144 if salt is None:
145 145 salt = _RandomNameSequence().next()
146 146
147 147 return hashlib.sha1(str_ + salt).hexdigest()
148 148
149 149
150 150 def authfunc(environ, username, password):
151 151 """
152 152 Dummy authentication wrapper function used in Mercurial and Git for
153 153 access control.
154 154
155 155 :param environ: needed only for using in Basic auth
156 156 """
157 157 return authenticate(username, password)
158 158
159 159
160 160 def authenticate(username, password):
161 161 """
162 162 Authentication function used for access control,
163 163 firstly checks for db authentication then if ldap is enabled for ldap
164 164 authentication, also creates ldap user if not in database
165 165
166 166 :param username: username
167 167 :param password: password
168 168 """
169 169
170 170 user_model = UserModel()
171 171 user = User.get_by_username(username)
172 172
173 173 log.debug('Authenticating user using RhodeCode account')
174 174 if user is not None and not user.ldap_dn:
175 175 if user.active:
176 176 if user.username == 'default' and user.active:
177 177 log.info('user %s authenticated correctly as anonymous user' %
178 178 username)
179 179 return True
180 180
181 181 elif user.username == username and check_password(password,
182 182 user.password):
183 183 log.info('user %s authenticated correctly' % username)
184 184 return True
185 185 else:
186 186 log.warning('user %s tried auth but is disabled' % username)
187 187
188 188 else:
189 189 log.debug('Regular authentication failed')
190 190 user_obj = User.get_by_username(username, case_insensitive=True)
191 191
192 192 if user_obj is not None and not user_obj.ldap_dn:
193 193 log.debug('this user already exists as non ldap')
194 194 return False
195 195
196 196 ldap_settings = RhodeCodeSetting.get_ldap_settings()
197 197 #======================================================================
198 198 # FALLBACK TO LDAP AUTH IF ENABLE
199 199 #======================================================================
200 200 if str2bool(ldap_settings.get('ldap_active')):
201 201 log.debug("Authenticating user using ldap")
202 202 kwargs = {
203 203 'server': ldap_settings.get('ldap_host', ''),
204 204 'base_dn': ldap_settings.get('ldap_base_dn', ''),
205 205 'port': ldap_settings.get('ldap_port'),
206 206 'bind_dn': ldap_settings.get('ldap_dn_user'),
207 207 'bind_pass': ldap_settings.get('ldap_dn_pass'),
208 208 'tls_kind': ldap_settings.get('ldap_tls_kind'),
209 209 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
210 210 'ldap_filter': ldap_settings.get('ldap_filter'),
211 211 'search_scope': ldap_settings.get('ldap_search_scope'),
212 212 'attr_login': ldap_settings.get('ldap_attr_login'),
213 213 'ldap_version': 3,
214 214 }
215 215 log.debug('Checking for ldap authentication')
216 216 try:
217 217 aldap = AuthLdap(**kwargs)
218 218 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
219 219 password)
220 220 log.debug('Got ldap DN response %s' % user_dn)
221 221
222 222 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
223 223 .get(k), [''])[0]
224 224
225 225 user_attrs = {
226 226 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
227 227 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
228 228 'email': get_ldap_attr('ldap_attr_email'),
229 'active': 'hg.register.auto_activate' in User\
230 .get_by_username('default').AuthUser.permissions['global']
229 231 }
230 232
231 233 # don't store LDAP password since we don't need it. Override
232 234 # with some random generated password
233 235 _password = PasswordGenerator().gen_password(length=8)
234 236 # create this user on the fly if it doesn't exist in rhodecode
235 237 # database
236 238 if user_model.create_ldap(username, _password, user_dn,
237 239 user_attrs):
238 240 log.info('created new ldap user %s' % username)
239 241
240 242 Session().commit()
241 243 return True
242 244 except (LdapUsernameError, LdapPasswordError,):
243 245 pass
244 246 except (Exception,):
245 247 log.error(traceback.format_exc())
246 248 pass
247 249 return False
248 250
249 251
250 252 def login_container_auth(username):
251 253 user = User.get_by_username(username)
252 254 if user is None:
253 255 user_attrs = {
254 256 'name': username,
255 257 'lastname': None,
256 258 'email': None,
259 'active': 'hg.register.auto_activate' in User\
260 .get_by_username('default').AuthUser.permissions['global']
257 261 }
258 262 user = UserModel().create_for_container_auth(username, user_attrs)
259 263 if not user:
260 264 return None
261 265 log.info('User %s was created by container authentication' % username)
262 266
263 267 if not user.active:
264 268 return None
265 269
266 270 user.update_lastlogin()
267 271 Session().commit()
268 272
269 273 log.debug('User %s is now logged in by container authentication',
270 274 user.username)
271 275 return user
272 276
273 277
274 278 def get_container_username(environ, config, clean_username=False):
275 279 """
276 280 Get's the container_auth username (or email). It tries to get username
277 281 from REMOTE_USER if container_auth_enabled is enabled, if that fails
278 282 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
279 283 is enabled. clean_username extracts the username from this data if it's
280 284 having @ in it.
281 285
282 286 :param environ:
283 287 :param config:
284 288 :param clean_username:
285 289 """
286 290 username = None
287 291
288 292 if str2bool(config.get('container_auth_enabled', False)):
289 293 from paste.httpheaders import REMOTE_USER
290 294 username = REMOTE_USER(environ)
291 295 log.debug('extracted REMOTE_USER:%s' % (username))
292 296
293 297 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
294 298 username = environ.get('HTTP_X_FORWARDED_USER')
295 299 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
296 300
297 301 if username and clean_username:
298 302 # Removing realm and domain from username
299 303 username = username.partition('@')[0]
300 304 username = username.rpartition('\\')[2]
301 305 log.debug('Received username %s from container' % username)
302 306
303 307 return username
304 308
305 309
306 310 class CookieStoreWrapper(object):
307 311
308 312 def __init__(self, cookie_store):
309 313 self.cookie_store = cookie_store
310 314
311 315 def __repr__(self):
312 316 return 'CookieStore<%s>' % (self.cookie_store)
313 317
314 318 def get(self, key, other=None):
315 319 if isinstance(self.cookie_store, dict):
316 320 return self.cookie_store.get(key, other)
317 321 elif isinstance(self.cookie_store, AuthUser):
318 322 return self.cookie_store.__dict__.get(key, other)
319 323
320 324
321 325 class AuthUser(object):
322 326 """
323 327 A simple object that handles all attributes of user in RhodeCode
324 328
325 329 It does lookup based on API key,given user, or user present in session
326 330 Then it fills all required information for such user. It also checks if
327 331 anonymous access is enabled and if so, it returns default user as logged
328 332 in
329 333 """
330 334
331 335 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
332 336
333 337 self.user_id = user_id
334 338 self.api_key = None
335 339 self.username = username
336 340 self.ip_addr = ip_addr
337 341
338 342 self.name = ''
339 343 self.lastname = ''
340 344 self.email = ''
341 345 self.is_authenticated = False
342 346 self.admin = False
343 347 self.inherit_default_permissions = False
344 348 self.permissions = {}
345 349 self._api_key = api_key
346 350 self.propagate_data()
347 351 self._instance = None
348 352
349 353 def propagate_data(self):
350 354 user_model = UserModel()
351 355 self.anonymous_user = User.get_by_username('default', cache=True)
352 356 is_user_loaded = False
353 357
354 358 # try go get user by api key
355 359 if self._api_key and self._api_key != self.anonymous_user.api_key:
356 360 log.debug('Auth User lookup by API KEY %s' % self._api_key)
357 361 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
358 362 # lookup by userid
359 363 elif (self.user_id is not None and
360 364 self.user_id != self.anonymous_user.user_id):
361 365 log.debug('Auth User lookup by USER ID %s' % self.user_id)
362 366 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
363 367 # lookup by username
364 368 elif self.username and \
365 369 str2bool(config.get('container_auth_enabled', False)):
366 370
367 371 log.debug('Auth User lookup by USER NAME %s' % self.username)
368 372 dbuser = login_container_auth(self.username)
369 373 if dbuser is not None:
370 374 log.debug('filling all attributes to object')
371 375 for k, v in dbuser.get_dict().items():
372 376 setattr(self, k, v)
373 377 self.set_authenticated()
374 378 is_user_loaded = True
375 379 else:
376 380 log.debug('No data in %s that could been used to log in' % self)
377 381
378 382 if not is_user_loaded:
379 383 # if we cannot authenticate user try anonymous
380 384 if self.anonymous_user.active is True:
381 385 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
382 386 # then we set this user is logged in
383 387 self.is_authenticated = True
384 388 else:
385 389 self.user_id = None
386 390 self.username = None
387 391 self.is_authenticated = False
388 392
389 393 if not self.username:
390 394 self.username = 'None'
391 395
392 396 log.debug('Auth User is now %s' % self)
393 397 user_model.fill_perms(self)
394 398
395 399 @property
396 400 def is_admin(self):
397 401 return self.admin
398 402
399 403 @property
400 404 def ip_allowed(self):
401 405 """
402 406 Checks if ip_addr used in constructor is allowed from defined list of
403 407 allowed ip_addresses for user
404 408
405 409 :returns: boolean, True if ip is in allowed ip range
406 410 """
407 411 #check IP
408 412 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
409 413 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
410 414 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
411 415 return True
412 416 else:
413 417 log.info('Access for IP:%s forbidden, '
414 418 'not in %s' % (self.ip_addr, allowed_ips))
415 419 return False
416 420
417 421 def __repr__(self):
418 422 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
419 423 self.is_authenticated)
420 424
421 425 def set_authenticated(self, authenticated=True):
422 426 if self.user_id != self.anonymous_user.user_id:
423 427 self.is_authenticated = authenticated
424 428
425 429 def get_cookie_store(self):
426 430 return {'username': self.username,
427 431 'user_id': self.user_id,
428 432 'is_authenticated': self.is_authenticated}
429 433
430 434 @classmethod
431 435 def from_cookie_store(cls, cookie_store):
432 436 """
433 437 Creates AuthUser from a cookie store
434 438
435 439 :param cls:
436 440 :param cookie_store:
437 441 """
438 442 user_id = cookie_store.get('user_id')
439 443 username = cookie_store.get('username')
440 444 api_key = cookie_store.get('api_key')
441 445 return AuthUser(user_id, api_key, username)
442 446
443 447 @classmethod
444 448 def get_allowed_ips(cls, user_id, cache=False):
445 449 _set = set()
446 450 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
447 451 if cache:
448 452 user_ips = user_ips.options(FromCache("sql_cache_short",
449 453 "get_user_ips_%s" % user_id))
450 454 for ip in user_ips:
451 455 try:
452 456 _set.add(ip.ip_addr)
453 457 except ObjectDeletedError:
454 458 # since we use heavy caching sometimes it happens that we get
455 459 # deleted objects here, we just skip them
456 460 pass
457 461 return _set or set(['0.0.0.0/0', '::/0'])
458 462
459 463
460 464 def set_available_permissions(config):
461 465 """
462 466 This function will propagate pylons globals with all available defined
463 467 permission given in db. We don't want to check each time from db for new
464 468 permissions since adding a new permission also requires application restart
465 469 ie. to decorate new views with the newly created permission
466 470
467 471 :param config: current pylons config instance
468 472
469 473 """
470 474 log.info('getting information about all available permissions')
471 475 try:
472 476 sa = meta.Session
473 477 all_perms = sa.query(Permission).all()
474 478 except Exception:
475 479 pass
476 480 finally:
477 481 meta.Session.remove()
478 482
479 483 config['available_permissions'] = [x.permission_name for x in all_perms]
480 484
481 485
482 486 #==============================================================================
483 487 # CHECK DECORATORS
484 488 #==============================================================================
485 489 class LoginRequired(object):
486 490 """
487 491 Must be logged in to execute this function else
488 492 redirect to login page
489 493
490 494 :param api_access: if enabled this checks only for valid auth token
491 495 and grants access based on valid token
492 496 """
493 497
494 498 def __init__(self, api_access=False):
495 499 self.api_access = api_access
496 500
497 501 def __call__(self, func):
498 502 return decorator(self.__wrapper, func)
499 503
500 504 def __wrapper(self, func, *fargs, **fkwargs):
501 505 cls = fargs[0]
502 506 user = cls.rhodecode_user
503 507 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
504 508
505 509 #check IP
506 510 ip_access_ok = True
507 511 if not user.ip_allowed:
508 512 from rhodecode.lib import helpers as h
509 513 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
510 514 category='warning')
511 515 ip_access_ok = False
512 516
513 517 api_access_ok = False
514 518 if self.api_access:
515 519 log.debug('Checking API KEY access for %s' % cls)
516 520 if user.api_key == request.GET.get('api_key'):
517 521 api_access_ok = True
518 522 else:
519 523 log.debug("API KEY token not valid")
520 524
521 525 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
522 526 if (user.is_authenticated or api_access_ok) and ip_access_ok:
523 527 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
524 528 log.info('user %s is authenticated and granted access to %s '
525 529 'using %s' % (user.username, loc, reason)
526 530 )
527 531 return func(*fargs, **fkwargs)
528 532 else:
529 533 log.warn('user %s NOT authenticated on func: %s' % (
530 534 user, loc)
531 535 )
532 536 p = url.current()
533 537
534 538 log.debug('redirecting to login page with %s' % p)
535 539 return redirect(url('login_home', came_from=p))
536 540
537 541
538 542 class NotAnonymous(object):
539 543 """
540 544 Must be logged in to execute this function else
541 545 redirect to login page"""
542 546
543 547 def __call__(self, func):
544 548 return decorator(self.__wrapper, func)
545 549
546 550 def __wrapper(self, func, *fargs, **fkwargs):
547 551 cls = fargs[0]
548 552 self.user = cls.rhodecode_user
549 553
550 554 log.debug('Checking if user is not anonymous @%s' % cls)
551 555
552 556 anonymous = self.user.username == 'default'
553 557
554 558 if anonymous:
555 559 p = url.current()
556 560
557 561 import rhodecode.lib.helpers as h
558 562 h.flash(_('You need to be a registered user to '
559 563 'perform this action'),
560 564 category='warning')
561 565 return redirect(url('login_home', came_from=p))
562 566 else:
563 567 return func(*fargs, **fkwargs)
564 568
565 569
566 570 class PermsDecorator(object):
567 571 """Base class for controller decorators"""
568 572
569 573 def __init__(self, *required_perms):
570 574 available_perms = config['available_permissions']
571 575 for perm in required_perms:
572 576 if perm not in available_perms:
573 577 raise Exception("'%s' permission is not defined" % perm)
574 578 self.required_perms = set(required_perms)
575 579 self.user_perms = None
576 580
577 581 def __call__(self, func):
578 582 return decorator(self.__wrapper, func)
579 583
580 584 def __wrapper(self, func, *fargs, **fkwargs):
581 585 cls = fargs[0]
582 586 self.user = cls.rhodecode_user
583 587 self.user_perms = self.user.permissions
584 588 log.debug('checking %s permissions %s for %s %s',
585 589 self.__class__.__name__, self.required_perms, cls, self.user)
586 590
587 591 if self.check_permissions():
588 592 log.debug('Permission granted for %s %s' % (cls, self.user))
589 593 return func(*fargs, **fkwargs)
590 594
591 595 else:
592 596 log.debug('Permission denied for %s %s' % (cls, self.user))
593 597 anonymous = self.user.username == 'default'
594 598
595 599 if anonymous:
596 600 p = url.current()
597 601
598 602 import rhodecode.lib.helpers as h
599 603 h.flash(_('You need to be a signed in to '
600 604 'view this page'),
601 605 category='warning')
602 606 return redirect(url('login_home', came_from=p))
603 607
604 608 else:
605 609 # redirect with forbidden ret code
606 610 return abort(403)
607 611
608 612 def check_permissions(self):
609 613 """Dummy function for overriding"""
610 614 raise Exception('You have to write this function in child class')
611 615
612 616
613 617 class HasPermissionAllDecorator(PermsDecorator):
614 618 """
615 619 Checks for access permission for all given predicates. All of them
616 620 have to be meet in order to fulfill the request
617 621 """
618 622
619 623 def check_permissions(self):
620 624 if self.required_perms.issubset(self.user_perms.get('global')):
621 625 return True
622 626 return False
623 627
624 628
625 629 class HasPermissionAnyDecorator(PermsDecorator):
626 630 """
627 631 Checks for access permission for any of given predicates. In order to
628 632 fulfill the request any of predicates must be meet
629 633 """
630 634
631 635 def check_permissions(self):
632 636 if self.required_perms.intersection(self.user_perms.get('global')):
633 637 return True
634 638 return False
635 639
636 640
637 641 class HasRepoPermissionAllDecorator(PermsDecorator):
638 642 """
639 643 Checks for access permission for all given predicates for specific
640 644 repository. All of them have to be meet in order to fulfill the request
641 645 """
642 646
643 647 def check_permissions(self):
644 648 repo_name = get_repo_slug(request)
645 649 try:
646 650 user_perms = set([self.user_perms['repositories'][repo_name]])
647 651 except KeyError:
648 652 return False
649 653 if self.required_perms.issubset(user_perms):
650 654 return True
651 655 return False
652 656
653 657
654 658 class HasRepoPermissionAnyDecorator(PermsDecorator):
655 659 """
656 660 Checks for access permission for any of given predicates for specific
657 661 repository. In order to fulfill the request any of predicates must be meet
658 662 """
659 663
660 664 def check_permissions(self):
661 665 repo_name = get_repo_slug(request)
662 666 try:
663 667 user_perms = set([self.user_perms['repositories'][repo_name]])
664 668 except KeyError:
665 669 return False
666 670
667 671 if self.required_perms.intersection(user_perms):
668 672 return True
669 673 return False
670 674
671 675
672 676 class HasReposGroupPermissionAllDecorator(PermsDecorator):
673 677 """
674 678 Checks for access permission for all given predicates for specific
675 679 repository. All of them have to be meet in order to fulfill the request
676 680 """
677 681
678 682 def check_permissions(self):
679 683 group_name = get_repos_group_slug(request)
680 684 try:
681 685 user_perms = set([self.user_perms['repositories_groups'][group_name]])
682 686 except KeyError:
683 687 return False
684 688
685 689 if self.required_perms.issubset(user_perms):
686 690 return True
687 691 return False
688 692
689 693
690 694 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
691 695 """
692 696 Checks for access permission for any of given predicates for specific
693 697 repository. In order to fulfill the request any of predicates must be meet
694 698 """
695 699
696 700 def check_permissions(self):
697 701 group_name = get_repos_group_slug(request)
698 702 try:
699 703 user_perms = set([self.user_perms['repositories_groups'][group_name]])
700 704 except KeyError:
701 705 return False
702 706
703 707 if self.required_perms.intersection(user_perms):
704 708 return True
705 709 return False
706 710
707 711
708 712 #==============================================================================
709 713 # CHECK FUNCTIONS
710 714 #==============================================================================
711 715 class PermsFunction(object):
712 716 """Base function for other check functions"""
713 717
714 718 def __init__(self, *perms):
715 719 available_perms = config['available_permissions']
716 720
717 721 for perm in perms:
718 722 if perm not in available_perms:
719 723 raise Exception("'%s' permission is not defined" % perm)
720 724 self.required_perms = set(perms)
721 725 self.user_perms = None
722 726 self.repo_name = None
723 727 self.group_name = None
724 728
725 729 def __call__(self, check_location=''):
726 730 #TODO: put user as attribute here
727 731 user = request.user
728 732 cls_name = self.__class__.__name__
729 733 check_scope = {
730 734 'HasPermissionAll': '',
731 735 'HasPermissionAny': '',
732 736 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
733 737 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
734 738 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
735 739 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
736 740 }.get(cls_name, '?')
737 741 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
738 742 self.required_perms, user, check_scope,
739 743 check_location or 'unspecified location')
740 744 if not user:
741 745 log.debug('Empty request user')
742 746 return False
743 747 self.user_perms = user.permissions
744 748 if self.check_permissions():
745 749 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
746 750 check_location or 'unspecified location')
747 751 return True
748 752
749 753 else:
750 754 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
751 755 check_location or 'unspecified location')
752 756 return False
753 757
754 758 def check_permissions(self):
755 759 """Dummy function for overriding"""
756 760 raise Exception('You have to write this function in child class')
757 761
758 762
759 763 class HasPermissionAll(PermsFunction):
760 764 def check_permissions(self):
761 765 if self.required_perms.issubset(self.user_perms.get('global')):
762 766 return True
763 767 return False
764 768
765 769
766 770 class HasPermissionAny(PermsFunction):
767 771 def check_permissions(self):
768 772 if self.required_perms.intersection(self.user_perms.get('global')):
769 773 return True
770 774 return False
771 775
772 776
773 777 class HasRepoPermissionAll(PermsFunction):
774 778 def __call__(self, repo_name=None, check_location=''):
775 779 self.repo_name = repo_name
776 780 return super(HasRepoPermissionAll, self).__call__(check_location)
777 781
778 782 def check_permissions(self):
779 783 if not self.repo_name:
780 784 self.repo_name = get_repo_slug(request)
781 785
782 786 try:
783 787 self._user_perms = set(
784 788 [self.user_perms['repositories'][self.repo_name]]
785 789 )
786 790 except KeyError:
787 791 return False
788 792 if self.required_perms.issubset(self._user_perms):
789 793 return True
790 794 return False
791 795
792 796
793 797 class HasRepoPermissionAny(PermsFunction):
794 798 def __call__(self, repo_name=None, check_location=''):
795 799 self.repo_name = repo_name
796 800 return super(HasRepoPermissionAny, self).__call__(check_location)
797 801
798 802 def check_permissions(self):
799 803 if not self.repo_name:
800 804 self.repo_name = get_repo_slug(request)
801 805
802 806 try:
803 807 self._user_perms = set(
804 808 [self.user_perms['repositories'][self.repo_name]]
805 809 )
806 810 except KeyError:
807 811 return False
808 812 if self.required_perms.intersection(self._user_perms):
809 813 return True
810 814 return False
811 815
812 816
813 817 class HasReposGroupPermissionAny(PermsFunction):
814 818 def __call__(self, group_name=None, check_location=''):
815 819 self.group_name = group_name
816 820 return super(HasReposGroupPermissionAny, self).__call__(check_location)
817 821
818 822 def check_permissions(self):
819 823 try:
820 824 self._user_perms = set(
821 825 [self.user_perms['repositories_groups'][self.group_name]]
822 826 )
823 827 except KeyError:
824 828 return False
825 829 if self.required_perms.intersection(self._user_perms):
826 830 return True
827 831 return False
828 832
829 833
830 834 class HasReposGroupPermissionAll(PermsFunction):
831 835 def __call__(self, group_name=None, check_location=''):
832 836 self.group_name = group_name
833 837 return super(HasReposGroupPermissionAll, self).__call__(check_location)
834 838
835 839 def check_permissions(self):
836 840 try:
837 841 self._user_perms = set(
838 842 [self.user_perms['repositories_groups'][self.group_name]]
839 843 )
840 844 except KeyError:
841 845 return False
842 846 if self.required_perms.issubset(self._user_perms):
843 847 return True
844 848 return False
845 849
846 850
847 851 #==============================================================================
848 852 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
849 853 #==============================================================================
850 854 class HasPermissionAnyMiddleware(object):
851 855 def __init__(self, *perms):
852 856 self.required_perms = set(perms)
853 857
854 858 def __call__(self, user, repo_name):
855 859 # repo_name MUST be unicode, since we handle keys in permission
856 860 # dict by unicode
857 861 repo_name = safe_unicode(repo_name)
858 862 usr = AuthUser(user.user_id)
859 863 try:
860 864 self.user_perms = set([usr.permissions['repositories'][repo_name]])
861 865 except Exception:
862 866 log.error('Exception while accessing permissions %s' %
863 867 traceback.format_exc())
864 868 self.user_perms = set()
865 869 self.username = user.username
866 870 self.repo_name = repo_name
867 871 return self.check_permissions()
868 872
869 873 def check_permissions(self):
870 874 log.debug('checking VCS protocol '
871 875 'permissions %s for user:%s repository:%s', self.user_perms,
872 876 self.username, self.repo_name)
873 877 if self.required_perms.intersection(self.user_perms):
874 878 log.debug('permission granted for user:%s on repo:%s' % (
875 879 self.username, self.repo_name
876 880 )
877 881 )
878 882 return True
879 883 log.debug('permission denied for user:%s on repo:%s' % (
880 884 self.username, self.repo_name
881 885 )
882 886 )
883 887 return False
884 888
885 889
886 890 #==============================================================================
887 891 # SPECIAL VERSION TO HANDLE API AUTH
888 892 #==============================================================================
889 893 class _BaseApiPerm(object):
890 894 def __init__(self, *perms):
891 895 self.required_perms = set(perms)
892 896
893 897 def __call__(self, check_location='unspecified', user=None, repo_name=None):
894 898 cls_name = self.__class__.__name__
895 899 check_scope = 'user:%s, repo:%s' % (user, repo_name)
896 900 log.debug('checking cls:%s %s %s @ %s', cls_name,
897 901 self.required_perms, check_scope, check_location)
898 902 if not user:
899 903 log.debug('Empty User passed into arguments')
900 904 return False
901 905
902 906 ## process user
903 907 if not isinstance(user, AuthUser):
904 908 user = AuthUser(user.user_id)
905 909
906 910 if self.check_permissions(user.permissions, repo_name):
907 911 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
908 912 user, check_location)
909 913 return True
910 914
911 915 else:
912 916 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
913 917 user, check_location)
914 918 return False
915 919
916 920 def check_permissions(self, perm_defs, repo_name):
917 921 """
918 922 implement in child class should return True if permissions are ok,
919 923 False otherwise
920 924
921 925 :param perm_defs: dict with permission definitions
922 926 :param repo_name: repo name
923 927 """
924 928 raise NotImplementedError()
925 929
926 930
927 931 class HasPermissionAllApi(_BaseApiPerm):
928 932 def __call__(self, user, check_location=''):
929 933 return super(HasPermissionAllApi, self)\
930 934 .__call__(check_location=check_location, user=user)
931 935
932 936 def check_permissions(self, perm_defs, repo):
933 937 if self.required_perms.issubset(perm_defs.get('global')):
934 938 return True
935 939 return False
936 940
937 941
938 942 class HasPermissionAnyApi(_BaseApiPerm):
939 943 def __call__(self, user, check_location=''):
940 944 return super(HasPermissionAnyApi, self)\
941 945 .__call__(check_location=check_location, user=user)
942 946
943 947 def check_permissions(self, perm_defs, repo):
944 948 if self.required_perms.intersection(perm_defs.get('global')):
945 949 return True
946 950 return False
947 951
948 952
949 953 class HasRepoPermissionAllApi(_BaseApiPerm):
950 954 def __call__(self, user, repo_name, check_location=''):
951 955 return super(HasRepoPermissionAllApi, self)\
952 956 .__call__(check_location=check_location, user=user,
953 957 repo_name=repo_name)
954 958
955 959 def check_permissions(self, perm_defs, repo_name):
956 960
957 961 try:
958 962 self._user_perms = set(
959 963 [perm_defs['repositories'][repo_name]]
960 964 )
961 965 except KeyError:
962 966 log.warning(traceback.format_exc())
963 967 return False
964 968 if self.required_perms.issubset(self._user_perms):
965 969 return True
966 970 return False
967 971
968 972
969 973 class HasRepoPermissionAnyApi(_BaseApiPerm):
970 974 def __call__(self, user, repo_name, check_location=''):
971 975 return super(HasRepoPermissionAnyApi, self)\
972 976 .__call__(check_location=check_location, user=user,
973 977 repo_name=repo_name)
974 978
975 979 def check_permissions(self, perm_defs, repo_name):
976 980
977 981 try:
978 982 _user_perms = set(
979 983 [perm_defs['repositories'][repo_name]]
980 984 )
981 985 except KeyError:
982 986 log.warning(traceback.format_exc())
983 987 return False
984 988 if self.required_perms.intersection(_user_perms):
985 989 return True
986 990 return False
987 991
988 992
989 993 def check_ip_access(source_ip, allowed_ips=None):
990 994 """
991 995 Checks if source_ip is a subnet of any of allowed_ips.
992 996
993 997 :param source_ip:
994 998 :param allowed_ips: list of allowed ips together with mask
995 999 """
996 1000 from rhodecode.lib import ipaddr
997 1001 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
998 1002 if isinstance(allowed_ips, (tuple, list, set)):
999 1003 for ip in allowed_ips:
1000 1004 try:
1001 1005 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1002 1006 return True
1003 1007 # for any case we cannot determine the IP, don't crash just
1004 1008 # skip it and log as error, we want to say forbidden still when
1005 1009 # sending bad IP
1006 1010 except Exception:
1007 1011 log.error(traceback.format_exc())
1008 1012 continue
1009 1013 return False
@@ -1,2033 +1,2042 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode, remove_suffix, remove_prefix
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 try:
122 122 id_ = int(id_)
123 123 except (TypeError, ValueError):
124 124 raise HTTPNotFound
125 125
126 126 res = cls.query().get(id_)
127 127 if not res:
128 128 raise HTTPNotFound
129 129 return res
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session().delete(obj)
139 139
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == unicode
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name in ["ldap_active",
171 171 "default_repo_enable_statistics",
172 172 "default_repo_enable_locking",
173 173 "default_repo_private",
174 174 "default_repo_enable_downloads"]:
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_unicode(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193 @classmethod
194 194 def get_by_name(cls, key):
195 195 return cls.query()\
196 196 .filter(cls.app_settings_name == key).scalar()
197 197
198 198 @classmethod
199 199 def get_by_name_or_create(cls, key):
200 200 res = cls.get_by_name(key)
201 201 if not res:
202 202 res = cls(key)
203 203 return res
204 204
205 205 @classmethod
206 206 def get_app_settings(cls, cache=False):
207 207
208 208 ret = cls.query()
209 209
210 210 if cache:
211 211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 212
213 213 if not ret:
214 214 raise Exception('Could not get application settings !')
215 215 settings = {}
216 216 for each in ret:
217 217 settings['rhodecode_' + each.app_settings_name] = \
218 218 each.app_settings_value
219 219
220 220 return settings
221 221
222 222 @classmethod
223 223 def get_ldap_settings(cls, cache=False):
224 224 ret = cls.query()\
225 225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 226 fd = {}
227 227 for row in ret:
228 228 fd.update({row.app_settings_name: row.app_settings_value})
229 229
230 230 return fd
231 231
232 232 @classmethod
233 233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 234 ret = cls.query()\
235 235 .filter(cls.app_settings_name.startswith('default_')).all()
236 236 fd = {}
237 237 for row in ret:
238 238 key = row.app_settings_name
239 239 if strip_prefix:
240 240 key = remove_prefix(key, prefix='default_')
241 241 fd.update({key: row.app_settings_value})
242 242
243 243 return fd
244 244
245 245
246 246 class RhodeCodeUi(Base, BaseModel):
247 247 __tablename__ = 'rhodecode_ui'
248 248 __table_args__ = (
249 249 UniqueConstraint('ui_key'),
250 250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 251 'mysql_charset': 'utf8'}
252 252 )
253 253
254 254 HOOK_UPDATE = 'changegroup.update'
255 255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 256 HOOK_PUSH = 'changegroup.push_logger'
257 257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 258 HOOK_PULL = 'outgoing.pull_logger'
259 259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 260
261 261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 266
267 267 @classmethod
268 268 def get_by_key(cls, key):
269 269 return cls.query().filter(cls.ui_key == key).scalar()
270 270
271 271 @classmethod
272 272 def get_builtin_hooks(cls):
273 273 q = cls.query()
274 274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 277 return q.all()
278 278
279 279 @classmethod
280 280 def get_custom_hooks(cls):
281 281 q = cls.query()
282 282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 285 q = q.filter(cls.ui_section == 'hooks')
286 286 return q.all()
287 287
288 288 @classmethod
289 289 def get_repos_location(cls):
290 290 return cls.get_by_key('/').ui_value
291 291
292 292 @classmethod
293 293 def create_or_update_hook(cls, key, val):
294 294 new_ui = cls.get_by_key(key) or cls()
295 295 new_ui.ui_section = 'hooks'
296 296 new_ui.ui_active = True
297 297 new_ui.ui_key = key
298 298 new_ui.ui_value = val
299 299
300 300 Session().add(new_ui)
301 301
302 302 def __repr__(self):
303 303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 304 self.ui_value)
305 305
306 306
307 307 class User(Base, BaseModel):
308 308 __tablename__ = 'users'
309 309 __table_args__ = (
310 310 UniqueConstraint('username'), UniqueConstraint('email'),
311 311 Index('u_username_idx', 'username'),
312 312 Index('u_email_idx', 'email'),
313 313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 314 'mysql_charset': 'utf8'}
315 315 )
316 316 DEFAULT_USER = 'default'
317 317 DEFAULT_PERMISSIONS = [
318 318 'hg.register.manual_activate', 'hg.create.repository',
319 319 'hg.fork.repository', 'repository.read', 'group.read'
320 320 ]
321 321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 333
334 334 user_log = relationship('UserLog')
335 335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 336
337 337 repositories = relationship('Repository')
338 338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 340
341 341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 343
344 344 group_member = relationship('UsersGroupMember', cascade='all')
345 345
346 346 notifications = relationship('UserNotification', cascade='all')
347 347 # notifications assigned to this user
348 348 user_created_notifications = relationship('Notification', cascade='all')
349 349 # comments created by this user
350 350 user_comments = relationship('ChangesetComment', cascade='all')
351 351 #extra emails for this user
352 352 user_emails = relationship('UserEmailMap', cascade='all')
353 353
354 354 @hybrid_property
355 355 def email(self):
356 356 return self._email
357 357
358 358 @email.setter
359 359 def email(self, val):
360 360 self._email = val.lower() if val else None
361 361
362 362 @property
363 363 def firstname(self):
364 364 # alias for future
365 365 return self.name
366 366
367 367 @property
368 368 def emails(self):
369 369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 370 return [self.email] + [x.email for x in other]
371 371
372 372 @property
373 373 def ip_addresses(self):
374 374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 375 return [x.ip_addr for x in ret]
376 376
377 377 @property
378 378 def username_and_name(self):
379 379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 380
381 381 @property
382 382 def full_name(self):
383 383 return '%s %s' % (self.firstname, self.lastname)
384 384
385 385 @property
386 386 def full_name_or_username(self):
387 387 return ('%s %s' % (self.firstname, self.lastname)
388 388 if (self.firstname and self.lastname) else self.username)
389 389
390 390 @property
391 391 def full_contact(self):
392 392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 393
394 394 @property
395 395 def short_contact(self):
396 396 return '%s %s' % (self.firstname, self.lastname)
397 397
398 398 @property
399 399 def is_admin(self):
400 400 return self.admin
401 401
402 @property
403 def AuthUser(self):
404 """
405 Returns instance of AuthUser for this user
406 """
407 from rhodecode.lib.auth import AuthUser
408 return AuthUser(user_id=self.user_id, api_key=self.api_key,
409 username=self.username)
410
402 411 def __unicode__(self):
403 412 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
404 413 self.user_id, self.username)
405 414
406 415 @classmethod
407 416 def get_by_username(cls, username, case_insensitive=False, cache=False):
408 417 if case_insensitive:
409 418 q = cls.query().filter(cls.username.ilike(username))
410 419 else:
411 420 q = cls.query().filter(cls.username == username)
412 421
413 422 if cache:
414 423 q = q.options(FromCache(
415 424 "sql_cache_short",
416 425 "get_user_%s" % _hash_key(username)
417 426 )
418 427 )
419 428 return q.scalar()
420 429
421 430 @classmethod
422 431 def get_by_api_key(cls, api_key, cache=False):
423 432 q = cls.query().filter(cls.api_key == api_key)
424 433
425 434 if cache:
426 435 q = q.options(FromCache("sql_cache_short",
427 436 "get_api_key_%s" % api_key))
428 437 return q.scalar()
429 438
430 439 @classmethod
431 440 def get_by_email(cls, email, case_insensitive=False, cache=False):
432 441 if case_insensitive:
433 442 q = cls.query().filter(cls.email.ilike(email))
434 443 else:
435 444 q = cls.query().filter(cls.email == email)
436 445
437 446 if cache:
438 447 q = q.options(FromCache("sql_cache_short",
439 448 "get_email_key_%s" % email))
440 449
441 450 ret = q.scalar()
442 451 if ret is None:
443 452 q = UserEmailMap.query()
444 453 # try fetching in alternate email map
445 454 if case_insensitive:
446 455 q = q.filter(UserEmailMap.email.ilike(email))
447 456 else:
448 457 q = q.filter(UserEmailMap.email == email)
449 458 q = q.options(joinedload(UserEmailMap.user))
450 459 if cache:
451 460 q = q.options(FromCache("sql_cache_short",
452 461 "get_email_map_key_%s" % email))
453 462 ret = getattr(q.scalar(), 'user', None)
454 463
455 464 return ret
456 465
457 466 @classmethod
458 467 def get_from_cs_author(cls, author):
459 468 """
460 469 Tries to get User objects out of commit author string
461 470
462 471 :param author:
463 472 """
464 473 from rhodecode.lib.helpers import email, author_name
465 474 # Valid email in the attribute passed, see if they're in the system
466 475 _email = email(author)
467 476 if _email:
468 477 user = cls.get_by_email(_email, case_insensitive=True)
469 478 if user:
470 479 return user
471 480 # Maybe we can match by username?
472 481 _author = author_name(author)
473 482 user = cls.get_by_username(_author, case_insensitive=True)
474 483 if user:
475 484 return user
476 485
477 486 def update_lastlogin(self):
478 487 """Update user lastlogin"""
479 488 self.last_login = datetime.datetime.now()
480 489 Session().add(self)
481 490 log.debug('updated user %s lastlogin' % self.username)
482 491
483 492 def get_api_data(self):
484 493 """
485 494 Common function for generating user related data for API
486 495 """
487 496 user = self
488 497 data = dict(
489 498 user_id=user.user_id,
490 499 username=user.username,
491 500 firstname=user.name,
492 501 lastname=user.lastname,
493 502 email=user.email,
494 503 emails=user.emails,
495 504 api_key=user.api_key,
496 505 active=user.active,
497 506 admin=user.admin,
498 507 ldap_dn=user.ldap_dn,
499 508 last_login=user.last_login,
500 509 ip_addresses=user.ip_addresses
501 510 )
502 511 return data
503 512
504 513 def __json__(self):
505 514 data = dict(
506 515 full_name=self.full_name,
507 516 full_name_or_username=self.full_name_or_username,
508 517 short_contact=self.short_contact,
509 518 full_contact=self.full_contact
510 519 )
511 520 data.update(self.get_api_data())
512 521 return data
513 522
514 523
515 524 class UserEmailMap(Base, BaseModel):
516 525 __tablename__ = 'user_email_map'
517 526 __table_args__ = (
518 527 Index('uem_email_idx', 'email'),
519 528 UniqueConstraint('email'),
520 529 {'extend_existing': True, 'mysql_engine': 'InnoDB',
521 530 'mysql_charset': 'utf8'}
522 531 )
523 532 __mapper_args__ = {}
524 533
525 534 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
526 535 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
527 536 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
528 537 user = relationship('User', lazy='joined')
529 538
530 539 @validates('_email')
531 540 def validate_email(self, key, email):
532 541 # check if this email is not main one
533 542 main_email = Session().query(User).filter(User.email == email).scalar()
534 543 if main_email is not None:
535 544 raise AttributeError('email %s is present is user table' % email)
536 545 return email
537 546
538 547 @hybrid_property
539 548 def email(self):
540 549 return self._email
541 550
542 551 @email.setter
543 552 def email(self, val):
544 553 self._email = val.lower() if val else None
545 554
546 555
547 556 class UserIpMap(Base, BaseModel):
548 557 __tablename__ = 'user_ip_map'
549 558 __table_args__ = (
550 559 UniqueConstraint('user_id', 'ip_addr'),
551 560 {'extend_existing': True, 'mysql_engine': 'InnoDB',
552 561 'mysql_charset': 'utf8'}
553 562 )
554 563 __mapper_args__ = {}
555 564
556 565 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
557 566 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
558 567 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
559 568 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
560 569 user = relationship('User', lazy='joined')
561 570
562 571 @classmethod
563 572 def _get_ip_range(cls, ip_addr):
564 573 from rhodecode.lib import ipaddr
565 574 net = ipaddr.IPNetwork(address=ip_addr)
566 575 return [str(net.network), str(net.broadcast)]
567 576
568 577 def __json__(self):
569 578 return dict(
570 579 ip_addr=self.ip_addr,
571 580 ip_range=self._get_ip_range(self.ip_addr)
572 581 )
573 582
574 583
575 584 class UserLog(Base, BaseModel):
576 585 __tablename__ = 'user_logs'
577 586 __table_args__ = (
578 587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
579 588 'mysql_charset': 'utf8'},
580 589 )
581 590 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
582 591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
583 592 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
584 593 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
585 594 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
586 595 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
587 596 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
588 597 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
589 598
590 599 @property
591 600 def action_as_day(self):
592 601 return datetime.date(*self.action_date.timetuple()[:3])
593 602
594 603 user = relationship('User')
595 604 repository = relationship('Repository', cascade='')
596 605
597 606
598 607 class UsersGroup(Base, BaseModel):
599 608 __tablename__ = 'users_groups'
600 609 __table_args__ = (
601 610 {'extend_existing': True, 'mysql_engine': 'InnoDB',
602 611 'mysql_charset': 'utf8'},
603 612 )
604 613
605 614 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 615 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
607 616 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
608 617 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
609 618
610 619 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
611 620 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
612 621 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
613 622
614 623 def __unicode__(self):
615 624 return u'<userGroup(%s)>' % (self.users_group_name)
616 625
617 626 @classmethod
618 627 def get_by_group_name(cls, group_name, cache=False,
619 628 case_insensitive=False):
620 629 if case_insensitive:
621 630 q = cls.query().filter(cls.users_group_name.ilike(group_name))
622 631 else:
623 632 q = cls.query().filter(cls.users_group_name == group_name)
624 633 if cache:
625 634 q = q.options(FromCache(
626 635 "sql_cache_short",
627 636 "get_user_%s" % _hash_key(group_name)
628 637 )
629 638 )
630 639 return q.scalar()
631 640
632 641 @classmethod
633 642 def get(cls, users_group_id, cache=False):
634 643 users_group = cls.query()
635 644 if cache:
636 645 users_group = users_group.options(FromCache("sql_cache_short",
637 646 "get_users_group_%s" % users_group_id))
638 647 return users_group.get(users_group_id)
639 648
640 649 def get_api_data(self):
641 650 users_group = self
642 651
643 652 data = dict(
644 653 users_group_id=users_group.users_group_id,
645 654 group_name=users_group.users_group_name,
646 655 active=users_group.users_group_active,
647 656 )
648 657
649 658 return data
650 659
651 660
652 661 class UsersGroupMember(Base, BaseModel):
653 662 __tablename__ = 'users_groups_members'
654 663 __table_args__ = (
655 664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
656 665 'mysql_charset': 'utf8'},
657 666 )
658 667
659 668 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
660 669 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
661 670 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
662 671
663 672 user = relationship('User', lazy='joined')
664 673 users_group = relationship('UsersGroup')
665 674
666 675 def __init__(self, gr_id='', u_id=''):
667 676 self.users_group_id = gr_id
668 677 self.user_id = u_id
669 678
670 679
671 680 class RepositoryField(Base, BaseModel):
672 681 __tablename__ = 'repositories_fields'
673 682 __table_args__ = (
674 683 UniqueConstraint('repository_id', 'field_key'), # no-multi field
675 684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
676 685 'mysql_charset': 'utf8'},
677 686 )
678 687 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
679 688
680 689 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
681 690 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
682 691 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
683 692 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
684 693 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
685 694 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
686 695 field_type = Column("field_type", String(256), nullable=False, unique=None)
687 696 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
688 697
689 698 repository = relationship('Repository')
690 699
691 700 @property
692 701 def field_key_prefixed(self):
693 702 return 'ex_%s' % self.field_key
694 703
695 704 @classmethod
696 705 def un_prefix_key(cls, key):
697 706 if key.startswith(cls.PREFIX):
698 707 return key[len(cls.PREFIX):]
699 708 return key
700 709
701 710 @classmethod
702 711 def get_by_key_name(cls, key, repo):
703 712 row = cls.query()\
704 713 .filter(cls.repository == repo)\
705 714 .filter(cls.field_key == key).scalar()
706 715 return row
707 716
708 717
709 718 class Repository(Base, BaseModel):
710 719 __tablename__ = 'repositories'
711 720 __table_args__ = (
712 721 UniqueConstraint('repo_name'),
713 722 Index('r_repo_name_idx', 'repo_name'),
714 723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
715 724 'mysql_charset': 'utf8'},
716 725 )
717 726
718 727 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
719 728 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
720 729 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
721 730 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
722 731 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
723 732 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
724 733 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
725 734 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
726 735 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
727 736 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
728 737 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
729 738 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
730 739 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
731 740 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
732 741 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
733 742
734 743 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
735 744 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
736 745
737 746 user = relationship('User')
738 747 fork = relationship('Repository', remote_side=repo_id)
739 748 group = relationship('RepoGroup')
740 749 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
741 750 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
742 751 stats = relationship('Statistics', cascade='all', uselist=False)
743 752
744 753 followers = relationship('UserFollowing',
745 754 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
746 755 cascade='all')
747 756 extra_fields = relationship('RepositoryField',
748 757 cascade="all, delete, delete-orphan")
749 758
750 759 logs = relationship('UserLog')
751 760 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
752 761
753 762 pull_requests_org = relationship('PullRequest',
754 763 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
755 764 cascade="all, delete, delete-orphan")
756 765
757 766 pull_requests_other = relationship('PullRequest',
758 767 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
759 768 cascade="all, delete, delete-orphan")
760 769
761 770 def __unicode__(self):
762 771 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
763 772 self.repo_name)
764 773
765 774 @hybrid_property
766 775 def locked(self):
767 776 # always should return [user_id, timelocked]
768 777 if self._locked:
769 778 _lock_info = self._locked.split(':')
770 779 return int(_lock_info[0]), _lock_info[1]
771 780 return [None, None]
772 781
773 782 @locked.setter
774 783 def locked(self, val):
775 784 if val and isinstance(val, (list, tuple)):
776 785 self._locked = ':'.join(map(str, val))
777 786 else:
778 787 self._locked = None
779 788
780 789 @hybrid_property
781 790 def changeset_cache(self):
782 791 from rhodecode.lib.vcs.backends.base import EmptyChangeset
783 792 dummy = EmptyChangeset().__json__()
784 793 if not self._changeset_cache:
785 794 return dummy
786 795 try:
787 796 return json.loads(self._changeset_cache)
788 797 except TypeError:
789 798 return dummy
790 799
791 800 @changeset_cache.setter
792 801 def changeset_cache(self, val):
793 802 try:
794 803 self._changeset_cache = json.dumps(val)
795 804 except:
796 805 log.error(traceback.format_exc())
797 806
798 807 @classmethod
799 808 def url_sep(cls):
800 809 return URL_SEP
801 810
802 811 @classmethod
803 812 def normalize_repo_name(cls, repo_name):
804 813 """
805 814 Normalizes os specific repo_name to the format internally stored inside
806 815 dabatabase using URL_SEP
807 816
808 817 :param cls:
809 818 :param repo_name:
810 819 """
811 820 return cls.url_sep().join(repo_name.split(os.sep))
812 821
813 822 @classmethod
814 823 def get_by_repo_name(cls, repo_name):
815 824 q = Session().query(cls).filter(cls.repo_name == repo_name)
816 825 q = q.options(joinedload(Repository.fork))\
817 826 .options(joinedload(Repository.user))\
818 827 .options(joinedload(Repository.group))
819 828 return q.scalar()
820 829
821 830 @classmethod
822 831 def get_by_full_path(cls, repo_full_path):
823 832 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
824 833 repo_name = cls.normalize_repo_name(repo_name)
825 834 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
826 835
827 836 @classmethod
828 837 def get_repo_forks(cls, repo_id):
829 838 return cls.query().filter(Repository.fork_id == repo_id)
830 839
831 840 @classmethod
832 841 def base_path(cls):
833 842 """
834 843 Returns base path when all repos are stored
835 844
836 845 :param cls:
837 846 """
838 847 q = Session().query(RhodeCodeUi)\
839 848 .filter(RhodeCodeUi.ui_key == cls.url_sep())
840 849 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
841 850 return q.one().ui_value
842 851
843 852 @property
844 853 def forks(self):
845 854 """
846 855 Return forks of this repo
847 856 """
848 857 return Repository.get_repo_forks(self.repo_id)
849 858
850 859 @property
851 860 def parent(self):
852 861 """
853 862 Returns fork parent
854 863 """
855 864 return self.fork
856 865
857 866 @property
858 867 def just_name(self):
859 868 return self.repo_name.split(Repository.url_sep())[-1]
860 869
861 870 @property
862 871 def groups_with_parents(self):
863 872 groups = []
864 873 if self.group is None:
865 874 return groups
866 875
867 876 cur_gr = self.group
868 877 groups.insert(0, cur_gr)
869 878 while 1:
870 879 gr = getattr(cur_gr, 'parent_group', None)
871 880 cur_gr = cur_gr.parent_group
872 881 if gr is None:
873 882 break
874 883 groups.insert(0, gr)
875 884
876 885 return groups
877 886
878 887 @property
879 888 def groups_and_repo(self):
880 889 return self.groups_with_parents, self.just_name
881 890
882 891 @LazyProperty
883 892 def repo_path(self):
884 893 """
885 894 Returns base full path for that repository means where it actually
886 895 exists on a filesystem
887 896 """
888 897 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
889 898 Repository.url_sep())
890 899 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
891 900 return q.one().ui_value
892 901
893 902 @property
894 903 def repo_full_path(self):
895 904 p = [self.repo_path]
896 905 # we need to split the name by / since this is how we store the
897 906 # names in the database, but that eventually needs to be converted
898 907 # into a valid system path
899 908 p += self.repo_name.split(Repository.url_sep())
900 909 return os.path.join(*p)
901 910
902 911 @property
903 912 def cache_keys(self):
904 913 """
905 914 Returns associated cache keys for that repo
906 915 """
907 916 return CacheInvalidation.query()\
908 917 .filter(CacheInvalidation.cache_args == self.repo_name)\
909 918 .order_by(CacheInvalidation.cache_key)\
910 919 .all()
911 920
912 921 def get_new_name(self, repo_name):
913 922 """
914 923 returns new full repository name based on assigned group and new new
915 924
916 925 :param group_name:
917 926 """
918 927 path_prefix = self.group.full_path_splitted if self.group else []
919 928 return Repository.url_sep().join(path_prefix + [repo_name])
920 929
921 930 @property
922 931 def _ui(self):
923 932 """
924 933 Creates an db based ui object for this repository
925 934 """
926 935 from rhodecode.lib.utils import make_ui
927 936 return make_ui('db', clear_session=False)
928 937
929 938 @classmethod
930 939 def inject_ui(cls, repo, extras={}):
931 940 from rhodecode.lib.vcs.backends.hg import MercurialRepository
932 941 from rhodecode.lib.vcs.backends.git import GitRepository
933 942 required = (MercurialRepository, GitRepository)
934 943 if not isinstance(repo, required):
935 944 raise Exception('repo must be instance of %s' % required)
936 945
937 946 # inject ui extra param to log this action via push logger
938 947 for k, v in extras.items():
939 948 repo._repo.ui.setconfig('rhodecode_extras', k, v)
940 949
941 950 @classmethod
942 951 def is_valid(cls, repo_name):
943 952 """
944 953 returns True if given repo name is a valid filesystem repository
945 954
946 955 :param cls:
947 956 :param repo_name:
948 957 """
949 958 from rhodecode.lib.utils import is_valid_repo
950 959
951 960 return is_valid_repo(repo_name, cls.base_path())
952 961
953 962 def get_api_data(self):
954 963 """
955 964 Common function for generating repo api data
956 965
957 966 """
958 967 repo = self
959 968 data = dict(
960 969 repo_id=repo.repo_id,
961 970 repo_name=repo.repo_name,
962 971 repo_type=repo.repo_type,
963 972 clone_uri=repo.clone_uri,
964 973 private=repo.private,
965 974 created_on=repo.created_on,
966 975 description=repo.description,
967 976 landing_rev=repo.landing_rev,
968 977 owner=repo.user.username,
969 978 fork_of=repo.fork.repo_name if repo.fork else None,
970 979 enable_statistics=repo.enable_statistics,
971 980 enable_locking=repo.enable_locking,
972 981 enable_downloads=repo.enable_downloads,
973 982 last_changeset=repo.changeset_cache
974 983 )
975 984 rc_config = RhodeCodeSetting.get_app_settings()
976 985 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
977 986 if repository_fields:
978 987 for f in self.extra_fields:
979 988 data[f.field_key_prefixed] = f.field_value
980 989
981 990 return data
982 991
983 992 @classmethod
984 993 def lock(cls, repo, user_id):
985 994 repo.locked = [user_id, time.time()]
986 995 Session().add(repo)
987 996 Session().commit()
988 997
989 998 @classmethod
990 999 def unlock(cls, repo):
991 1000 repo.locked = None
992 1001 Session().add(repo)
993 1002 Session().commit()
994 1003
995 1004 @property
996 1005 def last_db_change(self):
997 1006 return self.updated_on
998 1007
999 1008 def clone_url(self, **override):
1000 1009 from pylons import url
1001 1010 from urlparse import urlparse
1002 1011 import urllib
1003 1012 parsed_url = urlparse(url('home', qualified=True))
1004 1013 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1005 1014 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1006 1015 args = {
1007 1016 'user': '',
1008 1017 'pass': '',
1009 1018 'scheme': parsed_url.scheme,
1010 1019 'netloc': parsed_url.netloc,
1011 1020 'prefix': decoded_path,
1012 1021 'path': self.repo_name
1013 1022 }
1014 1023
1015 1024 args.update(override)
1016 1025 return default_clone_uri % args
1017 1026
1018 1027 #==========================================================================
1019 1028 # SCM PROPERTIES
1020 1029 #==========================================================================
1021 1030
1022 1031 def get_changeset(self, rev=None):
1023 1032 return get_changeset_safe(self.scm_instance, rev)
1024 1033
1025 1034 def get_landing_changeset(self):
1026 1035 """
1027 1036 Returns landing changeset, or if that doesn't exist returns the tip
1028 1037 """
1029 1038 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1030 1039 return cs
1031 1040
1032 1041 def update_changeset_cache(self, cs_cache=None):
1033 1042 """
1034 1043 Update cache of last changeset for repository, keys should be::
1035 1044
1036 1045 short_id
1037 1046 raw_id
1038 1047 revision
1039 1048 message
1040 1049 date
1041 1050 author
1042 1051
1043 1052 :param cs_cache:
1044 1053 """
1045 1054 from rhodecode.lib.vcs.backends.base import BaseChangeset
1046 1055 if cs_cache is None:
1047 1056 cs_cache = self.get_changeset()
1048 1057 if isinstance(cs_cache, BaseChangeset):
1049 1058 cs_cache = cs_cache.__json__()
1050 1059
1051 1060 if (cs_cache != self.changeset_cache
1052 1061 or not self.last_change
1053 1062 or not self.changeset_cache):
1054 1063 _default = datetime.datetime.fromtimestamp(0)
1055 1064 last_change = cs_cache.get('date') or self.last_change or _default
1056 1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1057 1066 self.updated_on = last_change
1058 1067 self.changeset_cache = cs_cache
1059 1068 Session().add(self)
1060 1069 Session().commit()
1061 1070 else:
1062 1071 log.debug('Skipping repo:%s already with latest changes' % self)
1063 1072
1064 1073 @property
1065 1074 def tip(self):
1066 1075 return self.get_changeset('tip')
1067 1076
1068 1077 @property
1069 1078 def author(self):
1070 1079 return self.tip.author
1071 1080
1072 1081 @property
1073 1082 def last_change(self):
1074 1083 return self.scm_instance.last_change
1075 1084
1076 1085 def get_comments(self, revisions=None):
1077 1086 """
1078 1087 Returns comments for this repository grouped by revisions
1079 1088
1080 1089 :param revisions: filter query by revisions only
1081 1090 """
1082 1091 cmts = ChangesetComment.query()\
1083 1092 .filter(ChangesetComment.repo == self)
1084 1093 if revisions:
1085 1094 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1086 1095 grouped = defaultdict(list)
1087 1096 for cmt in cmts.all():
1088 1097 grouped[cmt.revision].append(cmt)
1089 1098 return grouped
1090 1099
1091 1100 def statuses(self, revisions=None):
1092 1101 """
1093 1102 Returns statuses for this repository
1094 1103
1095 1104 :param revisions: list of revisions to get statuses for
1096 1105 :type revisions: list
1097 1106 """
1098 1107
1099 1108 statuses = ChangesetStatus.query()\
1100 1109 .filter(ChangesetStatus.repo == self)\
1101 1110 .filter(ChangesetStatus.version == 0)
1102 1111 if revisions:
1103 1112 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1104 1113 grouped = {}
1105 1114
1106 1115 #maybe we have open new pullrequest without a status ?
1107 1116 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1108 1117 status_lbl = ChangesetStatus.get_status_lbl(stat)
1109 1118 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1110 1119 for rev in pr.revisions:
1111 1120 pr_id = pr.pull_request_id
1112 1121 pr_repo = pr.other_repo.repo_name
1113 1122 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1114 1123
1115 1124 for stat in statuses.all():
1116 1125 pr_id = pr_repo = None
1117 1126 if stat.pull_request:
1118 1127 pr_id = stat.pull_request.pull_request_id
1119 1128 pr_repo = stat.pull_request.other_repo.repo_name
1120 1129 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1121 1130 pr_id, pr_repo]
1122 1131 return grouped
1123 1132
1124 1133 def _repo_size(self):
1125 1134 from rhodecode.lib import helpers as h
1126 1135 log.debug('calculating repository size...')
1127 1136 return h.format_byte_size(self.scm_instance.size)
1128 1137
1129 1138 #==========================================================================
1130 1139 # SCM CACHE INSTANCE
1131 1140 #==========================================================================
1132 1141
1133 1142 @property
1134 1143 def invalidate(self):
1135 1144 return CacheInvalidation.invalidate(self.repo_name)
1136 1145
1137 1146 def set_invalidate(self):
1138 1147 """
1139 1148 set a cache for invalidation for this instance
1140 1149 """
1141 1150 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1142 1151
1143 1152 @LazyProperty
1144 1153 def scm_instance_no_cache(self):
1145 1154 return self.__get_instance()
1146 1155
1147 1156 @LazyProperty
1148 1157 def scm_instance(self):
1149 1158 import rhodecode
1150 1159 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1151 1160 if full_cache:
1152 1161 return self.scm_instance_cached()
1153 1162 return self.__get_instance()
1154 1163
1155 1164 def scm_instance_cached(self, cache_map=None):
1156 1165 @cache_region('long_term')
1157 1166 def _c(repo_name):
1158 1167 return self.__get_instance()
1159 1168 rn = self.repo_name
1160 1169 log.debug('Getting cached instance of repo')
1161 1170
1162 1171 if cache_map:
1163 1172 # get using prefilled cache_map
1164 1173 invalidate_repo = cache_map[self.repo_name]
1165 1174 if invalidate_repo:
1166 1175 invalidate_repo = (None if invalidate_repo.cache_active
1167 1176 else invalidate_repo)
1168 1177 else:
1169 1178 # get from invalidate
1170 1179 invalidate_repo = self.invalidate
1171 1180
1172 1181 if invalidate_repo is not None:
1173 1182 region_invalidate(_c, None, rn)
1174 1183 # update our cache
1175 1184 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1176 1185 return _c(rn)
1177 1186
1178 1187 def __get_instance(self):
1179 1188 repo_full_path = self.repo_full_path
1180 1189 try:
1181 1190 alias = get_scm(repo_full_path)[0]
1182 1191 log.debug('Creating instance of %s repository' % alias)
1183 1192 backend = get_backend(alias)
1184 1193 except VCSError:
1185 1194 log.error(traceback.format_exc())
1186 1195 log.error('Perhaps this repository is in db and not in '
1187 1196 'filesystem run rescan repositories with '
1188 1197 '"destroy old data " option from admin panel')
1189 1198 return
1190 1199
1191 1200 if alias == 'hg':
1192 1201
1193 1202 repo = backend(safe_str(repo_full_path), create=False,
1194 1203 baseui=self._ui)
1195 1204 # skip hidden web repository
1196 1205 if repo._get_hidden():
1197 1206 return
1198 1207 else:
1199 1208 repo = backend(repo_full_path, create=False)
1200 1209
1201 1210 return repo
1202 1211
1203 1212
1204 1213 class RepoGroup(Base, BaseModel):
1205 1214 __tablename__ = 'groups'
1206 1215 __table_args__ = (
1207 1216 UniqueConstraint('group_name', 'group_parent_id'),
1208 1217 CheckConstraint('group_id != group_parent_id'),
1209 1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1210 1219 'mysql_charset': 'utf8'},
1211 1220 )
1212 1221 __mapper_args__ = {'order_by': 'group_name'}
1213 1222
1214 1223 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1215 1224 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1216 1225 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1217 1226 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1218 1227 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1219 1228
1220 1229 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1221 1230 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1222 1231
1223 1232 parent_group = relationship('RepoGroup', remote_side=group_id)
1224 1233
1225 1234 def __init__(self, group_name='', parent_group=None):
1226 1235 self.group_name = group_name
1227 1236 self.parent_group = parent_group
1228 1237
1229 1238 def __unicode__(self):
1230 1239 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1231 1240 self.group_name)
1232 1241
1233 1242 @classmethod
1234 1243 def groups_choices(cls, groups=None, show_empty_group=True):
1235 1244 from webhelpers.html import literal as _literal
1236 1245 if not groups:
1237 1246 groups = cls.query().all()
1238 1247
1239 1248 repo_groups = []
1240 1249 if show_empty_group:
1241 1250 repo_groups = [('-1', '-- no parent --')]
1242 1251 sep = ' &raquo; '
1243 1252 _name = lambda k: _literal(sep.join(k))
1244 1253
1245 1254 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1246 1255 for x in groups])
1247 1256
1248 1257 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1249 1258 return repo_groups
1250 1259
1251 1260 @classmethod
1252 1261 def url_sep(cls):
1253 1262 return URL_SEP
1254 1263
1255 1264 @classmethod
1256 1265 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1257 1266 if case_insensitive:
1258 1267 gr = cls.query()\
1259 1268 .filter(cls.group_name.ilike(group_name))
1260 1269 else:
1261 1270 gr = cls.query()\
1262 1271 .filter(cls.group_name == group_name)
1263 1272 if cache:
1264 1273 gr = gr.options(FromCache(
1265 1274 "sql_cache_short",
1266 1275 "get_group_%s" % _hash_key(group_name)
1267 1276 )
1268 1277 )
1269 1278 return gr.scalar()
1270 1279
1271 1280 @property
1272 1281 def parents(self):
1273 1282 parents_recursion_limit = 5
1274 1283 groups = []
1275 1284 if self.parent_group is None:
1276 1285 return groups
1277 1286 cur_gr = self.parent_group
1278 1287 groups.insert(0, cur_gr)
1279 1288 cnt = 0
1280 1289 while 1:
1281 1290 cnt += 1
1282 1291 gr = getattr(cur_gr, 'parent_group', None)
1283 1292 cur_gr = cur_gr.parent_group
1284 1293 if gr is None:
1285 1294 break
1286 1295 if cnt == parents_recursion_limit:
1287 1296 # this will prevent accidental infinit loops
1288 1297 log.error('group nested more than %s' %
1289 1298 parents_recursion_limit)
1290 1299 break
1291 1300
1292 1301 groups.insert(0, gr)
1293 1302 return groups
1294 1303
1295 1304 @property
1296 1305 def children(self):
1297 1306 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1298 1307
1299 1308 @property
1300 1309 def name(self):
1301 1310 return self.group_name.split(RepoGroup.url_sep())[-1]
1302 1311
1303 1312 @property
1304 1313 def full_path(self):
1305 1314 return self.group_name
1306 1315
1307 1316 @property
1308 1317 def full_path_splitted(self):
1309 1318 return self.group_name.split(RepoGroup.url_sep())
1310 1319
1311 1320 @property
1312 1321 def repositories(self):
1313 1322 return Repository.query()\
1314 1323 .filter(Repository.group == self)\
1315 1324 .order_by(Repository.repo_name)
1316 1325
1317 1326 @property
1318 1327 def repositories_recursive_count(self):
1319 1328 cnt = self.repositories.count()
1320 1329
1321 1330 def children_count(group):
1322 1331 cnt = 0
1323 1332 for child in group.children:
1324 1333 cnt += child.repositories.count()
1325 1334 cnt += children_count(child)
1326 1335 return cnt
1327 1336
1328 1337 return cnt + children_count(self)
1329 1338
1330 1339 def recursive_groups_and_repos(self):
1331 1340 """
1332 1341 Recursive return all groups, with repositories in those groups
1333 1342 """
1334 1343 all_ = []
1335 1344
1336 1345 def _get_members(root_gr):
1337 1346 for r in root_gr.repositories:
1338 1347 all_.append(r)
1339 1348 childs = root_gr.children.all()
1340 1349 if childs:
1341 1350 for gr in childs:
1342 1351 all_.append(gr)
1343 1352 _get_members(gr)
1344 1353
1345 1354 _get_members(self)
1346 1355 return [self] + all_
1347 1356
1348 1357 def get_new_name(self, group_name):
1349 1358 """
1350 1359 returns new full group name based on parent and new name
1351 1360
1352 1361 :param group_name:
1353 1362 """
1354 1363 path_prefix = (self.parent_group.full_path_splitted if
1355 1364 self.parent_group else [])
1356 1365 return RepoGroup.url_sep().join(path_prefix + [group_name])
1357 1366
1358 1367
1359 1368 class Permission(Base, BaseModel):
1360 1369 __tablename__ = 'permissions'
1361 1370 __table_args__ = (
1362 1371 Index('p_perm_name_idx', 'permission_name'),
1363 1372 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1364 1373 'mysql_charset': 'utf8'},
1365 1374 )
1366 1375 PERMS = [
1367 1376 ('repository.none', _('Repository no access')),
1368 1377 ('repository.read', _('Repository read access')),
1369 1378 ('repository.write', _('Repository write access')),
1370 1379 ('repository.admin', _('Repository admin access')),
1371 1380
1372 1381 ('group.none', _('Repositories Group no access')),
1373 1382 ('group.read', _('Repositories Group read access')),
1374 1383 ('group.write', _('Repositories Group write access')),
1375 1384 ('group.admin', _('Repositories Group admin access')),
1376 1385
1377 1386 ('hg.admin', _('RhodeCode Administrator')),
1378 1387 ('hg.create.none', _('Repository creation disabled')),
1379 1388 ('hg.create.repository', _('Repository creation enabled')),
1380 1389 ('hg.fork.none', _('Repository forking disabled')),
1381 1390 ('hg.fork.repository', _('Repository forking enabled')),
1382 1391 ('hg.register.none', _('Register disabled')),
1383 1392 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1384 1393 'with manual activation')),
1385 1394
1386 1395 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1387 1396 'with auto activation')),
1388 1397 ]
1389 1398
1390 1399 # defines which permissions are more important higher the more important
1391 1400 PERM_WEIGHTS = {
1392 1401 'repository.none': 0,
1393 1402 'repository.read': 1,
1394 1403 'repository.write': 3,
1395 1404 'repository.admin': 4,
1396 1405
1397 1406 'group.none': 0,
1398 1407 'group.read': 1,
1399 1408 'group.write': 3,
1400 1409 'group.admin': 4,
1401 1410
1402 1411 'hg.fork.none': 0,
1403 1412 'hg.fork.repository': 1,
1404 1413 'hg.create.none': 0,
1405 1414 'hg.create.repository':1
1406 1415 }
1407 1416
1408 1417 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1409 1418 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1410 1419 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1411 1420
1412 1421 def __unicode__(self):
1413 1422 return u"<%s('%s:%s')>" % (
1414 1423 self.__class__.__name__, self.permission_id, self.permission_name
1415 1424 )
1416 1425
1417 1426 @classmethod
1418 1427 def get_by_key(cls, key):
1419 1428 return cls.query().filter(cls.permission_name == key).scalar()
1420 1429
1421 1430 @classmethod
1422 1431 def get_default_perms(cls, default_user_id):
1423 1432 q = Session().query(UserRepoToPerm, Repository, cls)\
1424 1433 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1425 1434 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1426 1435 .filter(UserRepoToPerm.user_id == default_user_id)
1427 1436
1428 1437 return q.all()
1429 1438
1430 1439 @classmethod
1431 1440 def get_default_group_perms(cls, default_user_id):
1432 1441 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1433 1442 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1434 1443 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1435 1444 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1436 1445
1437 1446 return q.all()
1438 1447
1439 1448
1440 1449 class UserRepoToPerm(Base, BaseModel):
1441 1450 __tablename__ = 'repo_to_perm'
1442 1451 __table_args__ = (
1443 1452 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1444 1453 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1445 1454 'mysql_charset': 'utf8'}
1446 1455 )
1447 1456 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1448 1457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1449 1458 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1450 1459 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1451 1460
1452 1461 user = relationship('User')
1453 1462 repository = relationship('Repository')
1454 1463 permission = relationship('Permission')
1455 1464
1456 1465 @classmethod
1457 1466 def create(cls, user, repository, permission):
1458 1467 n = cls()
1459 1468 n.user = user
1460 1469 n.repository = repository
1461 1470 n.permission = permission
1462 1471 Session().add(n)
1463 1472 return n
1464 1473
1465 1474 def __unicode__(self):
1466 1475 return u'<user:%s => %s >' % (self.user, self.repository)
1467 1476
1468 1477
1469 1478 class UserToPerm(Base, BaseModel):
1470 1479 __tablename__ = 'user_to_perm'
1471 1480 __table_args__ = (
1472 1481 UniqueConstraint('user_id', 'permission_id'),
1473 1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1474 1483 'mysql_charset': 'utf8'}
1475 1484 )
1476 1485 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1477 1486 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1478 1487 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1479 1488
1480 1489 user = relationship('User')
1481 1490 permission = relationship('Permission', lazy='joined')
1482 1491
1483 1492
1484 1493 class UsersGroupRepoToPerm(Base, BaseModel):
1485 1494 __tablename__ = 'users_group_repo_to_perm'
1486 1495 __table_args__ = (
1487 1496 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1488 1497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1489 1498 'mysql_charset': 'utf8'}
1490 1499 )
1491 1500 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1492 1501 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1493 1502 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1494 1503 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1495 1504
1496 1505 users_group = relationship('UsersGroup')
1497 1506 permission = relationship('Permission')
1498 1507 repository = relationship('Repository')
1499 1508
1500 1509 @classmethod
1501 1510 def create(cls, users_group, repository, permission):
1502 1511 n = cls()
1503 1512 n.users_group = users_group
1504 1513 n.repository = repository
1505 1514 n.permission = permission
1506 1515 Session().add(n)
1507 1516 return n
1508 1517
1509 1518 def __unicode__(self):
1510 1519 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1511 1520
1512 1521
1513 1522 class UsersGroupToPerm(Base, BaseModel):
1514 1523 __tablename__ = 'users_group_to_perm'
1515 1524 __table_args__ = (
1516 1525 UniqueConstraint('users_group_id', 'permission_id',),
1517 1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1518 1527 'mysql_charset': 'utf8'}
1519 1528 )
1520 1529 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1521 1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1522 1531 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1523 1532
1524 1533 users_group = relationship('UsersGroup')
1525 1534 permission = relationship('Permission')
1526 1535
1527 1536
1528 1537 class UserRepoGroupToPerm(Base, BaseModel):
1529 1538 __tablename__ = 'user_repo_group_to_perm'
1530 1539 __table_args__ = (
1531 1540 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1532 1541 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1533 1542 'mysql_charset': 'utf8'}
1534 1543 )
1535 1544
1536 1545 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1537 1546 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1538 1547 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1539 1548 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1540 1549
1541 1550 user = relationship('User')
1542 1551 group = relationship('RepoGroup')
1543 1552 permission = relationship('Permission')
1544 1553
1545 1554
1546 1555 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1547 1556 __tablename__ = 'users_group_repo_group_to_perm'
1548 1557 __table_args__ = (
1549 1558 UniqueConstraint('users_group_id', 'group_id'),
1550 1559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1551 1560 'mysql_charset': 'utf8'}
1552 1561 )
1553 1562
1554 1563 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1555 1564 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1556 1565 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1557 1566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1558 1567
1559 1568 users_group = relationship('UsersGroup')
1560 1569 permission = relationship('Permission')
1561 1570 group = relationship('RepoGroup')
1562 1571
1563 1572
1564 1573 class Statistics(Base, BaseModel):
1565 1574 __tablename__ = 'statistics'
1566 1575 __table_args__ = (
1567 1576 UniqueConstraint('repository_id'),
1568 1577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1569 1578 'mysql_charset': 'utf8'}
1570 1579 )
1571 1580 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1572 1581 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1573 1582 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1574 1583 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1575 1584 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1576 1585 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1577 1586
1578 1587 repository = relationship('Repository', single_parent=True)
1579 1588
1580 1589
1581 1590 class UserFollowing(Base, BaseModel):
1582 1591 __tablename__ = 'user_followings'
1583 1592 __table_args__ = (
1584 1593 UniqueConstraint('user_id', 'follows_repository_id'),
1585 1594 UniqueConstraint('user_id', 'follows_user_id'),
1586 1595 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1587 1596 'mysql_charset': 'utf8'}
1588 1597 )
1589 1598
1590 1599 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 1600 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1592 1601 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1593 1602 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1594 1603 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1595 1604
1596 1605 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1597 1606
1598 1607 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1599 1608 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1600 1609
1601 1610 @classmethod
1602 1611 def get_repo_followers(cls, repo_id):
1603 1612 return cls.query().filter(cls.follows_repo_id == repo_id)
1604 1613
1605 1614
1606 1615 class CacheInvalidation(Base, BaseModel):
1607 1616 __tablename__ = 'cache_invalidation'
1608 1617 __table_args__ = (
1609 1618 UniqueConstraint('cache_key'),
1610 1619 Index('key_idx', 'cache_key'),
1611 1620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1612 1621 'mysql_charset': 'utf8'},
1613 1622 )
1614 1623 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1615 1624 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1616 1625 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1617 1626 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1618 1627
1619 1628 def __init__(self, cache_key, cache_args=''):
1620 1629 self.cache_key = cache_key
1621 1630 self.cache_args = cache_args
1622 1631 self.cache_active = False
1623 1632
1624 1633 def __unicode__(self):
1625 1634 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1626 1635 self.cache_id, self.cache_key)
1627 1636
1628 1637 @property
1629 1638 def prefix(self):
1630 1639 _split = self.cache_key.split(self.cache_args, 1)
1631 1640 if _split and len(_split) == 2:
1632 1641 return _split[0]
1633 1642 return ''
1634 1643
1635 1644 @classmethod
1636 1645 def clear_cache(cls):
1637 1646 cls.query().delete()
1638 1647
1639 1648 @classmethod
1640 1649 def _get_key(cls, key):
1641 1650 """
1642 1651 Wrapper for generating a key, together with a prefix
1643 1652
1644 1653 :param key:
1645 1654 """
1646 1655 import rhodecode
1647 1656 prefix = ''
1648 1657 org_key = key
1649 1658 iid = rhodecode.CONFIG.get('instance_id')
1650 1659 if iid:
1651 1660 prefix = iid
1652 1661
1653 1662 return "%s%s" % (prefix, key), prefix, org_key
1654 1663
1655 1664 @classmethod
1656 1665 def get_by_key(cls, key):
1657 1666 return cls.query().filter(cls.cache_key == key).scalar()
1658 1667
1659 1668 @classmethod
1660 1669 def get_by_repo_name(cls, repo_name):
1661 1670 return cls.query().filter(cls.cache_args == repo_name).all()
1662 1671
1663 1672 @classmethod
1664 1673 def _get_or_create_key(cls, key, repo_name, commit=True):
1665 1674 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1666 1675 if not inv_obj:
1667 1676 try:
1668 1677 inv_obj = CacheInvalidation(key, repo_name)
1669 1678 Session().add(inv_obj)
1670 1679 if commit:
1671 1680 Session().commit()
1672 1681 except Exception:
1673 1682 log.error(traceback.format_exc())
1674 1683 Session().rollback()
1675 1684 return inv_obj
1676 1685
1677 1686 @classmethod
1678 1687 def invalidate(cls, key):
1679 1688 """
1680 1689 Returns Invalidation object if this given key should be invalidated
1681 1690 None otherwise. `cache_active = False` means that this cache
1682 1691 state is not valid and needs to be invalidated
1683 1692
1684 1693 :param key:
1685 1694 """
1686 1695 repo_name = key
1687 1696 repo_name = remove_suffix(repo_name, '_README')
1688 1697 repo_name = remove_suffix(repo_name, '_RSS')
1689 1698 repo_name = remove_suffix(repo_name, '_ATOM')
1690 1699
1691 1700 # adds instance prefix
1692 1701 key, _prefix, _org_key = cls._get_key(key)
1693 1702 inv = cls._get_or_create_key(key, repo_name)
1694 1703
1695 1704 if inv and inv.cache_active is False:
1696 1705 return inv
1697 1706
1698 1707 @classmethod
1699 1708 def set_invalidate(cls, key=None, repo_name=None):
1700 1709 """
1701 1710 Mark this Cache key for invalidation, either by key or whole
1702 1711 cache sets based on repo_name
1703 1712
1704 1713 :param key:
1705 1714 """
1706 1715 invalidated_keys = []
1707 1716 if key:
1708 1717 key, _prefix, _org_key = cls._get_key(key)
1709 1718 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1710 1719 elif repo_name:
1711 1720 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1712 1721
1713 1722 try:
1714 1723 for inv_obj in inv_objs:
1715 1724 inv_obj.cache_active = False
1716 1725 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1717 1726 % (inv_obj, key, repo_name))
1718 1727 invalidated_keys.append(inv_obj.cache_key)
1719 1728 Session().add(inv_obj)
1720 1729 Session().commit()
1721 1730 except Exception:
1722 1731 log.error(traceback.format_exc())
1723 1732 Session().rollback()
1724 1733 return invalidated_keys
1725 1734
1726 1735 @classmethod
1727 1736 def set_valid(cls, key):
1728 1737 """
1729 1738 Mark this cache key as active and currently cached
1730 1739
1731 1740 :param key:
1732 1741 """
1733 1742 inv_obj = cls.get_by_key(key)
1734 1743 inv_obj.cache_active = True
1735 1744 Session().add(inv_obj)
1736 1745 Session().commit()
1737 1746
1738 1747 @classmethod
1739 1748 def get_cache_map(cls):
1740 1749
1741 1750 class cachemapdict(dict):
1742 1751
1743 1752 def __init__(self, *args, **kwargs):
1744 1753 fixkey = kwargs.get('fixkey')
1745 1754 if fixkey:
1746 1755 del kwargs['fixkey']
1747 1756 self.fixkey = fixkey
1748 1757 super(cachemapdict, self).__init__(*args, **kwargs)
1749 1758
1750 1759 def __getattr__(self, name):
1751 1760 key = name
1752 1761 if self.fixkey:
1753 1762 key, _prefix, _org_key = cls._get_key(key)
1754 1763 if key in self.__dict__:
1755 1764 return self.__dict__[key]
1756 1765 else:
1757 1766 return self[key]
1758 1767
1759 1768 def __getitem__(self, key):
1760 1769 if self.fixkey:
1761 1770 key, _prefix, _org_key = cls._get_key(key)
1762 1771 try:
1763 1772 return super(cachemapdict, self).__getitem__(key)
1764 1773 except KeyError:
1765 1774 return
1766 1775
1767 1776 cache_map = cachemapdict(fixkey=True)
1768 1777 for obj in cls.query().all():
1769 1778 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1770 1779 return cache_map
1771 1780
1772 1781
1773 1782 class ChangesetComment(Base, BaseModel):
1774 1783 __tablename__ = 'changeset_comments'
1775 1784 __table_args__ = (
1776 1785 Index('cc_revision_idx', 'revision'),
1777 1786 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1778 1787 'mysql_charset': 'utf8'},
1779 1788 )
1780 1789 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1781 1790 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1782 1791 revision = Column('revision', String(40), nullable=True)
1783 1792 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1784 1793 line_no = Column('line_no', Unicode(10), nullable=True)
1785 1794 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1786 1795 f_path = Column('f_path', Unicode(1000), nullable=True)
1787 1796 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1788 1797 text = Column('text', UnicodeText(25000), nullable=False)
1789 1798 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1790 1799 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1791 1800
1792 1801 author = relationship('User', lazy='joined')
1793 1802 repo = relationship('Repository')
1794 1803 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1795 1804 pull_request = relationship('PullRequest', lazy='joined')
1796 1805
1797 1806 @classmethod
1798 1807 def get_users(cls, revision=None, pull_request_id=None):
1799 1808 """
1800 1809 Returns user associated with this ChangesetComment. ie those
1801 1810 who actually commented
1802 1811
1803 1812 :param cls:
1804 1813 :param revision:
1805 1814 """
1806 1815 q = Session().query(User)\
1807 1816 .join(ChangesetComment.author)
1808 1817 if revision:
1809 1818 q = q.filter(cls.revision == revision)
1810 1819 elif pull_request_id:
1811 1820 q = q.filter(cls.pull_request_id == pull_request_id)
1812 1821 return q.all()
1813 1822
1814 1823
1815 1824 class ChangesetStatus(Base, BaseModel):
1816 1825 __tablename__ = 'changeset_statuses'
1817 1826 __table_args__ = (
1818 1827 Index('cs_revision_idx', 'revision'),
1819 1828 Index('cs_version_idx', 'version'),
1820 1829 UniqueConstraint('repo_id', 'revision', 'version'),
1821 1830 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1822 1831 'mysql_charset': 'utf8'}
1823 1832 )
1824 1833 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1825 1834 STATUS_APPROVED = 'approved'
1826 1835 STATUS_REJECTED = 'rejected'
1827 1836 STATUS_UNDER_REVIEW = 'under_review'
1828 1837
1829 1838 STATUSES = [
1830 1839 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1831 1840 (STATUS_APPROVED, _("Approved")),
1832 1841 (STATUS_REJECTED, _("Rejected")),
1833 1842 (STATUS_UNDER_REVIEW, _("Under Review")),
1834 1843 ]
1835 1844
1836 1845 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1837 1846 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1838 1847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1839 1848 revision = Column('revision', String(40), nullable=False)
1840 1849 status = Column('status', String(128), nullable=False, default=DEFAULT)
1841 1850 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1842 1851 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1843 1852 version = Column('version', Integer(), nullable=False, default=0)
1844 1853 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1845 1854
1846 1855 author = relationship('User', lazy='joined')
1847 1856 repo = relationship('Repository')
1848 1857 comment = relationship('ChangesetComment', lazy='joined')
1849 1858 pull_request = relationship('PullRequest', lazy='joined')
1850 1859
1851 1860 def __unicode__(self):
1852 1861 return u"<%s('%s:%s')>" % (
1853 1862 self.__class__.__name__,
1854 1863 self.status, self.author
1855 1864 )
1856 1865
1857 1866 @classmethod
1858 1867 def get_status_lbl(cls, value):
1859 1868 return dict(cls.STATUSES).get(value)
1860 1869
1861 1870 @property
1862 1871 def status_lbl(self):
1863 1872 return ChangesetStatus.get_status_lbl(self.status)
1864 1873
1865 1874
1866 1875 class PullRequest(Base, BaseModel):
1867 1876 __tablename__ = 'pull_requests'
1868 1877 __table_args__ = (
1869 1878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1870 1879 'mysql_charset': 'utf8'},
1871 1880 )
1872 1881
1873 1882 STATUS_NEW = u'new'
1874 1883 STATUS_OPEN = u'open'
1875 1884 STATUS_CLOSED = u'closed'
1876 1885
1877 1886 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1878 1887 title = Column('title', Unicode(256), nullable=True)
1879 1888 description = Column('description', UnicodeText(10240), nullable=True)
1880 1889 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1881 1890 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1882 1891 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1883 1892 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1884 1893 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1885 1894 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1886 1895 org_ref = Column('org_ref', Unicode(256), nullable=False)
1887 1896 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1888 1897 other_ref = Column('other_ref', Unicode(256), nullable=False)
1889 1898
1890 1899 @hybrid_property
1891 1900 def revisions(self):
1892 1901 return self._revisions.split(':')
1893 1902
1894 1903 @revisions.setter
1895 1904 def revisions(self, val):
1896 1905 self._revisions = ':'.join(val)
1897 1906
1898 1907 @property
1899 1908 def org_ref_parts(self):
1900 1909 return self.org_ref.split(':')
1901 1910
1902 1911 @property
1903 1912 def other_ref_parts(self):
1904 1913 return self.other_ref.split(':')
1905 1914
1906 1915 author = relationship('User', lazy='joined')
1907 1916 reviewers = relationship('PullRequestReviewers',
1908 1917 cascade="all, delete, delete-orphan")
1909 1918 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1910 1919 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1911 1920 statuses = relationship('ChangesetStatus')
1912 1921 comments = relationship('ChangesetComment',
1913 1922 cascade="all, delete, delete-orphan")
1914 1923
1915 1924 def is_closed(self):
1916 1925 return self.status == self.STATUS_CLOSED
1917 1926
1918 1927 @property
1919 1928 def last_review_status(self):
1920 1929 return self.statuses[-1].status if self.statuses else ''
1921 1930
1922 1931 def __json__(self):
1923 1932 return dict(
1924 1933 revisions=self.revisions
1925 1934 )
1926 1935
1927 1936
1928 1937 class PullRequestReviewers(Base, BaseModel):
1929 1938 __tablename__ = 'pull_request_reviewers'
1930 1939 __table_args__ = (
1931 1940 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1932 1941 'mysql_charset': 'utf8'},
1933 1942 )
1934 1943
1935 1944 def __init__(self, user=None, pull_request=None):
1936 1945 self.user = user
1937 1946 self.pull_request = pull_request
1938 1947
1939 1948 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1940 1949 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1941 1950 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1942 1951
1943 1952 user = relationship('User')
1944 1953 pull_request = relationship('PullRequest')
1945 1954
1946 1955
1947 1956 class Notification(Base, BaseModel):
1948 1957 __tablename__ = 'notifications'
1949 1958 __table_args__ = (
1950 1959 Index('notification_type_idx', 'type'),
1951 1960 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1952 1961 'mysql_charset': 'utf8'},
1953 1962 )
1954 1963
1955 1964 TYPE_CHANGESET_COMMENT = u'cs_comment'
1956 1965 TYPE_MESSAGE = u'message'
1957 1966 TYPE_MENTION = u'mention'
1958 1967 TYPE_REGISTRATION = u'registration'
1959 1968 TYPE_PULL_REQUEST = u'pull_request'
1960 1969 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1961 1970
1962 1971 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1963 1972 subject = Column('subject', Unicode(512), nullable=True)
1964 1973 body = Column('body', UnicodeText(50000), nullable=True)
1965 1974 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1966 1975 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1967 1976 type_ = Column('type', Unicode(256))
1968 1977
1969 1978 created_by_user = relationship('User')
1970 1979 notifications_to_users = relationship('UserNotification', lazy='joined',
1971 1980 cascade="all, delete, delete-orphan")
1972 1981
1973 1982 @property
1974 1983 def recipients(self):
1975 1984 return [x.user for x in UserNotification.query()\
1976 1985 .filter(UserNotification.notification == self)\
1977 1986 .order_by(UserNotification.user_id.asc()).all()]
1978 1987
1979 1988 @classmethod
1980 1989 def create(cls, created_by, subject, body, recipients, type_=None):
1981 1990 if type_ is None:
1982 1991 type_ = Notification.TYPE_MESSAGE
1983 1992
1984 1993 notification = cls()
1985 1994 notification.created_by_user = created_by
1986 1995 notification.subject = subject
1987 1996 notification.body = body
1988 1997 notification.type_ = type_
1989 1998 notification.created_on = datetime.datetime.now()
1990 1999
1991 2000 for u in recipients:
1992 2001 assoc = UserNotification()
1993 2002 assoc.notification = notification
1994 2003 u.notifications.append(assoc)
1995 2004 Session().add(notification)
1996 2005 return notification
1997 2006
1998 2007 @property
1999 2008 def description(self):
2000 2009 from rhodecode.model.notification import NotificationModel
2001 2010 return NotificationModel().make_description(self)
2002 2011
2003 2012
2004 2013 class UserNotification(Base, BaseModel):
2005 2014 __tablename__ = 'user_to_notification'
2006 2015 __table_args__ = (
2007 2016 UniqueConstraint('user_id', 'notification_id'),
2008 2017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2009 2018 'mysql_charset': 'utf8'}
2010 2019 )
2011 2020 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2012 2021 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2013 2022 read = Column('read', Boolean, default=False)
2014 2023 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2015 2024
2016 2025 user = relationship('User', lazy="joined")
2017 2026 notification = relationship('Notification', lazy="joined",
2018 2027 order_by=lambda: Notification.created_on.desc(),)
2019 2028
2020 2029 def mark_as_read(self):
2021 2030 self.read = True
2022 2031 Session().add(self)
2023 2032
2024 2033
2025 2034 class DbMigrateVersion(Base, BaseModel):
2026 2035 __tablename__ = 'db_migrate_version'
2027 2036 __table_args__ = (
2028 2037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2029 2038 'mysql_charset': 'utf8'},
2030 2039 )
2031 2040 repository_id = Column('repository_id', String(250), primary_key=True)
2032 2041 repository_path = Column('repository_path', Text)
2033 2042 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now